373 lines
14 KiB
Python
373 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from django import forms
|
|
from django.contrib.auth import authenticate, get_user_model
|
|
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, UserCreationForm
|
|
from django.utils import timezone
|
|
|
|
from .models import Business, BusinessMembership, Feedback, ProofCard, ReviewRequest
|
|
|
|
logger = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
|
|
class TrustForgeAuthenticationForm(AuthenticationForm):
|
|
username = forms.CharField(
|
|
label='Work email',
|
|
widget=forms.EmailInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'you@company.com',
|
|
'autocomplete': 'email',
|
|
}
|
|
),
|
|
)
|
|
password = forms.CharField(
|
|
label='Password',
|
|
strip=False,
|
|
widget=forms.PasswordInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'Enter your password',
|
|
'autocomplete': 'current-password',
|
|
}
|
|
),
|
|
)
|
|
|
|
def clean(self):
|
|
email = (self.cleaned_data.get('username') or '').strip().lower()
|
|
password = self.cleaned_data.get('password')
|
|
|
|
if email and password:
|
|
matched_user = User._default_manager.filter(email__iexact=email).first()
|
|
auth_username = matched_user.get_username() if matched_user else email
|
|
self.user_cache = authenticate(self.request, username=auth_username, password=password)
|
|
if self.user_cache is None:
|
|
raise self.get_invalid_login_error()
|
|
self.confirm_login_allowed(self.user_cache)
|
|
|
|
return self.cleaned_data
|
|
|
|
|
|
class SignUpForm(UserCreationForm):
|
|
first_name = forms.CharField(
|
|
required=False,
|
|
max_length=150,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Avery'}),
|
|
)
|
|
last_name = forms.CharField(
|
|
required=False,
|
|
max_length=150,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Stone'}),
|
|
)
|
|
email = forms.EmailField(
|
|
widget=forms.EmailInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'owner@servicebrand.com',
|
|
'autocomplete': 'email',
|
|
}
|
|
)
|
|
)
|
|
password1 = forms.CharField(
|
|
label='Password',
|
|
strip=False,
|
|
widget=forms.PasswordInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'Create a password',
|
|
'autocomplete': 'new-password',
|
|
}
|
|
),
|
|
)
|
|
password2 = forms.CharField(
|
|
label='Confirm password',
|
|
strip=False,
|
|
widget=forms.PasswordInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'Repeat your password',
|
|
'autocomplete': 'new-password',
|
|
}
|
|
),
|
|
)
|
|
|
|
class Meta(UserCreationForm.Meta):
|
|
model = User
|
|
fields = ('first_name', 'last_name', 'email', 'password1', 'password2')
|
|
|
|
def clean_email(self):
|
|
email = (self.cleaned_data.get('email') or '').strip().lower()
|
|
if User._default_manager.filter(email__iexact=email).exists() or User._default_manager.filter(username__iexact=email).exists():
|
|
raise forms.ValidationError('An account with this email already exists.')
|
|
return email
|
|
|
|
def save(self, commit=True):
|
|
user = super().save(commit=False)
|
|
email = self.cleaned_data['email']
|
|
user.username = email
|
|
user.email = email
|
|
user.first_name = self.cleaned_data.get('first_name', '').strip()
|
|
user.last_name = self.cleaned_data.get('last_name', '').strip()
|
|
if commit:
|
|
user.save()
|
|
return user
|
|
|
|
|
|
class ProfileSettingsForm(forms.ModelForm):
|
|
first_name = forms.CharField(
|
|
required=False,
|
|
max_length=150,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Avery'}),
|
|
)
|
|
last_name = forms.CharField(
|
|
required=False,
|
|
max_length=150,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Stone'}),
|
|
)
|
|
email = forms.EmailField(
|
|
widget=forms.EmailInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'you@company.com',
|
|
'autocomplete': 'email',
|
|
}
|
|
)
|
|
)
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = ('first_name', 'last_name', 'email')
|
|
|
|
def clean_email(self):
|
|
email = (self.cleaned_data.get('email') or '').strip().lower()
|
|
email_exists = User._default_manager.filter(email__iexact=email).exclude(pk=self.instance.pk).exists()
|
|
username_exists = User._default_manager.filter(username__iexact=email).exclude(pk=self.instance.pk).exists()
|
|
if email_exists or username_exists:
|
|
raise forms.ValidationError('Another account already uses this email.')
|
|
return email
|
|
|
|
def save(self, commit=True):
|
|
user = super().save(commit=False)
|
|
email = self.cleaned_data['email']
|
|
user.email = email
|
|
user.username = email
|
|
user.first_name = self.cleaned_data.get('first_name', '').strip()
|
|
user.last_name = self.cleaned_data.get('last_name', '').strip()
|
|
if commit:
|
|
user.save()
|
|
return user
|
|
|
|
|
|
class TrustForgePasswordResetForm(PasswordResetForm):
|
|
email = forms.EmailField(
|
|
widget=forms.EmailInput(
|
|
attrs={
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'you@company.com',
|
|
'autocomplete': 'email',
|
|
}
|
|
)
|
|
)
|
|
|
|
def send_mail(self, *args, **kwargs):
|
|
try:
|
|
return super().send_mail(*args, **kwargs)
|
|
except Exception:
|
|
logger.exception('Password reset email failed to send.')
|
|
|
|
|
|
class TrustForgeSetPasswordForm(SetPasswordForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['new_password1'].widget.attrs.update(
|
|
{
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'Create a new password',
|
|
'autocomplete': 'new-password',
|
|
}
|
|
)
|
|
self.fields['new_password2'].widget.attrs.update(
|
|
{
|
|
'class': 'form-control form-control-lg',
|
|
'placeholder': 'Confirm the new password',
|
|
'autocomplete': 'new-password',
|
|
}
|
|
)
|
|
|
|
|
|
class BusinessOnboardingForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Business
|
|
fields = ('name', 'industry', 'primary_city', 'primary_state', 'google_review_url')
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control form-control-lg', 'placeholder': 'Summit Home Services'}),
|
|
'industry': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'HVAC, Roofing, Plumbing, Junk Removal…'}),
|
|
'primary_city': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Austin'}),
|
|
'primary_state': forms.TextInput(attrs={'class': 'form-control text-uppercase', 'placeholder': 'TX'}),
|
|
'google_review_url': forms.URLInput(attrs={'class': 'form-control', 'placeholder': 'https://g.page/r/.../review'}),
|
|
}
|
|
|
|
def clean_primary_state(self):
|
|
return (self.cleaned_data.get('primary_state') or '').upper()
|
|
|
|
|
|
class BusinessSettingsForm(BusinessOnboardingForm):
|
|
pass
|
|
|
|
|
|
class TeamMemberInviteForm(forms.Form):
|
|
first_name = forms.CharField(
|
|
required=False,
|
|
max_length=150,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Jamie'}),
|
|
)
|
|
last_name = forms.CharField(
|
|
required=False,
|
|
max_length=150,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Rivera'}),
|
|
)
|
|
email = forms.EmailField(
|
|
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'tech@servicebrand.com'})
|
|
)
|
|
role = forms.ChoiceField(
|
|
choices=BusinessMembership.Role.choices,
|
|
initial=BusinessMembership.Role.TECHNICIAN,
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
)
|
|
|
|
def clean_email(self):
|
|
return (self.cleaned_data.get('email') or '').strip().lower()
|
|
|
|
|
|
class JobIntakeForm(forms.Form):
|
|
business = forms.ModelChoiceField(
|
|
queryset=Business.objects.filter(is_active=True),
|
|
empty_label=None,
|
|
widget=forms.Select(attrs={'class': 'form-select form-control-lg'}),
|
|
)
|
|
customer_name = forms.CharField(
|
|
max_length=160,
|
|
widget=forms.TextInput(attrs={'class': 'form-control form-control-lg', 'placeholder': 'Homeowner or business contact'}),
|
|
)
|
|
customer_email = forms.EmailField(
|
|
required=False,
|
|
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'customer@example.com'}),
|
|
)
|
|
customer_phone = forms.CharField(
|
|
required=False,
|
|
max_length=40,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '(555) 555-0123'}),
|
|
)
|
|
service_type = forms.CharField(
|
|
max_length=120,
|
|
widget=forms.TextInput(attrs={'class': 'form-control form-control-lg', 'placeholder': 'Roof replacement, HVAC tune-up, junk removal…'}),
|
|
)
|
|
description = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'What did the crew complete on-site?'}),
|
|
)
|
|
customer_city = forms.CharField(
|
|
max_length=120,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Austin'}),
|
|
)
|
|
customer_state = forms.CharField(
|
|
max_length=2,
|
|
widget=forms.TextInput(attrs={'class': 'form-control text-uppercase', 'placeholder': 'TX'}),
|
|
)
|
|
technician_name = forms.CharField(
|
|
required=False,
|
|
max_length=120,
|
|
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Luis R.'}),
|
|
)
|
|
completion_date = forms.DateField(
|
|
initial=timezone.localdate,
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
)
|
|
project_value = forms.DecimalField(
|
|
required=False,
|
|
min_value=0,
|
|
max_digits=10,
|
|
decimal_places=2,
|
|
widget=forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '4500'}),
|
|
)
|
|
before_photo = forms.FileField(
|
|
required=False,
|
|
widget=forms.ClearableFileInput(attrs={'class': 'form-control', 'accept': 'image/*'}),
|
|
)
|
|
after_photo = forms.FileField(
|
|
required=False,
|
|
widget=forms.ClearableFileInput(attrs={'class': 'form-control', 'accept': 'image/*'}),
|
|
)
|
|
anonymize_customer = forms.BooleanField(
|
|
required=False,
|
|
initial=True,
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
)
|
|
send_review_request = forms.BooleanField(
|
|
required=False,
|
|
initial=True,
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
)
|
|
review_channel = forms.ChoiceField(
|
|
choices=ReviewRequest.Channel.choices,
|
|
initial=ReviewRequest.Channel.EMAIL,
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
)
|
|
|
|
def __init__(self, *args, business: Business | None = None, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if business is not None:
|
|
self.fields['business'].queryset = Business.objects.filter(pk=business.pk)
|
|
self.fields['business'].initial = business
|
|
self.fields['business'].widget = forms.HiddenInput()
|
|
|
|
def clean_customer_state(self):
|
|
return self.cleaned_data['customer_state'].upper()
|
|
|
|
|
|
class PublicFeedbackForm(forms.Form):
|
|
experience = forms.ChoiceField(
|
|
choices=Feedback.Experience.choices,
|
|
widget=forms.RadioSelect,
|
|
)
|
|
testimonial = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4, 'placeholder': 'Tell us what stood out about the service…'}),
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
experience = cleaned_data.get('experience')
|
|
testimonial = (cleaned_data.get('testimonial') or '').strip()
|
|
if experience in {Feedback.Experience.GREAT, Feedback.Experience.GOOD} and len(testimonial) < 12:
|
|
self.add_error('testimonial', 'For positive feedback, add a short testimonial so it can power the proof card.')
|
|
return cleaned_data
|
|
|
|
|
|
class ProofCardForm(forms.ModelForm):
|
|
class Meta:
|
|
model = ProofCard
|
|
fields = [
|
|
'customer_display_name',
|
|
'is_anonymized',
|
|
'testimonial_quote',
|
|
'rating',
|
|
'status',
|
|
'is_featured',
|
|
'attached_widget_label',
|
|
'attached_pages',
|
|
]
|
|
widgets = {
|
|
'customer_display_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'is_anonymized': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'testimonial_quote': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
'rating': forms.NumberInput(attrs={'class': 'form-control', 'min': 1, 'max': 5}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'is_featured': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'attached_widget_label': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'attached_pages': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|