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:
@@ -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",
|
||||||
|
|||||||
55
accounts/migrations/0012_user_eventify_id.py
Normal file
55
accounts/migrations/0012_user_eventify_id.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class LoginView(View):
|
|||||||
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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user