Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,11 @@
JDOODLE_API_CLIENT_ID = env("JDOODLE_API_CLIENT_ID", "")
JDOODLE_API_CLIENT_SECRET = env("JDOODLE_API_CLIENT_SECRET", "")

# Monday.com CRM integration
MONDAY_API_TOKEN = env("MONDAY_API_TOKEN", default="")
MONDAY_CONTACTS_BOARD_ID = env("MONDAY_CONTACTS_BOARD_ID", default="")
MONDAY_LEADS_BOARD_ID = env("MONDAY_LEADS_BOARD_ID", default="")

# Django Allauth settings

ACCOUNT_EMAIL_VERIFICATION = "mandatory"
Expand Down
11 changes: 11 additions & 0 deletions core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,17 @@ def get_context_data(self, **kwargs):
],
}

context["achievements_data"] = {
"achievements": [
{
"title": "Lorem Ipsum",
"points": 22,
"description": "A longer description giving a summary of the achievement.",
}
for _ in range(4)
]
}

context["banner_data"] = {
"icon_name": "alert",
"banner_message": "This is an older version of Boost and was released in 2017. The <a href='https://www.example.com'>current version</a> is 1.90.0.",
Expand Down
9 changes: 9 additions & 0 deletions marketing/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ class CapturedEmailResource(resources.ModelResource):
address_country = fields.Field(
column_name="Address (Country)", attribute="address_country"
)
github_username = fields.Field(
column_name="GitHub Username", attribute="github_username"
)

def skip_row(self, instance, original, row, import_validation_errors=None):
if not instance.email:
return True
return super().skip_row(instance, original, row, import_validation_errors)

class Meta:
model = CapturedEmail
Expand All @@ -36,6 +44,7 @@ class Meta:
"address_city",
"address_state",
"address_country",
"github_username",
)


Expand Down
Empty file.
Empty file.
81 changes: 81 additions & 0 deletions marketing/management/commands/sync_monday_crm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import logging

import djclick as click
from django.contrib.auth import get_user_model

from marketing.models import CapturedEmail
from marketing.monday import MondayClient

User = get_user_model()
logger = logging.getLogger(__name__)


@click.command()
@click.option(
"--dry-run",
is_flag=True,
default=False,
help="Print what would be synced without making API calls.",
)
@click.option(
"--board",
type=click.Choice(["contacts", "leads", "all"]),
default="all",
show_default=True,
help="Which Monday.com board to sync.",
)
def command(dry_run, board):
"""Sync Users and CapturedEmails to Monday.com CRM boards.

contacts: active + claimed Users -> Contacts board
leads: non-opted-out emails -> Leads board
all: both boards (default)
"""
if dry_run:
click.secho("DRY RUN — no data will be sent to Monday.com", fg="yellow")

client = None if dry_run else MondayClient()

if board in ("contacts", "all"):
_sync_contacts(client, dry_run)

if board in ("leads", "all"):
_sync_leads(client, dry_run)


def _sync_contacts(client, dry_run):
users = User.objects.filter(is_active=True, claimed=True).order_by("pk")
total = users.count()
click.secho(f"Syncing {total} contacts to Monday.com Contacts board...", fg="green")

if dry_run:
for user in users.iterator():
click.echo(f" [DRY RUN] contact: {user.email}")
return

created, updated = client.bulk_upsert_contacts(users)
click.secho(
f"Contacts done: {created} created, {updated} updated.",
fg="green",
)


def _sync_leads(client, dry_run):
leads = (
CapturedEmail.objects.filter(opted_out=False)
.select_related("page")
.order_by("pk")
)
total = leads.count()
click.secho(f"Syncing {total} leads to Monday.com Leads board...", fg="green")

if dry_run:
for lead in leads.iterator():
click.echo(f" [DRY RUN] lead: {lead.email}")
return

created, updated = client.bulk_upsert_leads(leads)
click.secho(
f"Leads done: {created} created, {updated} updated.",
fg="green",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 6.0.2 on 2026-03-06 01:57

from django.db import migrations, models


def delete_blank_emails(apps, schema_editor):
CapturedEmail = apps.get_model("marketing", "CapturedEmail")
count, _ = CapturedEmail.objects.filter(email="").delete()
if count:
print(f"\n Deleted {count} CapturedEmail record(s) with blank email.")


class Migration(migrations.Migration):

dependencies = [
("marketing", "0003_capturedemail_address_city_and_more"),
("wagtailcore", "0096_referenceindex_referenceindex_source_object_and_more"),
]

operations = [
migrations.RunPython(delete_blank_emails, migrations.RunPython.noop),
migrations.AddField(
model_name="capturedemail",
name="github_username",
field=models.CharField(blank=True, default=""),
),
migrations.AddConstraint(
model_name="capturedemail",
constraint=models.CheckConstraint(
condition=models.Q(("email", ""), _negated=True),
name="captured_email_requires_email",
),
),
]
9 changes: 9 additions & 0 deletions marketing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class CapturedEmail(models.Model):
address_city = models.CharField(blank=True, default="")
address_state = models.CharField(blank=True, default="")
address_country = models.CharField(blank=True, default="")
github_username = models.CharField(blank=True, default="")
opted_out = models.BooleanField(default=False)

referrer = models.CharField(blank=True, default="")
Expand All @@ -35,6 +36,14 @@ class CapturedEmail(models.Model):
)
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.CheckConstraint(
condition=~models.Q(email=""),
name="captured_email_requires_email",
),
]

def __str__(self):
return self.email

Expand Down
Loading
Loading