From 384797551f0a7bf6290901b857747c7894f89f31 Mon Sep 17 00:00:00 2001 From: Sicherhaven Date: Thu, 2 Apr 2026 10:25:25 +0530 Subject: [PATCH] feat: add Eventify ID (EVT-XXXXXXXX) to User model and all APIs - Add eventify_id CharField (unique, indexed, editable=False) to User - Auto-generate on save() with charset excluding I/O/0/1 for clarity - Migration 0012: add field nullable, backfill all existing users, make non-null - Sync migration 0011 (allowed_modules) pulled from server - Expose eventify_id in accounts/api.py, partner/api.py serializers - Expose eventify_id in mobile_api login response (populates localStorage) --- accounts/api.py | 1 + accounts/migrations/0012_user_eventify_id.py | 55 ++++++++++++++++++++ accounts/models.py | 30 ++++++++++- mobile_api/views/user.py | 3 +- partner/api.py | 1 + 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 accounts/migrations/0012_user_eventify_id.py diff --git a/accounts/api.py b/accounts/api.py index 2e91ce0..bf42992 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -17,6 +17,7 @@ def _partner_user_to_dict(user, request=None): user, fields=[ "id", + "eventify_id", "username", "email", "phone_number", diff --git a/accounts/migrations/0012_user_eventify_id.py b/accounts/migrations/0012_user_eventify_id.py new file mode 100644 index 0000000..92c74f7 --- /dev/null +++ b/accounts/migrations/0012_user_eventify_id.py @@ -0,0 +1,55 @@ +# Generated migration for eventify_id field + +import secrets + +from django.db import migrations, models + +EVENTIFY_ID_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' + + +def generate_unique_eventify_id(existing_ids): + for _ in range(100): + candidate = 'EVT-' + ''.join(secrets.choice(EVENTIFY_ID_CHARS) for _ in range(8)) + if candidate not in existing_ids: + return candidate + raise RuntimeError("Could not generate a unique Eventify ID after 100 attempts") + + +def backfill_eventify_ids(apps, schema_editor): + User = apps.get_model('accounts', 'User') + existing_ids = set(User.objects.exclude(eventify_id__isnull=True).values_list('eventify_id', flat=True)) + users_to_update = [] + for user in User.objects.filter(eventify_id__isnull=True): + new_id = generate_unique_eventify_id(existing_ids) + existing_ids.add(new_id) + user.eventify_id = new_id + users_to_update.append(user) + User.objects.bulk_update(users_to_update, ['eventify_id']) + + +def reverse_backfill(apps, schema_editor): + pass # No-op: reversing just leaves IDs set, which is fine + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0011_user_allowed_modules_alter_user_id'), + ] + + operations = [ + # Step 1: Add the column as nullable + migrations.AddField( + model_name='user', + name='eventify_id', + field=models.CharField(blank=True, db_index=True, editable=False, max_length=12, null=True, unique=True), + ), + # Step 2: Backfill all existing users + migrations.RunPython(backfill_eventify_ids, reverse_backfill), + # Step 3: Make the field non-nullable + migrations.AlterField( + model_name='user', + name='eventify_id', + field=models.CharField(blank=False, db_index=True, editable=False, max_length=12, null=False, unique=True), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index b8e3845..fe9ffc3 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,8 +1,18 @@ +import secrets + from django.contrib.auth.models import AbstractUser from django.db import models from accounts.manager import UserManager from partner.models import Partner + +EVENTIFY_ID_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' # no I, O, 0, 1 + + +def generate_eventify_id(): + return 'EVT-' + ''.join(secrets.choice(EVENTIFY_ID_CHARS) for _ in range(8)) + + ROLE_CHOICES = ( ('admin', 'Admin'), ('manager', 'Manager'), @@ -15,6 +25,15 @@ ROLE_CHOICES = ( ) class User(AbstractUser): + eventify_id = models.CharField( + max_length=12, + unique=True, + editable=False, + db_index=True, + null=True, + blank=True, + ) + phone_number = models.CharField(max_length=15, blank=True, null=True) role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='Staff') @@ -39,7 +58,7 @@ class User(AbstractUser): allowed_modules = models.TextField( blank=True, null=True, - help_text="Comma-separated module slugs this user can access" + help_text='Comma-separated module slugs this user can access', ) ALL_MODULES = ["dashboard", "partners", "events", "ad-control", "users", "reviews", "contributions", "financials", "settings"] @@ -56,5 +75,14 @@ class User(AbstractUser): objects = UserManager() + def save(self, *args, **kwargs): + if not self.eventify_id: + for _ in range(10): + candidate = generate_eventify_id() + if not User.objects.filter(eventify_id=candidate).exists(): + self.eventify_id = candidate + break + super().save(*args, **kwargs) + def __str__(self): return self.username diff --git a/mobile_api/views/user.py b/mobile_api/views/user.py index 59a0980..4d5c7b0 100644 --- a/mobile_api/views/user.py +++ b/mobile_api/views/user.py @@ -82,8 +82,9 @@ class LoginView(View): print('3') log("info", "API login", request=request, user=user) response = { - 'message': 'Login successful', + 'message': 'Login successful', 'token': token.key, + 'eventify_id': user.eventify_id, 'username': user.username, 'email': user.email, 'phone_number': user.phone_number, diff --git a/partner/api.py b/partner/api.py index 71ee149..67bbd79 100644 --- a/partner/api.py +++ b/partner/api.py @@ -835,6 +835,7 @@ def _user_to_dict(user, request=None): user, fields=[ "id", + "eventify_id", "username", "email", "phone_number",