Adds new Campaign model to make groups of users + way of creating them from users

mailing
Laurent 2 months ago
parent 147c8e9ba3
commit f1c02a7d1b
  1. 16
      biz/admin.py
  2. 15
      biz/filters.py
  3. 38
      biz/migrations/0005_alter_activity_status_campaign.py
  4. 8
      biz/models.py
  5. 31
      tournaments/admin.py

@ -12,9 +12,9 @@ import io
import time import time
import logging import logging
from .models import Entity, Prospect, Activity, Status, ActivityType, EmailTemplate, DeclinationReason from .models import Entity, Prospect, Activity, Status, ActivityType, EmailTemplate, DeclinationReason, Campaign
from .forms import FileImportForm, EmailTemplateSelectionForm from .forms import FileImportForm, EmailTemplateSelectionForm
from .filters import ContactAgainFilter, ProspectStatusFilter, StaffUserFilter, ProspectProfileFilter, ProspectDeclineReasonFilter from .filters import ContactAgainFilter, ProspectStatusFilter, StaffUserFilter, ProspectProfileFilter, ProspectDeclineReasonFilter, ProspectCampaignFilter
from tournaments.models import CustomUser from tournaments.models import CustomUser
from tournaments.models.enums import UserOrigin from tournaments.models.enums import UserOrigin
@ -75,6 +75,10 @@ def declined_android_user(modeladmin, request, queryset):
create_default_activity_for_prospect(modeladmin, request, queryset, None, Status.DECLINED, DeclinationReason.USE_ANDROID) create_default_activity_for_prospect(modeladmin, request, queryset, None, Status.DECLINED, DeclinationReason.USE_ANDROID)
declined_android_user.short_description = "Declined use Android" declined_android_user.short_description = "Declined use Android"
def mark_as_have_account(modeladmin, request, queryset):
create_default_activity_for_prospect(modeladmin, request, queryset, None, Status.HAVE_CREATED_ACCOUNT, None)
mark_as_have_account.short_description = "Mark as having an account"
def create_default_activity_for_prospect(modeladmin, request, queryset, type, status, reason): def create_default_activity_for_prospect(modeladmin, request, queryset, type, status, reason):
for prospect in queryset: for prospect in queryset:
activity = Activity.objects.create( activity = Activity.objects.create(
@ -114,13 +118,13 @@ class ProspectAdmin(SyncedObjectAdmin):
] ]
list_display = ('first_name', 'last_name', 'entity_names', 'last_update_date', 'current_status', 'current_text', 'contact_again') list_display = ('first_name', 'last_name', 'entity_names', 'last_update_date', 'current_status', 'current_text', 'contact_again')
list_filter = (ContactAgainFilter, ProspectStatusFilter, ProspectDeclineReasonFilter, 'creation_date', StaffUserFilter, 'source', ProspectProfileFilter) list_filter = (ContactAgainFilter, ProspectStatusFilter, ProspectDeclineReasonFilter, ProspectCampaignFilter, 'creation_date', StaffUserFilter, 'source', ProspectProfileFilter)
search_fields = ('first_name', 'last_name', 'email', 'entities__name') search_fields = ('first_name', 'last_name', 'email', 'entities__name')
date_hierarchy = 'creation_date' date_hierarchy = 'creation_date'
change_list_template = "admin/biz/prospect/change_list.html" change_list_template = "admin/biz/prospect/change_list.html"
ordering = ['-last_update'] ordering = ['-last_update']
filter_horizontal = ['entities'] filter_horizontal = ['entities']
actions = ['send_email', create_activity_for_prospect, mark_as_inbound, contacted_by_sms, mark_as_should_test, mark_as_testing, mark_as_customer, declined_too_expensive, declined_use_something_else, declined_android_user] actions = ['send_email', create_activity_for_prospect, mark_as_inbound, contacted_by_sms, mark_as_should_test, mark_as_testing, mark_as_customer, mark_as_have_account, declined_too_expensive, declined_use_something_else, declined_android_user]
raw_id_fields = ['official_user', 'related_user'] raw_id_fields = ['official_user', 'related_user']
def last_update_date(self, obj): def last_update_date(self, obj):
@ -397,6 +401,10 @@ class ProspectAdmin(SyncedObjectAdmin):
time.sleep(1) time.sleep(1)
@admin.register(Campaign)
class CampaignAdmin(SyncedObjectAdmin):
list_display = ('name', 'user_count')
date_hierarchy = 'creation_date'
@admin.register(Activity) @admin.register(Activity)
class ActivityAdmin(SyncedObjectAdmin): class ActivityAdmin(SyncedObjectAdmin):

@ -7,7 +7,7 @@ from django.utils import timezone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from .models import Activity, Prospect, Status, DeclinationReason from .models import Activity, Prospect, Status, DeclinationReason, Campaign
User = get_user_model() User = get_user_model()
@ -110,6 +110,19 @@ class ProspectDeclineReasonFilter(admin.SimpleListFilter):
else: else:
return queryset return queryset
class ProspectCampaignFilter(admin.SimpleListFilter):
title = 'Campaign'
parameter_name = 'campaign'
def lookups(self, request, model_admin):
campaigns = Campaign.objects.all().order_by('-creation_date')
return [(campaign.id, campaign.name) for campaign in campaigns]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(campaigns__id=self.value())
return queryset
class ContactAgainFilter(admin.SimpleListFilter): class ContactAgainFilter(admin.SimpleListFilter):
title = 'Contact again' # or whatever you want title = 'Contact again' # or whatever you want
parameter_name = 'contact_again' parameter_name = 'contact_again'

