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 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 .filters import ContactAgainFilter, ProspectStatusFilter, StaffUserFilter, ProspectProfileFilter, ProspectDeclineReasonFilter
from .filters import ContactAgainFilter, ProspectStatusFilter, StaffUserFilter, ProspectProfileFilter, ProspectDeclineReasonFilter, ProspectCampaignFilter
from tournaments.models import CustomUser
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)
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):
for prospect in queryset:
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_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')
date_hierarchy = 'creation_date'
change_list_template = "admin/biz/prospect/change_list.html"
ordering = ['-last_update']
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']
def last_update_date(self, obj):
@ -397,6 +401,10 @@ class ProspectAdmin(SyncedObjectAdmin):
time.sleep(1)
@admin.register(Campaign)
class CampaignAdmin(SyncedObjectAdmin):
list_display = ('name', 'user_count')
date_hierarchy = 'creation_date'
@admin.register(Activity)
class ActivityAdmin(SyncedObjectAdmin):

@ -7,7 +7,7 @@ from django.utils import timezone
from dateutil.relativedelta import relativedelta
from .models import Activity, Prospect, Status, DeclinationReason
from .models import Activity, Prospect, Status, DeclinationReason, Campaign
User = get_user_model()
@ -110,6 +110,19 @@ class ProspectDeclineReasonFilter(admin.SimpleListFilter):
else:
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):
title = 'Contact again' # or whatever you want
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'
NOT_CONCERNED = 'NOT_CONCERNED', 'Not concerned'
SHOULD_BUY = 'SHOULD_BUY', 'Should buy'
HAVE_CREATED_ACCOUNT = 'HAVE_CREATED_ACCOUNT', 'Have created account'
class DeclinationReason(models.TextChoices):
TOO_EXPENSIVE = 'TOO_EXPENSIVE', 'Too expensive'
@ -170,6 +171,13 @@ class EmailTemplate(BaseModel):
def delete_dependencies(self):
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):
# event = models.OneToOneField(Event, on_delete=models.CASCADE)
# subject = models.CharField(max_length=200)

@ -9,7 +9,7 @@ from django.shortcuts import render
from django.db.models import Avg
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 .forms import CustomUserCreationForm, CustomUserChangeForm
@ -27,7 +27,7 @@ class CustomUserAdmin(UserAdmin):
model = CustomUser
search_fields = ['username', 'email', 'phone', 'first_name', 'last_name', 'licence_id']
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_filter = ['is_active', 'origin', UserWithEventsFilter, UserWithPurchasesFilter, UserWithProspectFilter]
@ -59,10 +59,35 @@ class CustomUserAdmin(UserAdmin):
obj.last_update = timezone.now()
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):
created_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:
if user.email and Prospect.objects.filter(email=user.email).exists():

Loading…
Cancel
Save