Skip to content

Instantly share code, notes, and snippets.

@neilbradley
Created October 11, 2017 14:28
Show Gist options
  • Save neilbradley/e9e7c61fb39f6b8d55b2bc17822f3935 to your computer and use it in GitHub Desktop.
Save neilbradley/e9e7c61fb39f6b8d55b2bc17822f3935 to your computer and use it in GitHub Desktop.
from django import forms
from django.contrib.auth.models import User
from django.forms.util import ErrorList
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.contrib.auth import authenticate, login
from django.utils.safestring import mark_safe
from django.forms.models import modelformset_factory
from captcha.fields import CaptchaField
from django.forms.extras.widgets import SelectDateWidget
from django.utils.translation import ugettext_lazy as _
import datetime
from countrysites.settings import settings as countrysettings
from models import *
from basket import Basket
import re
import widgets
default_choice = [('', '----------')]
SubCategoryFormSet = modelformset_factory(SubCategory)
ProductCategoryFormSet = modelformset_factory(ProductCategory)
class SAIDForm(forms.Form):
id_number = forms.RegexField(regex='(((\d{2}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229))(( |-)(\d{4})( |-)(\d{3})|(\d{7}))')
class DiscountForm(forms.Form):
discount = forms.CharField(max_length=30, required=False, label="Promotional Code")
def clean_discount(self):
discount = self.cleaned_data['discount'].strip()
return discount
class SalesReport(forms.Form):
# payment statuses - this should probably be migrated to the db
STATUS_CHOICES = (
(-1, 'all'),
(0, 'Refunded'),
(1, 'Voided'),
(2, 'Pending'),
(3, 'Failed'),
(4, 'Authorised'),
(5, 'Packing'),
(6, 'Dispatched'),
(7, 'Complete'),
)
QUERY_TYPE = (
('yearly', 'Yearly'),
('monthly', 'Monthly'),
('weekly', 'Weekly'),
('daily', 'Daily'),
)
RESULT_TYPE = (
('onscreen', 'On Screen'),
('csv', 'CSV (for excell)'),
)
# , input_formats='%d/%m/%y'
start_date = forms.DateField(label="start date", input_formats=('%d/%m/%y',),initial=datetime.date.today().replace(day=1).strftime('%d/%m/%y'),required=True)
end_date = forms.DateField(label="end date", input_formats=('%d/%m/%y',), initial=datetime.date.today().strftime('%d/%m/%y'),required=True)
status = forms.ChoiceField(choices=STATUS_CHOICES, label="Select orders by status", initial=7)
#query_type = forms.ChoiceField(choices=QUERY_TYPE, label="Results by", initial='daily')
result_type = forms.ChoiceField(choices=RESULT_TYPE, label="Result type", initial='onscreen')
class ProductCategoryForm(forms.ModelForm):
"""
A form for the relationship between products and their parent categories
"""
class Meta:
model = ProductCategory
class ProductForm(forms.ModelForm):
"""
A form for the editing of a shop product
"""
class Meta:
model = Product
class CategoryForm(forms.ModelForm):
"""
A form for the editing of a shop category
"""
class Meta:
model = Category
class RegisterAccountForm(forms.ModelForm):
"""
A form with most of the fields needed for registering an account.
Note that this form is different to the UpdateAccountForm form because
it doesn't allow the ability to change addresses (purely because the user
won't yet have any at registration point). Also assumes that the customer
must agree to some terms and conditions at registration.
"""
# tsandcs = forms.BooleanField(help_text="I agree to the Terms and Conditions below",error_messages={'required': 'You must agree to the Terms and Conditions'})
#captcha = CaptchaField(label="Verification Code")
class Meta:
model = Account
exclude = ('user','default_address','temporary','dob','gender','malinglist','informed','security_question')
''' class QuickPayAccountForm(forms.ModelForm):
""" """
A form with most of the fields needed for registering an account ergo just
for the sake of ergo.
Note that this form is different to the UpdateAccountForm form because
it doesn't allow the ability to change addresses (purely because the user
won't yet have any at registration point). Also assumes that the customer
must agree to some terms and conditions at registration.
""" """
# tsandcs = forms.BooleanField(help_text="I agree to the Terms and Conditions below",error_messages={'required': 'You must agree to the Terms and Conditions'})
#captcha = CaptchaField(label="Verification Code")
class Meta:
model = Account
exclude = ('user','default_address','temporary','security_question','security_answer','feedback')
class UpdateAccountForm(forms.ModelForm):
""" """
Partnering RegisterAccountForm, this version allows the editing of both
address fields and insists that the 'instance' keyword is supplied to the
constructor, indicating an update. Builds the list of address choices
dynamically, from those assigned to the related account.
""" """
class Meta:
model = Account
exclude = ('user','temporary')
def __init__(self, *args, **kwargs):
""" """
Set the address choices for this account to be those that already exist
against the account
""" """
super(UpdateAccountForm, self).__init__(*args, **kwargs)
if not 'instance' in kwargs:
raise TypeError("UpdateAccountForm requires keyword paramter 'instance'")
address_choices = [(address.id, str(address)) for address in kwargs['instance'].address_set.all()]
self.fields['default_address'].choices = address_choices
class LoginForm(forms.Form):
""" """
A helper form for logging in customers. This form actually performs a log in
when clean is called, assuming the email and password combination is valid.
""" """
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput,min_length=5)
def __init__(self, request, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':',
empty_permitted=False):
""" """
Save a reference to the request object on this instance
""" """
super(LoginForm, self).__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted)
self.request = request
def clean(self):
""" """
Process the log in request. If both email and password data has been
entered, this function first tries to retrieve a matching account
object from the database which is both persistent and non-staff. If
successful, the username is passed through the django authenticate
mechanism to check whether the username and password is correct.
""" """
cleaned_data = self.cleaned_data
email = cleaned_data.get('email')
password = cleaned_data.get('password')
if email and password:
try:
user = Account.objects.get(user__email=email, user__is_staff=False, temporary=False).user
except ObjectDoesNotExist:
raise forms.ValidationError("No customer exists with that email address")
except MultipleObjectsReturned:
raise forms.ValidationError("Unfortunately, there seems to be an issue. We have two accounts with that email address and cannot log you in! "
"This should not happen and is likely a bug with our software. Please get in touch and notify us of this issue.")
user = authenticate(username=user.username, password=password)
if user is not None:
if user.is_active:
login(self.request, user)
else:
# Possibly email admin here to notify of attempted access to locked account
raise forms.ValidationError("Your account has been temporarily locked.")
else:
raise forms.ValidationError("The password for that account is incorrect")
self.user = user
return cleaned_data
'''
class DiscountCodeForm(forms.Form):
"""
A form that allows the customer to enter a discount code on their basket.
The clean method implements the logic that inserts the discount on to the
basket, if the offer code validates
"""
discount_code = forms.CharField(label=_("Discount Code:"), required=False)
''' def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':',
empty_permitted=False):
"""
If the customer has already set a discount code, then populate it's
initial value correctly on the form
"""
super(DiscountCodeForm, self).__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted)
#discount = basket.discount
#if discount:
# self.fields['discount_code'].initial = discount.code
def clean(self):
"""
Validate that the discount code being passed to this form exists in
the database.
"""
cleaned_data = self.cleaned_data
#discount_code = cleaned_data.get('discount_code')
#if discount_code:
# try:
# offer = Offer.objects.get(public=True,code=discount_code)
# except ObjectDoesNotExist, MultipleObjectsReturned:
# self._errors["discount_code"] = ErrorList([u"Discount code not found"])
#return '' #cleaned_data '''
class BasketRowForm(forms.Form):
"""
A form that allows quantities to be updated per row on the basket. This
allows for the change of quantities and the deletion of the row entirely
via the checkbox
"""
operate = forms.BooleanField(required=False)
quantity = forms.IntegerField(required=False,min_value=0)
def __init__(self, basket_item, data=None, files=None, auto_id='id_%s', initial=None,
error_class=ErrorList, label_suffix=':', empty_permitted=False):
"""
Change the initial value of this row's quantity field to be the value
in the basket row.
"""
prefix = basket_item.item.id
super(BasketRowForm, self).__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted)
self.fields['quantity'].initial = basket_item.quantity
'''
class PasswordResetForm(forms.Form):
"""
This form provides validation for the second step of the password reset
process. It validates valid passwords and that the answer is valid for the
given user's question
"""
answer = forms.CharField()
new_password = forms.CharField(widget=forms.PasswordInput,min_length=5)
confirm_password = forms.CharField(widget=forms.PasswordInput,min_length=5)
def __init__(self, password_request, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':',
empty_permitted=False):
"""
Set the label of the answer field to be the question that the customer
selected and store a reference to the password request object in this
instance
"""
super(PasswordResetForm, self).__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted)
self.fields['answer'].label = str(password_request.account.security_question)
self.password_request = password_request
def clean(self):
"""
Ensure that the new passwords being chosen match and that the answer
is correct for this customer's chosen question. Also ensure that the
associated password request is still in-date and valid
"""
cleaned_data = self.cleaned_data
new_password = cleaned_data.get("new_password")
confirm_password = cleaned_data.get("confirm_password")
if new_password != confirm_password:
self._errors["new_password"] = ErrorList([u"Your passwords did not match"])
answer = cleaned_data.get('answer')
if answer:
if str(answer).lower() != str(self.password_request.account.security_answer).lower():
self._errors["answer"] = ErrorList([u"This answer is incorrect"])
if not self.password_request.is_valid():
raise forms.ValidationError("This password reset request is no longer valid.")
return cleaned_data
class ForgottenPasswordForm(forms.Form):
"""
The first step in the forgotten password process. Requests that you enter
your email address and complete a captcha. On success, an email should be
sent out with a link to the second stage of this process
"""
email = forms.EmailField()
#captcha = CaptchaField(label="Verification Code")
def clean(self):
"""
Provide validation that the email being entered exists in the database
for a persistent store account
"""
cleaned_data = self.cleaned_data
email = cleaned_data.get('email')
if email:
try:
user = Account.objects.get(user__email=email, user__is_staff=False, temporary=False)
except ObjectDoesNotExist:
raise forms.ValidationError("No customer exists with that email address")
except MultipleObjectsReturned:
raise forms.ValidationError("Unfortunately, there seems to be an issue. We have two accounts with that email address and cannot retreive a password! "
"This should not happen and is likely a bug with our software. Please get in touch and notify us of this issue.")
return cleaned_data
'''
class AddressForm(forms.ModelForm):
"""
Add new addresses. It is assumed that when adding an address, the customer
will be logged in, so we don't want, or need, them to choose the related
account. This will come from session data.
"""
country = forms.ModelChoiceField(queryset=Country.objects,widget=widgets.SelectCountry)
class Meta:
model = Address
exclude = ('account',)
def __init__(self, *args, **kwargs):
super(AddressForm, self).__init__(*args, **kwargs)
self.fields['country'].initial = 1
if countrysettings.country == 'me':
self.fields['county'].label = 'State'
self.fields['postcode'].required = False
else:
self.fields['postcode'].required = True
class AddressForm_me(forms.ModelForm):
"""
Add new addresses. It is assumed that when adding an address, the customer
will be logged in, so we don't want, or need, them to choose the related
account. This will come from session data.
"""
country = forms.ModelChoiceField(queryset=Country.objects,widget=widgets.SelectCountry)
class Meta:
model = Address_me
exclude = ('account',)
def __init__(self, *args, **kwargs):
super(AddressForm_me, self).__init__(*args, **kwargs)
self.fields['country'].initial = 1
'''
class DeliveryOptions(forms.Form):
"""
Choose your delivery method! Also provides an extra information box if the
customer wishes to provide extra delivery instructions
"""
method = forms.ChoiceField(widget=forms.RadioSelect(),error_messages={'required': 'Please select a shipping option.'})
information = forms.CharField(widget=forms.Textarea(),required=False)
def has_choices(self):
"""
Return True if the number of shipping methods available to the customer
is at least one. False otherwise.
"""
return len(self.fields['method'].choices) > 0
def num_choices(self):
"""
Return the number of shipping methods available in the customers chosen
region
"""
return len(self.fields['method'].choices)
def method_choices(self):
"""
Return the choices available for the shipping method, this will be
returned as a list of 2-item tuples
"""
return self.fields['method'].choices
def __init__(self, account, basket, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':',
empty_permitted=False):
"""
If the customer has already chosen a delivery method then set the method
default to that chosen method, also, pre-populate the information field
if the customer has already specified some delivery instructions
"""
super(DeliveryOptions, self).__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted)
if basket.delivery_address:
bands = basket.delivery_address.country.zone.band_set.all()
self.fields['method'].choices = ((band.postage.id, mark_safe(band.display_line(account))) for band in bands)
if basket.postage:
self.fields['method'].initial = basket.postage.id
if basket.delivery_instructions:
self.fields['information'].initial = basket.delivery_instructions
class AddressSelectForm(forms.ModelForm):
"""
Select the address you want the item shipping to. It is assumed that when
adding an address, the customer will be logged in, so we don't want, or
need, them to choose the related account. This will come from session data.
"""
existing_address = forms.ChoiceField(label="Existing addresses", widget=forms.RadioSelect(), required=False)
class Meta:
model = Address
exclude = ('account',)
def __init__(self, *args, **kwargs):
"""
Store references to the account and basket that should be passed to this
form as keyword parameters (account, basket).
"""
self.account = kwargs.pop('account')
self.basket = kwargs.pop('basket')
super(AddressSelectForm, self).__init__(*args, **kwargs)
value = self.fields.pop('existing_address')
self.fields.insert(0, 'existing_address', value)
self.refresh_address_options()
def refresh_address_options(self):
"""
Generate a new list of valid addresses for this customer.
"""
default_addr = None
if self.basket.delivery_address:
default_addr = self.basket.delivery_address.id
elif self.account.default_address:
default_addr = self.account.default_address.id
addresses = [(addr.id,mark_safe("<br/>".join(addr.line_list()))) for addr in self.account.address_set.all()] + [('','Use Another')]
self.fields['existing_address'].choices = addresses
self.fields['existing_address'].initial = default_addr
def clean(self):
"""
Remove all errors related to the existing_address field. They won't be
valid anyway
"""
cleaned_data = self.cleaned_data
if cleaned_data.get("existing_address"):
for k, v in self._errors.items():
del self._errors[k]
return cleaned_data
class QuickPayUserForm(forms.ModelForm):
"""
A base User form that implements logic for ensuring that the email being
used is not already used by a persistent account. It will however allow a
registration for a duplicate email address if that email is only used by
temporary account holders!
Unlike the account forms, which are separated into two distinct classes,
the UserForm class takes a keyword parameter in it's constructor, which,
if True, tells the class to apply validation as if it were an
update, rather than an insertion. This lets us ignore email collisions in
the user table.
"""
class Meta:
model = User
fields = ('first_name','last_name','email')
def __init__(self, *args, **kwargs):
"""
Make it so that first name, last name and email addresses are required
inputs, even though they're allowed to be blank in the User class
Django provides
"""
super(QuickPayUserForm, self).__init__(*args, **kwargs)
self.update = 'instance' in kwargs
# We really need this data
self.fields['first_name'].required = True
self.fields['last_name'].required = True
self.fields['email'].required = True
def clean(self):
"""
Check that the email address being used is not already taken by a
permanent member of the site
"""
cleaned_data = self.cleaned_data
email = cleaned_data.get('email')
clashes = Account.objects.filter(user__email=email, user__is_staff=False, temporary=False)
if self.update:
clashes = clashes.exclude(user__id=self.instance.id)
if clashes.count():
self._errors["email"] = ErrorList([u"A customer with that email address already exists"])
return cleaned_data
class UserForm(QuickPayUserForm):
"""
A extension of the QuickPayUserForm. This form also deals with passwords
because we're now concerning ourself with persistant users rather than
temporary ones.
"""
password = forms.CharField(widget=forms.PasswordInput,min_length=5)
confirm_password = forms.CharField(widget=forms.PasswordInput,min_length=5)
def __init__(self, *args, **kwargs):
"""
Make it so that the password and confirm_password fields are not
required if we're performing an update rather than an insert on the
User
"""
super(UserForm, self).__init__(*args, **kwargs)
if self.update:
self.fields['password'].required = False
self.fields['confirm_password'].required = False
def clean(self):
"""
Check that the passwords match
"""
super(UserForm, self).clean()
cleaned_data = self.cleaned_data
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
self.set_password = password and confirm_password
if self.set_password and password != confirm_password:
self._errors["password"] = ErrorList([u"Your passwords did not match"])
return cleaned_data
'''
CARD_TYPES = (
(0, 'VISA', 'Visa'),
(1, 'MC', 'Master Card'),
(2, 'DELTA', 'Delta'),
(3, 'SOLO', 'Solo'),
(4, 'MAESTRO', 'Maestro / Switch'),
(5, 'UKE', 'Visa Electron'),
# (6, 'JCB', 'JCB'),
#(7, 'AMEX', 'Amex'),
# (8, 'DC', 'Diners Club'),
)
CARD_TYPES_CHOICES = [(aid, adesc) for aid, vpsid, adesc in CARD_TYPES]
class PaymentForm(forms.Form):
"""
A payment form that requests credit card details off the customer
for forwarding on to payment gateways such as protx
"""
card_holder = forms.CharField(label=_('Name on Card'), max_length=300)
card_number = forms.RegexField(label=_('Card Number'), max_length=26, regex=r'^[0-9\ ]{12,26}$', error_messages={'invalid': _('Please enter a valid card number')})
expiry_month = forms.IntegerField(label='MM', min_value=1, max_value=12, widget=forms.TextInput(attrs={'size': '3', 'maxlength': '2'}))
expiry_year = forms.IntegerField(label='YY', min_value=1, max_value=99, widget=forms.TextInput(attrs={'size': '3', 'maxlength': '2'}))
ccv = forms.CharField(label=_('Security Number'), widget=forms.TextInput(attrs={'size': '5', 'maxlength': '4'}))
card_type = forms.ChoiceField(label=_('Card Type'), widget=forms.Select, choices=CARD_TYPES_CHOICES)
start_month = forms.IntegerField(required=False, label='MM', min_value=1, max_value=12, widget=forms.TextInput(attrs={'size': '3', 'maxlength': '2'}))
start_year = forms.IntegerField(required=False, label='YY', min_value=1, max_value=99, widget=forms.TextInput(attrs={'size': '3', 'maxlength': '2'}))
issue_number = forms.CharField(required=False, label=_('Issue Number'), widget=forms.TextInput(attrs={'size': '3', 'maxlength': '3'}))
def clean(self):
"""
Remove white spaces from the card number
"""
cleaned_data = self.cleaned_data
if 'card_number' in cleaned_data:
cleaned_data['card_number'] = re.sub("\s+", "", cleaned_data['card_number'])
return cleaned_data
class BuyerForm(forms.ModelForm):
"""
A payment form that requests credit card details off the customer
for forwarding on to payment gateways such as protx
"""
class Meta:
model = User
fields = ('first_name','last_name','email')
#exclude = ('is_staff','is_active','last_login','date_joined','username','password','user_permissions','groups','is_superuser')
first_name = forms.CharField(label=_('First name'), max_length=200,required=True)
last_name = forms.CharField(label=_('Surname'), max_length=200,required=True)
#dob = forms.DateField(label='DOB', widget=SelectDateWidget(years=range(2006,1900,-1)))
#gender = forms.CharField(label='Gender', widget=forms.RadioSelect(choices=(('Male','Male'),('Female','Female'))), required=True)
#phone = forms.CharField(label='Phone number', max_length=50,required=True)
email = forms.EmailField(label=_('Email'), max_length=500,required=True)
def clean(self):
cleaned_data = self.cleaned_data
return cleaned_data
class AccountForm(forms.ModelForm):
"""
A payment form that requests credit card details off the customer
for forwarding on to payment gateways such as protx
"""
class Meta:
model = Account
fields = ('dob','gender','telephone')
#exclude = ('is_staff','is_active','last_login','date_joined','username','password','user_permissions','groups','is_superuser')
dob = forms.DateField(label=_('DOB'), widget=SelectDateWidget(years=range(2006,1900,-1)))
gender = forms.CharField(label=_('Gender'), widget=forms.RadioSelect(choices=(('Male',_('Male')),('Female',_('Female')))), required=True)
telephone = forms.CharField(label=_('Phone number'), max_length=50,required=True)
def clean(self):
cleaned_data = self.cleaned_data
return cleaned_data
class TermsForm(forms.Form):
# informed = forms.BooleanField(label='Keeping you informed',required=False)
mailing = forms.BooleanField(label=_('Add to mailing list'),required=False)
terms = forms.BooleanField(label=_('I accept the T&C\'s'), required=True)
def clean(self):
"""
Remove white spaces from the card number
"""
cleaned_data = self.cleaned_data
return cleaned_data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment