2026-05-20 10:50:30 +00:00

267 lines
10 KiB
Python

from django import forms
from django.contrib.auth.models import User
from .delivery import apply_profile_delivery_fields, derive_location_label, phone_has_valid_digits
from .models import Profile
class DeliveryLocationValidationMixin:
def clean_phone(self):
phone = (self.cleaned_data.get('phone') or '').strip()
if not phone_has_valid_digits(phone):
raise forms.ValidationError('Please enter a valid phone number.')
return phone
def clean_default_address(self):
address = (self.cleaned_data.get('default_address') or '').strip()
if not address:
raise forms.ValidationError('Please enter your delivery address.')
return address
def clean(self):
cleaned_data = super().clean()
latitude = cleaned_data.get('latitude')
longitude = cleaned_data.get('longitude')
if (latitude is None) != (longitude is None):
raise forms.ValidationError('GPS location is incomplete. Please retry the location button or save without GPS.')
if latitude is not None and not (-90 <= latitude <= 90):
raise forms.ValidationError('Latitude is outside the valid range.')
if longitude is not None and not (-180 <= longitude <= 180):
raise forms.ValidationError('Longitude is outside the valid range.')
location_accuracy_m = cleaned_data.get('location_accuracy_m')
if location_accuracy_m is not None and location_accuracy_m < 0:
raise forms.ValidationError('Location accuracy cannot be negative.')
if cleaned_data.get('default_address') and not cleaned_data.get('location_label'):
cleaned_data['location_label'] = derive_location_label('', cleaned_data['default_address'])
return cleaned_data
def apply_location_to_profile(self, profile):
return apply_profile_delivery_fields(
profile,
phone=self.cleaned_data.get('phone', ''),
location_label=self.cleaned_data.get('location_label', ''),
address=self.cleaned_data.get('default_address', ''),
latitude=self.cleaned_data.get('latitude'),
longitude=self.cleaned_data.get('longitude'),
location_accuracy_m=self.cleaned_data.get('location_accuracy_m'),
)
class RegistrationForm(DeliveryLocationValidationMixin, forms.Form):
username = forms.CharField(
min_length=3,
max_length=150,
widget=forms.TextInput(
attrs={
'placeholder': 'Choose a username',
'autocomplete': 'username',
}
),
)
first_name = forms.CharField(
required=False,
max_length=30,
widget=forms.TextInput(attrs={'placeholder': 'First name', 'autocomplete': 'given-name'}),
)
last_name = forms.CharField(
required=False,
max_length=150,
widget=forms.TextInput(attrs={'placeholder': 'Last name', 'autocomplete': 'family-name'}),
)
email = forms.EmailField(
required=False,
widget=forms.EmailInput(attrs={'placeholder': 'your@email.com', 'autocomplete': 'email'}),
)
password = forms.CharField(
min_length=6,
widget=forms.PasswordInput(attrs={'placeholder': 'At least 6 characters', 'autocomplete': 'new-password'}),
)
confirm_password = forms.CharField(
min_length=6,
widget=forms.PasswordInput(attrs={'placeholder': 'Confirm your password', 'autocomplete': 'new-password'}),
)
phone = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={'placeholder': 'Phone number', 'autocomplete': 'tel'}),
)
location_label = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(
attrs={
'placeholder': 'Area / city / delivery label',
'autocomplete': 'address-level2',
}
),
)
default_address = forms.CharField(
required=True,
widget=forms.Textarea(
attrs={
'rows': 4,
'placeholder': 'Street, city, area, landmark',
'autocomplete': 'street-address',
}
),
)
latitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
longitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
location_accuracy_m = forms.DecimalField(required=False, max_digits=8, decimal_places=2, widget=forms.HiddenInput())
register_as_seller = forms.BooleanField(required=False)
def clean_username(self):
username = (self.cleaned_data.get('username') or '').strip()
if User.objects.filter(username__iexact=username).exists():
raise forms.ValidationError('Username already exists.')
return username
def clean_email(self):
email = (self.cleaned_data.get('email') or '').strip()
if email and User.objects.filter(email__iexact=email).exists():
raise forms.ValidationError('Email already registered.')
return email
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if password and confirm_password and password != confirm_password:
self.add_error('confirm_password', 'Passwords do not match.')
return cleaned_data
def save(self):
user = User.objects.create_user(
username=self.cleaned_data['username'],
password=self.cleaned_data['password'],
email=self.cleaned_data.get('email', ''),
)
user.first_name = self.cleaned_data.get('first_name', '').strip()
user.last_name = self.cleaned_data.get('last_name', '').strip()
user.save(update_fields=['first_name', 'last_name', 'email'])
profile = user.profile
self.apply_location_to_profile(profile)
profile.is_seller = self.cleaned_data.get('register_as_seller', False)
profile.save()
return user
class ProfileForm(DeliveryLocationValidationMixin, forms.ModelForm):
first_name = forms.CharField(required=False, max_length=30)
last_name = forms.CharField(required=False, max_length=150)
email = forms.EmailField(required=False)
phone = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={'placeholder': 'Phone number', 'autocomplete': 'tel'}),
)
location_label = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(
attrs={
'placeholder': 'Area / city / delivery label',
'autocomplete': 'address-level2',
}
),
)
default_address = forms.CharField(
required=True,
widget=forms.Textarea(
attrs={
'rows': 4,
'placeholder': 'Street, city, area, landmark',
'autocomplete': 'street-address',
}
),
)
latitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
longitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
location_accuracy_m = forms.DecimalField(required=False, max_digits=8, decimal_places=2, widget=forms.HiddenInput())
class Meta:
model = Profile
fields = ['image', 'bio', 'phone', 'location_label', 'default_address', 'latitude', 'longitude', 'location_accuracy_m']
widgets = {
'bio': forms.Textarea(attrs={'rows': 5, 'placeholder': 'Tell shoppers something about yourself'}),
}
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
self.user = user or getattr(self.instance, 'user', None)
if self.user:
self.fields['first_name'].initial = self.user.first_name
self.fields['last_name'].initial = self.user.last_name
self.fields['email'].initial = self.user.email
def clean_email(self):
email = (self.cleaned_data.get('email') or '').strip()
if email and User.objects.exclude(pk=getattr(self.user, 'pk', None)).filter(email__iexact=email).exists():
raise forms.ValidationError('That email is already being used by another account.')
return email
def save(self, commit=True):
profile = super().save(commit=False)
self.apply_location_to_profile(profile)
user = self.user or profile.user
user.first_name = self.cleaned_data.get('first_name', '').strip()
user.last_name = self.cleaned_data.get('last_name', '').strip()
user.email = self.cleaned_data.get('email', '').strip()
if commit:
user.save()
profile.save()
return profile
class DeliveryPreferencesForm(DeliveryLocationValidationMixin, forms.ModelForm):
phone = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={'placeholder': 'Phone number', 'autocomplete': 'tel'}),
)
location_label = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(
attrs={
'placeholder': 'Area / city / delivery label',
'autocomplete': 'address-level2',
}
),
)
default_address = forms.CharField(
required=True,
widget=forms.Textarea(
attrs={
'rows': 4,
'placeholder': 'Street, city, area, landmark',
'autocomplete': 'street-address',
}
),
)
latitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
longitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
location_accuracy_m = forms.DecimalField(required=False, max_digits=8, decimal_places=2, widget=forms.HiddenInput())
class Meta:
model = Profile
fields = ['phone', 'location_label', 'default_address', 'latitude', 'longitude', 'location_accuracy_m']
def save(self, commit=True):
profile = super().save(commit=False)
self.apply_location_to_profile(profile)
if commit:
profile.save()
return profile