@ -0,0 +1,38 @@
# Generated by Django 5.1 on 2025-09-22 12:34
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('biz', '0004_prospect_contact_again'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='activity',
name='status',
field=models.CharField(blank=True, choices=[('NONE', 'None'), ('INBOUND', 'Inbound'), ('CONTACTED', 'Contacted'), ('RESPONDED', 'Responded'), ('SHOULD_TEST', 'Should test'), ('TESTING', 'Testing'), ('CUSTOMER', 'Customer'), ('LOST', 'Lost customer'), ('DECLINED', 'Declined'), ('NOT_CONCERNED', 'Not concerned'), ('SHOULD_BUY', 'Should buy'), ('HAVE_CREATED_ACCOUNT', 'Have created account')], max_length=50, null=True),
),
migrations.CreateModel(
name='Campaign',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('creation_date', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('last_update', models.DateTimeField(default=django.utils.timezone.now)),
('data_access_ids', models.JSONField(default=list)),
('name', models.CharField(blank=True, max_length=200, null=True)),
('last_updated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('prospects', models.ManyToManyField(blank=True, related_name='campaigns', to='biz.prospect')),
('related_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

@ -25,6 +25,7 @@ class Status(models.TextChoices):
# DECLINED_UNRELATED = 'DECLINED_UNRELATED', 'Declined without significance' # DECLINED_UNRELATED = 'DECLINED_UNRELATED', 'Declined without significance'
NOT_CONCERNED = 'NOT_CONCERNED', 'Not concerned' NOT_CONCERNED = 'NOT_CONCERNED', 'Not concerned'
SHOULD_BUY = 'SHOULD_BUY', 'Should buy' SHOULD_BUY = 'SHOULD_BUY', 'Should buy'
HAVE_CREATED_ACCOUNT = 'HAVE_CREATED_ACCOUNT', 'Have created account'
class DeclinationReason(models.TextChoices): class DeclinationReason(models.TextChoices):
TOO_EXPENSIVE = 'TOO_EXPENSIVE', 'Too expensive' TOO_EXPENSIVE = 'TOO_EXPENSIVE', 'Too expensive'
@ -170,6 +171,13 @@ class EmailTemplate(BaseModel):
def delete_dependencies(self): def delete_dependencies(self):
pass pass
class Campaign(BaseModel):
name = models.CharField(max_length=200, null=True, blank=True)
prospects = models.ManyToManyField(Prospect, blank=True, related_name='campaigns')
def user_count(self):
return self.prospects.count()
# class EmailCampaign(models.Model): # class EmailCampaign(models.Model):
# event = models.OneToOneField(Event, on_delete=models.CASCADE) # event = models.OneToOneField(Event, on_delete=models.CASCADE)
# subject = models.CharField(max_length=200) # subject = models.CharField(max_length=200)

@ -9,7 +9,7 @@ from django.shortcuts import render
from django.db.models import Avg from django.db.models import Avg
from datetime import timedelta, datetime from datetime import timedelta, datetime
from biz.models import Prospect from biz.models import Prospect, Campaign
from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer, Image from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer, Image
from .forms import CustomUserCreationForm, CustomUserChangeForm from .forms import CustomUserCreationForm, CustomUserChangeForm
@ -27,7 +27,7 @@ class CustomUserAdmin(UserAdmin):
model = CustomUser model = CustomUser
search_fields = ['username', 'email', 'phone', 'first_name', 'last_name', 'licence_id'] search_fields = ['username', 'email', 'phone', 'first_name', 'last_name', 'licence_id']
filter_horizontal = ('clubs',) filter_horizontal = ('clubs',)
actions = ['convert_to_prospect'] actions = ['convert_to_prospect', 'create_campaign']
list_display = ['email', 'first_name', 'last_name', 'username', 'date_joined', 'latest_event_club_name', 'is_active', 'event_count', 'origin', 'registration_payment_mode', 'licence_id'] list_display = ['email', 'first_name', 'last_name', 'username', 'date_joined', 'latest_event_club_name', 'is_active', 'event_count', 'origin', 'registration_payment_mode', 'licence_id']
list_filter = ['is_active', 'origin', UserWithEventsFilter, UserWithPurchasesFilter, UserWithProspectFilter] list_filter = ['is_active', 'origin', UserWithEventsFilter, UserWithPurchasesFilter, UserWithProspectFilter]
@ -59,10 +59,35 @@ class CustomUserAdmin(UserAdmin):
obj.last_update = timezone.now() obj.last_update = timezone.now()
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
def create_campaign(self, request, queryset):
prospects = []
source_value = f"auto_created_{datetime.now().strftime('%Y-%m-%d_%H:%M')}"
for user in queryset:
prospect = Prospect.objects.filter(email=user.email).first()
if prospect:
prospects.append(prospect)
else:
prospect = Prospect.objects.create(
first_name=user.first_name,
last_name=user.last_name,
email=user.email,
phone=user.phone,
official_user=user,
source=source_value
)
prospects.append(prospect)
campaign = Campaign.objects.create(
name=f"{datetime.now().strftime('%Y-%m-%d_%H:%M')}",
)
campaign.prospects.add(*prospects)
messages.success(request, f'Created campaign {campaign.name} with {queryset.count()} prospects')
create_campaign.short_description = "Create campaign with selection"
def convert_to_prospect(self, request, queryset): def convert_to_prospect(self, request, queryset):
created_count = 0 created_count = 0
skipped_count = 0 skipped_count = 0
source_value = f"user_conversion_{datetime.now().strftime('%Y%m%d_%H%M')}" source_value = f"user_conversion_{datetime.now().strftime('%Y-%m-%d_%H:%M')}"
for user in queryset: for user in queryset:
if user.email and Prospect.objects.filter(email=user.email).exists(): if user.email and Prospect.objects.filter(email=user.email).exists():

Loading…
Cancel
Save