"""Attendee Registration Forma """ from django.contrib.sites.models import Site from django.contrib.auth.models import User from django.conf import settings from django.newforms import BaseForm, Form, ValidationError from django.newforms import ChoiceField, DecimalField, CharField, EmailField from django.newforms import ModelMultipleChoiceField from django.newforms import Textarea, TextInput, Select, RadioSelect from django.newforms import HiddenInput, BooleanField, MultipleChoiceField from django.newforms import CheckboxSelectMultiple, IntegerField from django.newforms import form_for_model, form_for_instance from django.newforms.widgets import RadioFieldRenderer from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_unicode, smart_str from django.core.urlresolvers import reverse from django.template import RequestContext, Context, loader from django.core.cache import cache from datetime import datetime from itertools import chain from decimal import Decimal from copy import deepcopy from models import * from fields import * import urllib ## django has two conflicting ValidationError classes!!! from django.newforms import ValidationError PAYPAL_LOGIN = getattr(settings, 'PAYPAL_LOGIN', 'testing') PAYPAL_PARTNER = getattr(settings, 'PAYPAL_PARTNER', 'testing') PAYPAL_URL = getattr(settings, 'PAYPAL_URL', 'testing') ## RED_FLAG: must match data in admin on the number of sessions reg_costs = ( ## EarlyBirdReg Feature is enabled { 'reg': (('400.00', 'Corporate Early-Bird ($400)'), ('220.00', 'Hobbyist Early-Bird ($220)'), ('125.00', 'Student Early-Bird ($125)'), ( '60.00', 'Tutorial Only Pass ($60.00)')), 'tut': ('0.00', '120.00', '200.00', '280.00') }, ## EarlyBirdReg Feature is disabled { 'reg': (('500.00', 'Corporate ($500)'), ('300.00', 'Hobbyist ($300)'), ('180.00', 'Student ($180)'), ( '80.00', 'Tutorial Only Pass ($80.00)')), 'tut': ('0.00', '120.00', '200.00', '280.00') }, ## at door costs (how to integrate?) { 'reg': (('600.00', 'Corporate ($600)'), ('400.00', 'Hobbyist ($400)'), ('250.00', 'Student ($250)'), ('100.00', 'Tutorial Only Pass ($100.00)')), 'tut': ('0.00', '120.00', '200.00', '280.00') }, ) def donation_fields(field, **kwdargs): if field.name.startswith("donation"): return field.formfield(**kwdargs) return None FakePayPalAckForm = form_for_model(PayPalAck, fields=PayPalAck.post_fields) #EditRegForm = form_for_model(Registration, formfield_callback=edit_fields) _DonationForm = form_for_model(Invoice, formfield_callback=donation_fields) class DonationForm(_DonationForm): def __init__(self, user, *args, **kwdargs): self.user = user initial = {'donation_name': unicode(user) } initial.update(kwdargs.get('initial', dict())) kwdargs['initial'] = initial super(DonationForm, self).__init__(*args, **kwdargs) self.fields['donation'].min_value=Decimal("0.00") self.fields['donation'].max_value=Decimal("1000.00") self.fields['donation'].help_text=_(u"(up to 1000, in US dollars)") self.fields['donation_name'].label=_('Donor name') self.fields['donation'].required = False self.fields['donation_name'].required = False def clean_donation(self): if not self.cleaned_data['donation']: return Decimal("0.00") return self.cleaned_data['donation'] def clean_donation_name(self): if (self.cleaned_data['donation_name'] == u'' and self.cleaned_data['donation'] > Decimal("0.00")): raise ValidationError, ( "The name under which the donation is being made must be given.") if not self.cleaned_data['donation']: return u'' return self.cleaned_data['donation_name'] def EditRegForm(reg, *args, **kwdargs): return form_for_instance( reg, fields=Registration.changable)(*args, **kwdargs) class RemovedRegEntryForm(Form): '''We need to keep track of removed entries to keep the data in later reg entries intact, and only loose the specific deleted entry. To do this, we replace the real entry with a hidden entry, keeping the prefix id unique. ''' deleted = BooleanField(widget=HiddenInput, initial=True) def save(self, invoice=None): pass class NewRegEntryForm(Form): sentinal = BooleanField(widget=HiddenInput, initial=True) not_me = BooleanField( label="Check if this registration is for someone else:") name = CharField(max_length="60", help_text=_("(required)"), label=_('Name of attendee'), ) email = EmailField(max_length="60", help_text=_("(required)"), label=_('E-mail address'), ) reg_type = ChoiceField( label=_('Registration type:'), widget=RadioSelect(renderer=RadioFieldInlineRenderer)) sample_badge = HtmlInjectionField() badge_name = CharField( max_length="60", required=False, label="Name for badge:", help_text=_('(if different from the "Name of attendee" field, above)')) badge_text1 = CharField(max_length="60", required=False, label="Badge text, line 1:", help_text=_("(brief information for your badge:")) badge_text2 = CharField(max_length="60", required=False, label="Badge text, line 2:", help_text="organization, project, email, etc.)") shirt_size = ChoiceField(choices=SHIRT_SIZES, initial='L', widget=RadioSelect( renderer=RadioFieldInlineRenderer)) shirt_type = ChoiceField(choices=SHIRT_TYPES, initial='M', widget=RadioSelect( renderer=RadioFieldInlineRenderer)) food_options = MultipleChoiceField( choices=zip(Registration.food_options, Registration.food_option_labels), label=_('Please indicate any special dietary requirements ' '(we will do our best to accommodate requests, ' 'but cannot guarantee availability):'), required=False, widget=CheckboxInlineSelectMultiple) email_ok = BooleanField(initial=True, label="Check here if we may send email updates about PyCon:") listing_ok = BooleanField(initial=True, label="Check here to permit inclusion in the delegate listing:") extra_info = CharField(required=False, widget=Textarea, label=_('Additional information'), help_text=_('(special instructions, etc.)')) tutorials = HtmlInjectionField( label=_('Tutorials (fees include lunch and break refreshments):')) def __init__(self, early_reg, user=None, sample_badge=False, at_door=False, *args, **kwdargs): self.user = user self.early_reg = early_reg self.at_door = at_door if user is not None: initial = {} initial['name'] = unicode(user)[:60] initial['email'] = user.email initial.update(kwdargs.get('initial', dict())) kwdargs['initial'] = initial super(NewRegEntryForm, self).__init__(*args, **kwdargs) if user is None: self.fields.pop('not_me') mycosts = reg_costs[0 if early_reg else 1] if at_door: mycosts = reg_costs[-1] self.fields['reg_type'].choices = mycosts['reg'] self.fields['reg_type'].initial = mycosts['reg'][0][0] self.reg_costs = [x for x,y in mycosts['reg']] self.tut_costs = mycosts['tut'] self.sessions = [] for session, label in SESSIONS: choices = [ (t.id, t) for t in Tutorial.objects.filter(status='Open', session=session)] if len(choices) == 0: continue choices.insert(0,('', 'None')) self.fields.setdefault(session, ChoiceField(label=label, initial='', required=False, choices=choices, widget=RadioSelect( renderer=TutorialRadioFieldRenderer))) self.sessions.append(session) if not len(self.sessions): self.fields.pop('tutorials') else: t = loader.get_template("attendeereg/tutorial_costs.html") self.fields['tutorials'].html = t.render( Context({'costs': mycosts['tut'][1:len(self.sessions)+1]})) if sample_badge: self.update_badge() else: self.fields.pop('sample_badge') def update_badge(self): '''update the badge url data from "dirty" form data''' sbadge = self.fields['sample_badge'] data = self.data if self.data else {} initial = self.initial if self.initial else {} prefix = (self.prefix + '-') if self.prefix else '' badge_field_map = Registration.badge_field_map get_field = lambda name: data.get(prefix+name, initial.get(prefix+name, None)) def set_field(d, n, v): if v: d[smart_str(badge_field_map.get(n, n))] = smart_str(v) def get_and_set_field(d,n): set_field(d, n, get_field(n)) name = get_field('name') badge_name = get_field('badge_name') if not name and self.user: name = unicode(self.user) if not badge_name: badge_name = name gdict = {} ## RED_FLAG: should we special encode the names/text for unicode? set_field(gdict, 'name', name) set_field(gdict, 'badge_name', badge_name) get_and_set_field(gdict, 'badge_text1') get_and_set_field(gdict, 'badge_text2') if self.user: #gdict['id'] = self.user.id # sample badge says 'SAMPLE' hgs = [ g['name'] for g in self.user.groups.filter( name__in=Registration.group_flag_map.keys()).values('name') ] for key, val in Registration.group_flag_map.iteritems(): if key in hgs: set_field(gdict, val, u'True') badge_html = u'
' self.fields['sample_badge'].html = badge_html def clean_reg_type(self): """There is an issue with early bound special cross fields validators like this one. We have not processed teh tutorial data yet. So we are in a real bind here. We must back off to the data object for the post. yuck! """ val = self.cleaned_data['reg_type'] index = self.reg_costs.index(val) if index == 3: n = sum(1 for s in self.sessions if self.data.get(self.prefix+'-'+s,'') != '') if n == 0: raise ValidationError, ( "You must select at least one tutorial for a tutorial pass") return Decimal(val) def subtotal(self): if not self.is_valid(): if self.data: return Decimal("0.00") return Decimal(self.fields['reg_type'].initial) reg = self.cleaned_data['reg_type'] ind = sum(1 for s in self.sessions if self.cleaned_data[s] != '') tut = Decimal(self.tut_costs[ind]) return reg+tut def save(self, invoice): if not self.is_valid(): raise ValidationError, "Something broke" reg = Registration() reg.invoice = invoice if self.user is not None and not self.cleaned_data['not_me']: reg.user = self.user reg.early_reg = self.early_reg reg.at_door = self.at_door reg.name = self.cleaned_data['name'] reg.email = self.cleaned_data['email'] index = self.reg_costs.index(self.data[self.prefix+'-reg_type']) if index == 0: reg.corporate = True if index < 3: reg.conference = True if index == 2: reg.student = True if index == 3: reg.tutorial = True reg.cost = self.subtotal() for name in Registration.changable: if name in self.cleaned_data: setattr(reg, name, self.cleaned_data[name]) if 'food_options' in self.cleaned_data: for option in self.cleaned_data['food_options']: setattr(reg, option, True) reg.save() tut_ids = [ self.cleaned_data[s] for s in self.sessions if self.cleaned_data[s] != ''] if tut_ids: tuts = Tutorial.objects.filter(pk__in=tut_ids) #assert(len(tut_ids) == len(tuts)) reg.tutorial = True reg.tutorials = tuts reg.save() ## red_flag: send e-mail's here!?!? or in view? ## in view is safer in case of crash? hmmm... return reg class ManualPayPalAckForm(Form): PNREF = CharField(max_length=13) NAME = CharField(max_length=61) EMAIL = CharField(max_length=41) AUTHCODE = CharField(required=False, widget=Textarea) RESULTMSG = CharField(required=False, widget=Textarea) org_notes = CharField(label=_('organizer notes'), widget=Textarea, required=False) def __init__(self, invoice, ip, *args, **kwdargs): self.ip = ip self.invoice = invoice initial = kwdargs.setdefault('initial', dict()) if 'NAME' not in initial: initial['NAME'] = unicode(invoice.user) if 'EMAIL' not in initial: initial['EMAIL'] = invoice.user.email super(ManualPayPalAckForm, self).__init__(*args, **kwdargs) def clean_PNREF(self): val = self.cleaned_data['PNREF'] if PayPalAck.objects.filter(PNREF=val).count(): raise ValidationError, ( "Ack with this PNREF already exists.") return val def save(self, commit=True): ack = PayPalAck() ack.invoice = self.invoice ack.from_ip = self.ip ack.INVOICE = unicode(self.invoice) ack.CUSTID = self.invoice.user.id ack.DESCRIPTION = self.invoice.get_desc() ack.RESULT = 1 ack.NAME = self.cleaned_data['NAME'] ack.EMAIL = self.cleaned_data['EMAIL'] ack.AUTHCODE = self.cleaned_data['AUTHCODE'] ack.RESULTMSG = self.cleaned_data['RESULTMSG'] ack.PNREF = self.cleaned_data['PNREF'] ack.org_notes = self.cleaned_data['org_notes'] if commit: ack.save() return ack class PayPalCheckoutForm(Form): LOGIN = CharField(max_length=100, widget=HiddenInput, initial=PAYPAL_LOGIN) PARTNER = CharField(max_length=100, widget=HiddenInput, initial=PAYPAL_PARTNER) TYPE = CharField(max_length=1, widget=HiddenInput, initial='S') AMOUNT = DecimalField(max_digits=4, decimal_places=2, widget=HiddenInput) INVOICE = CharField(max_length=9, widget=HiddenInput) NAME = CharField(max_length=60, widget=HiddenInput, required=False) CUSTID = CharField(max_length=11, widget=HiddenInput, required=False) EMAIL = EmailField(max_length=40, widget=HiddenInput, required=False) DESCRIPTION = CharField(max_length=255, widget=HiddenInput, required=False) COMMENT1 = CharField(max_length=255, widget=HiddenInput, required=False) COMMENT2 = CharField(max_length=255, widget=HiddenInput, required=False) def __init__(self, invoice, *args, **kwdargs): self.invoice = invoice self.url = PAYPAL_URL initial = {} initial['AMOUNT'] = '%.2f' % invoice.total_cost initial['INVOICE'] = unicode(invoice) ## RED_FLAG: safe ascii_ize? #initial['NAME'] = unicode(invoice.user)[:60] initial['CUSTID'] = str(invoice.user.id)[:11] initial['EMAIL'] = invoice.user.email[:40] desc = invoice.get_desc() initial['DESCRIPTION'] = desc[:255] initial['COMMENT1'] = desc[:255] if len(desc) > 510: initial['COMMENT2'] = desc[255:510] if 'initial' in kwdargs: initial.update(kwdargs['initial']) kwdargs['initial'] = initial super(PayPalCheckoutForm, self).__init__(*args, **kwdargs) if 'COMMENT2' not in initial or not initial['COMMENT2']: self.fields.pop('COMMENT2') class ChangeRequestForm(Form): message = CharField(widget=Textarea) mail_base = { 'from_email': settings.ATTREG_REQUEST_FROM_EMAIL, 'replyto_email': getattr(settings,'ATTREG_REQUEST_REPLYTO_EMAIL',None), 'recipient_list': getattr(settings, 'ATTREG_REQUEST_CC', []), } def __init__(self, user, inv, reg, *args, **kwdargs): self.reg = reg self.inv = inv self.user = user super(ChangeRequestForm, self).__init__(*args, **kwdargs) def get_to_list(self): to = [self.user.email] if (self.reg and self.reg.invoice and self.reg.invoice.user_id != self.user.id): to.append(self.reg.invoice.user.email) elif self.inv and self.inv.user_id != self.user.id: to.append(self.inv.user.email) to.extend(self.mail_base['recipient_list']) return to def save(self, commit=True, sendmail=True): cr = ChangeRequest() cr.user = self.user if self.reg: cr.reg = self.reg if self.inv: cr.invoice = self.inv cr.text = self.cleaned_data['message'] to_list = self.get_to_list() cr.to = u", ".join(to_list) if commit: cr.save() if sendmail: self.send_mail(cr, to_list) return cr def get_subject(self): subject = u'Change request for ' if self.inv: subject += u'Invoice ' + unicode(self.inv) else: if self.reg.invoice: subject += u'Invoice ' + unicode(self.reg.invoice) + ' ' subject += u'Registration R%d ' % (self.reg.id,) subject += self.reg.name return subject def send_mail(self, cr, to_list): data = deepcopy(self.mail_base) data['recipient_list'] = to_list data['subject'] = self.get_subject() t = loader.get_template("attendeereg/change_request.txt") data['message'] = t.render(Context({ 'settings': settings, 'message': cr.text, 'site': Site.objects.get_current(), 'from_user': self.user, 'registration': self.reg, 'invoice': self.inv, })) from pycon.core.mail import send_mail return send_mail(**data) class OptionalCommentForm(Form): mail_reg = BooleanField(initial=False, label=_("Also send e-mails to registrations"), help_text=_("(If reg has multiple registrations)")) comment = CharField(required=False, widget=Textarea, label=_('Additional information'), help_text=_('(optional additional comments to invoice owner)')) class ChangeRequestMultiChoiceIdField(MultipleChoiceField): widget = CheckboxSelectMultiple def __init__(self): super(ChangeRequestMultiChoiceIdField, self).__init__(required=False) def clean(self, value): if not value: return [] if not isinstance(value, (list, tuple)): return [] return ChangeRequest.objects.filter(id__in=[ int(x) for x in value ], handled=False) class MarkRequestsHandled(Form): request = ChangeRequestMultiChoiceIdField() def save(self): if not self.is_valid(): raise ValidationError, "Something broke" for request in self.cleaned_data['request']: request.handled = True request.save() class GetRegistration(Form): reg = IntegerField(label=_('Registration ID: R')) def clean_reg(self): try: return Registration.objects.get(pk=self.cleaned_data['reg']) except Registration.DoesNotExist: raise ValidationError, "Not a valid registration ID." @property def registration(self): if not self.is_valid(): return None return self.cleaned_data['reg'] class SelectUsersOrRegistrationsForm(Form): selected = MultipleChoiceField( label=_('Search Users and Unconnected Registrations')) script = HtmlInjectionField(label=u'', html=(u'')) def __init__(self, *args, **kwdargs): super(SelectUsersOrRegistrationsForm, self).__init__(*args, **kwdargs) self.fields['selected'].choices = self.get_choices() def clean_selected(self): selected = self.cleaned_data['selected'] if not selected: raise ValidationError, ( "You must select at least one useror registration.") rids = [ ent[1:] for ent in selected if ent.startswith('R') ] uids = [ ent for ent in selected if not ent.startswith('R') ] users = User.objects.filter(id__in=uids).select_related() reges = Registration.objects.filter(id__in=rids).select_related() self._users = users self._registrations = reges return (users, reges) @property def users(self): return self.cleaned_data['selected'][0] @property def unconnected(self): return self.cleaned_data['selected'][1] @classmethod def regen_choices(cls): cache.delete('attendeereg_user_reg_choice_widget') return cls.get_choices() @classmethod def get_choices(cls): ## there must be a decorator for this choices = cache.get('attendeereg_user_reg_choice_widget') if choices is None: choices = cls.generate_choices() cache.set('attendeereg_user_reg_choice_widget', choices, 60*60*1) return choices @staticmethod def generate_choices(): def un2data(ent): last, first = split_name(ent['name']) key = u'R' + unicode(ent['id']) val = u'%s <%s> (unconnected)' % (ent['name'], ent['email']) return (last, first, key, val) def us2data(u): val = u'%s <%s>' % (unicode(u), u.email) if u.has_reg: val += u' (registered)' if not u.is_active: val+=u'*' return (u.last_name, u.first_name, unicode(u.id), val) users = (us2data(u) for u in User.objects.extra(select={'has_reg': '(SELECT COUNT(*) FROM attendeereg_registration ' ' WHERE attendeereg_registration.user_id = auth_user.id ' ' AND attendeereg_registration.active = TRUE) > 0'})) unconnected = (un2data(r) for r in Registration.objects.filter( user__isnull=True).exclude( invoice__status='IV').values('name', 'email', 'id')) return [c[2:] for c in sorted(chain(users, unconnected))] class RegSearch(Form): query = CharField(label="",required=False) class UserForm(Form): # username=form.CharField(_('username'), max_length=30, required=False, validator_list=[validators.isAlphaNumeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores).")) first_name = CharField(_('first name'), required=False) last_name = CharField(_('last name'), required=False) # email = EmailField(_('e-mail address'), required=True) # password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the change password form.")) # class InvosceForm(form.Form): class RegForm(Form): tutorials = ModelMultipleChoiceField(queryset=Tutorial.objects.all(), required=False) corporate = BooleanField(_('corporate registration')) student = BooleanField(_('student registration')) # early_reg = BooleanField(_('early-bird registration')) door_reg = BooleanField(_('door registration'), initial=True) conference = BooleanField(_('conference registration')) tutorial = BooleanField(_('tutorial registration')) vendor = BooleanField() sponsor = BooleanField() session = BooleanField() speaker = BooleanField() keynote = BooleanField() cost = DecimalField(max_digits=6, decimal_places=2) paid = BooleanField() active = BooleanField() badge_name = CharField(required=False) badge_text1 = CharField(required=False) badge_text2 = CharField(required=False) shirt_size = ChoiceField( choices=SHIRT_SIZES, initial='L') shirt_type = ChoiceField( choices=SHIRT_TYPES, initial='M') vegetarian = BooleanField() vegan = BooleanField() kosher = BooleanField() halal = BooleanField() email_ok = BooleanField(_('ok to e-mail'), initial=True) listing_ok = BooleanField(_('ok to list name'), initial=True) # hotel = CharField(required=False) # checkin = DateTimeField(required=False) extra_info = CharField(required=False)