267 lines
10 KiB
Python
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
|