feat(accounts): home district with 6-month cooldown

- accounts/models.py: add district_changed_at DateTimeField + VALID_DISTRICTS constant (14 Kerala districts)
- migration 0013_user_district_changed_at: nullable DateTimeField, no backfill
- WebRegisterForm: accept optional district during signup, stamp district_changed_at
- UpdateProfileView: enforce 183-day cooldown with human-readable error
- LoginView/WebRegisterView/StatusView: include district_changed_at in responses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 10:42:44 +05:30
parent ac2b2ba242
commit c9afbcf3cc
5 changed files with 78 additions and 9 deletions

View File

@@ -45,7 +45,7 @@ class WebRegisterForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email', 'phone_number', 'password', 'confirm_password']
fields = ['first_name', 'last_name', 'email', 'phone_number', 'password', 'confirm_password', 'district']
def clean_email(self):
email = self.cleaned_data.get('email')
@@ -76,6 +76,12 @@ class WebRegisterForm(forms.ModelForm):
# Mark as a customer / end-user
user.is_customer = True
user.role = 'customer'
from django.utils import timezone
from accounts.models import VALID_DISTRICTS
if user.district and user.district in VALID_DISTRICTS:
user.district_changed_at = timezone.now()
elif user.district:
user.district = None # reject invalid district silently
if commit:
user.save()
return user

View File

@@ -58,6 +58,11 @@ class WebRegisterView(View):
'username': user.username,
'email': user.email,
'phone_number': user.phone_number,
'district': user.district or '',
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
'first_name': user.first_name,
'last_name': user.last_name,
'eventify_id': user.eventify_id or '',
}
return JsonResponse(response, status=201)
log("warning", "Web registration failed", request=request, logger_data=dict(errors=form.errors))
@@ -93,6 +98,7 @@ class LoginView(View):
'role': user.role,
'pincode': user.pincode,
'district': user.district,
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
'state': user.state,
'country': user.country,
'place': user.place,
@@ -124,6 +130,8 @@ class StatusView(View):
"username": user.username,
"email": user.email,
"eventify_id": user.eventify_id or '',
"district": user.district or '',
"district_changed_at": user.district_changed_at.isoformat() if user.district_changed_at else None,
})
except Exception as e:
@@ -245,15 +253,33 @@ class UpdateProfileView(View):
user.pincode = None
updated_fields.append('pincode')
# Update district
# Update district (with 6-month cooldown)
if 'district' in json_data:
district = json_data.get('district', '').strip()
if district:
user.district = district
updated_fields.append('district')
elif district == '':
user.district = None
updated_fields.append('district')
from django.utils import timezone
from datetime import timedelta
from accounts.models import VALID_DISTRICTS
COOLDOWN = timedelta(days=183) # ~6 months
new_district = json_data.get('district', '').strip()
if new_district and new_district not in VALID_DISTRICTS:
errors['district'] = 'Invalid district.'
elif new_district and new_district != (user.district or ''):
if user.district_changed_at and timezone.now() < user.district_changed_at + COOLDOWN:
next_date = (user.district_changed_at + COOLDOWN).strftime('%d %b %Y')
errors['district'] = f'District can only be changed once every 6 months. Next change: {next_date}.'
else:
user.district = new_district
user.district_changed_at = timezone.now()
updated_fields.append('district')
elif new_district == '' and user.district:
if user.district_changed_at and timezone.now() < user.district_changed_at + COOLDOWN:
next_date = (user.district_changed_at + COOLDOWN).strftime('%d %b %Y')
errors['district'] = f'District can only be changed once every 6 months. Next change: {next_date}.'
else:
user.district = None
user.district_changed_at = timezone.now()
updated_fields.append('district')
# Update state
if 'state' in json_data:
@@ -318,6 +344,7 @@ class UpdateProfileView(View):
'phone_number': user.phone_number,
'pincode': user.pincode,
'district': user.district,
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
'state': user.state,
'country': user.country,
'place': user.place,