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)
This commit is contained in:
2026-04-02 10:25:25 +05:30
parent 255519473b
commit 384797551f
5 changed files with 88 additions and 2 deletions

View File

@@ -17,6 +17,7 @@ def _partner_user_to_dict(user, request=None):
user, user,
fields=[ fields=[
"id", "id",
"eventify_id",
"username", "username",
"email", "email",
"phone_number", "phone_number",

View File

@@ -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),
),
]

View File

@@ -1,8 +1,18 @@
import secrets
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from accounts.manager import UserManager from accounts.manager import UserManager
from partner.models import Partner 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 = ( ROLE_CHOICES = (
('admin', 'Admin'), ('admin', 'Admin'),
('manager', 'Manager'), ('manager', 'Manager'),
@@ -15,6 +25,15 @@ ROLE_CHOICES = (
) )
class User(AbstractUser): 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) phone_number = models.CharField(max_length=15, blank=True, null=True)
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='Staff') role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='Staff')
@@ -39,7 +58,7 @@ class User(AbstractUser):
allowed_modules = models.TextField( allowed_modules = models.TextField(
blank=True, null=True, 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"] ALL_MODULES = ["dashboard", "partners", "events", "ad-control", "users", "reviews", "contributions", "financials", "settings"]
@@ -56,5 +75,14 @@ class User(AbstractUser):
objects = UserManager() 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): def __str__(self):
return self.username return self.username

View File

@@ -82,8 +82,9 @@ class LoginView(View):
print('3') print('3')
log("info", "API login", request=request, user=user) log("info", "API login", request=request, user=user)
response = { response = {
'message': 'Login successful', 'message': 'Login successful',
'token': token.key, 'token': token.key,
'eventify_id': user.eventify_id,
'username': user.username, 'username': user.username,
'email': user.email, 'email': user.email,
'phone_number': user.phone_number, 'phone_number': user.phone_number,

View File

@@ -835,6 +835,7 @@ def _user_to_dict(user, request=None):
user, user,
fields=[ fields=[
"id", "id",
"eventify_id",
"username", "username",
"email", "email",
"phone_number", "phone_number",