/zkpylons/controllers/registration.py
Python | 1039 lines | 1015 code | 22 blank | 2 comment | 16 complexity | 6f350d352d5d0e9e3e24ddba424c3e8a MD5 | raw file
- # coding=utf-8
- import logging
- import re
- from pylons import request, response, session, tmpl_context as c
- from zkpylons.lib.helpers import redirect_to
- from pylons.controllers.util import Response
- from pylons.decorators import validate
- from pylons.decorators.rest import dispatch_on
- from formencode import validators, htmlfill, Invalid
- from formencode.variabledecode import NestedVariables
- from zkpylons.lib.base import BaseController, render
- from zkpylons.lib.ssl_requirement import enforce_ssl
- from zkpylons.lib.validators import BaseSchema, DictSet, ProductInCategory, CheckboxQty
- from zkpylons.lib.validators import ProductQty, ProductMinMax, IAgreeValidator, CountryValidator
- # validators used from the database
- from zkpylons.lib.validators import ProDinner, PPDetails, PPChildrenAdult
- import zkpylons.lib.helpers as h
- from authkit.authorize.pylons_adaptors import authorize
- from authkit.permissions import ValidAuthKitUser
- from zkpylons.lib.mail import email
- from zkpylons.model import meta
- from zkpylons.model import Registration, Role, RegistrationProduct, Person
- from zkpylons.model import ProductCategory, Product, Voucher, Ceiling
- from zkpylons.model import Invoice, InvoiceItem
- from zkpylons.model.special_offer import SpecialOffer
- from zkpylons.model.special_registration import SpecialRegistration
- from zkpylons.config.lca_info import lca_info, lca_rego
- from zkpylons.controllers.person import PersonSchema
- log = logging.getLogger(__name__)
- import datetime
- class CheckAccomDates(validators.FormValidator):
- def __init__(self, checkin_name, checkout_name):
- super(self.__class__, self).__init__()
- self.checkin = checkin_name
- self.checkout = checkout_name
- def validate_python(self, values, state):
- if values[self.checkin] >= values[self.checkout]:
- error_dict = {
- self.checkin: "Your checkin date must be before your check out",
- self.checkout: "Your checkout date must be after your check in.",
- }
- raise Invalid(self.__class__.__name__, values, state, error_dict=error_dict)
- class VoucherValidator(validators.FancyValidator):
- def validate_python(self, value, state):
- voucher = Voucher.find_by_code(value)
- if voucher != None:
- if voucher.registration:
- if voucher.registration.person.id != h.signed_in_person().id:
- raise Invalid("Voucher code already in use!", value, state)
- elif value:
- raise Invalid("Unknown voucher code!", value, state)
- class SillyDescriptionChecksum(validators.FormValidator):
- def __init__(self, silly_name, checksum_name):
- super(self.__class__, self).__init__()
- self.__silly_name = silly_name
- self.__checksum_name = checksum_name
- def validate_python(self, values, state):
- silly_description = values.get(self.__silly_name, None)
- if silly_description is None:
- return
- checksum = h.silly_description_checksum(silly_description)
- if values.get(self.__checksum_name, None) != checksum:
- error_dict = {
- self.__silly_name: "Smart enough to hack the silly description, not smart enough to hack the checksum.",
- }
- raise Invalid(self.__class__.__name__, values, state, error_dict=error_dict)
- class OtherValidator(validators.String):
- def __init__(self, list, if_missing=None):
- super(self.__class__, self).__init__(if_missing=if_missing)
- self.__list = list
- def validate_python(self, value, state):
- if value is not None:
- for bad in self.__list:
- if bad in value.lower():
- raise Invalid(value + ' -- Nice try!', value, state)
- class ExistingPersonSchema(BaseSchema):
- company = validators.String()
- phone = validators.String()
- mobile = validators.String()
- address1 = validators.String(not_empty=True)
- address2 = validators.String()
- city = validators.String(not_empty=True)
- state = validators.String()
- postcode = validators.String(not_empty=True, min=3, max=10)
- country = CountryValidator(not_empty=True)
- i_agree = validators.Bool(if_missing=False)
- chained_validators = [IAgreeValidator("i_agree")]
- class RegistrationSchema(BaseSchema):
- over18 = validators.Int(min=0, max=1, not_empty=True)
- nick = validators.String(if_missing=None)
- shell = validators.String(if_missing=None)
- shelltext = OtherValidator(if_missing=None, list=('cmd', 'command'))
- editor = validators.String(if_missing=None)
- editortext = OtherValidator(if_missing=None, list=('word', 'write'))
- distro = validators.String(if_missing=None)
- distrotext = OtherValidator(if_missing=None, list=('window', 'xp', 'vista'))
- vcs = validators.String(if_missing=None)
- vcstext = OtherValidator(if_missing=None, list=('visual sourcesafe', 'bitkeeper'))
- silly_description = validators.String(if_missing=None)
- silly_description_checksum = validators.String(if_missing=None, strip=True)
- if lca_rego['pgp_collection'] != 'no':
- keyid = validators.String()
- planetfeed = validators.String(if_missing=None)
- voucher_code = VoucherValidator(if_empty=None)
- diet = validators.String()
- special = validators.String()
- signup = DictSet(if_missing=None)
- prevlca = DictSet(if_missing=None)
- chained_validators = [
- SillyDescriptionChecksum("silly_description", "silly_description_checksum"),
- ]
- class SpecialOfferSchema(BaseSchema):
- name = validators.String()
- member_number = validators.String()
- class NewRegistrationSchema(BaseSchema):
- person = ExistingPersonSchema()
- registration = RegistrationSchema()
- special_offer = SpecialOfferSchema()
- pre_validators = [NestedVariables]
- class ProductUnavailable(Exception):
- """Exception when a product isn't available
- Attributes:
- product -- the product that wasn't available
- """
- def __init__(self, product):
- self.product = product
- edit_schema = NewRegistrationSchema()
- class RegistrationController(BaseController):
- @enforce_ssl(required_all=True)
- @authorize(h.auth.is_activated_user)
- @authorize(h.auth.is_valid_user)
- def __before__(self, **kwargs):
- c.product_categories = ProductCategory.find_all()
- c.ceilings = {}
- for ceiling in Ceiling.find_all():
- c.ceilings[ceiling.name] = ceiling
- self._generate_product_schema()
- c.products = Product.find_all()
- c.product_available = self._product_available
- c.able_to_edit = self._able_to_edit
- c.manual_invoice = self.manual_invoice
- c.signed_in_person = h.signed_in_person()
- def _able_to_edit(self):
- for invoice in h.signed_in_person().invoices:
- if not invoice.is_void:
- if invoice.is_paid and invoice.total != 0:
- return False, "Sorry, you've already paid. Contact the team at " + h.lca_info['contact_email'] + " if you need anything changed."
- return True, "You can edit"
- def _product_available(self, product, stock=True, qty=0):
- # bool stock: care about if the product is in stock (ie sold out?)
- if not product.available(stock, qty):
- return False
- if product.auth is not None:
- exec("auth = " + product.auth)
- if not auth:
- return False
- return True
- def _generate_product_schema(self):
- # Since the form is arbitrarily defined by what product types there are, the validation
- # (aka schema) also needs to be dynamic.
- # Thus, this function generates a dynamic schema to validate a given set of products.
- #
- class ProductSchema(BaseSchema):
- # This schema is used to validate the products submitted by the
- # form. It is populated below
- # EG:
- # ProductSchema.add_field('count', validators.Int(min=1, max=100))
- # is the same as doing this inline:
- # count = validators.Int(min=1, max=100)
- #
- # 2009-05-07 Josh H: Not sure why, but there is a reason this
- # class is declaired within this method and not earlier on
- # like in the voucher controller. Or maybe I just did this
- # poorly...
- pass
- # placed here so prevalidator can refer to it. This means we need a hacky method to save it :S
- ProductSchema.add_field('partner_name', validators.String(if_missing=None))
- ProductSchema.add_field('partner_email', validators.Email(if_missing=None))
- ProductSchema.add_field('partner_mobile', validators.String(if_missing=None))
- # Go through each category and each product and add generic validation
- for category in c.product_categories:
- clean_cat_name = category.clean_name()
- if category.display in ('radio', 'select'):
- # min/max can't be calculated on this form. You should only have 1 selected.
- ProductSchema.add_field('category_' + clean_cat_name, ProductInCategory(category=category, not_empty=True))
- for product in category.products:
- if product.validate is not None:
- validator = eval(product.validate)
- validator.error_field_name = "error.%s" % category.clean_name()
- ProductSchema.add_chained_validator(validator)
- elif category.display == 'checkbox':
- product_fields = []
- for product in category.products:
- clean_prod_desc = product.clean_description()
- product_field_name = 'product_' + clean_cat_name + '_' + clean_prod_desc + '_checkbox'
- ProductSchema.add_field(product_field_name, CheckboxQty(product=product, if_missing=False))
- product_fields.append(product_field_name)
- if product.validate is not None:
- validator = eval(product.validate)
- validator.error_field_name = "error.%s" % category.clean_name()
- ProductSchema.add_chained_validator(validator)
- validator = self.min_max_validator(product_fields, category)
- ProductSchema.add_chained_validator(validator)
- elif category.display == 'qty':
- # qty
- product_fields = []
- for product in category.products:
- clean_prod_desc = product.clean_description()
- product_field_name = 'product_' + clean_cat_name + '_' + clean_prod_desc + '_qty'
- ProductSchema.add_field(product_field_name, ProductQty(product=product, if_missing=None))
- product_fields.append(product_field_name)
- if product.validate is not None:
- validator = eval(product.validate)
- validator.error_field_name = "error.%s" % category.clean_name()
- ProductSchema.add_chained_validator(validator)
- validator = self.min_max_validator(product_fields, category)
- ProductSchema.add_chained_validator(validator)
- edit_schema.add_field('products', ProductSchema)
- @classmethod
- def min_max_validator(cls, fields, category):
- return ProductMinMax(
- product_fields=fields,
- min_qty=category.min_qty,
- max_qty=category.max_qty,
- category_name=category.name,
- error_field_name="error.%s" % category.clean_name())
- def is_speaker(self):
- try:
- return c.signed_in_person.is_speaker()
- except:
- return False
- def is_miniconf_org(self):
- try:
- return c.signed_in_person.is_miniconf_org()
- except:
- return False
- def is_volunteer(self,product):
- try:
- return c.signed_in_person and c.signed_in_person.volunteer and c.signed_in_person.volunteer.accepted and c.signed_in_person.volunteer.ticket_type == product
- except:
- return False
- def is_role(self, role):
- try:
- return c.signed_in_person.has_role(role)
- except:
- return False
- def is_same_person(self):
- return c.signed_in_person == c.registration.person
- @dispatch_on(POST="_new")
- def new(self):
- c.signed_in_person = h.signed_in_person()
- h.check_for_incomplete_profile(c.signed_in_person)
- if c.signed_in_person and c.signed_in_person.registration:
- redirect_to(action='edit', id=c.signed_in_person.registration.id)
- fields = dict(request.GET)
- c.special_offer = None
- if 'offer' in fields:
- c.special_offer = SpecialOffer.find_by_name(fields['offer'])
- if c.special_offer is not None:
- if c.special_offer.enabled:
- # Create the special_registration record
- special_registration = SpecialRegistration()
- special_registration.special_offer_id = c.special_offer.id
- special_registration.member_number = '' # TODO: switch to None
- special_registration.person_id = c.signed_in_person.id
- meta.Session.add(special_registration)
- meta.Session.commit()
- else:
- c.special_offer = None
- else:
- # The user alreay has used a special URL to register
- if c.signed_in_person.special_registration:
- c.special_offer = c.signed_in_person.special_registration[0].special_offer
- if c.special_offer is None and lca_info['conference_status'] is not 'open':
- if not h.auth.authorized(h.auth.has_organiser_role):
- redirect_to(action='status')
- else:
- # User is an organiser, so if the status is also 'debug' then they can register
- if lca_info['conference_status'] is not 'debug':
- redirect_to(action='status')
- defaults = {}
- if h.lca_rego['personal_info']['home_address'] == 'no':
- defaults['person.address1'] = 'not available'
- defaults['person.city'] = 'not available'
- defaults['person.postcode'] = 'not available'
- if c.signed_in_person:
- for k in ['address1', 'address2', 'city', 'state', 'postcode', 'country', 'phone', 'mobile', 'company', 'i_agree']:
- v = getattr(c.signed_in_person, k)
- if v is not None:
- defaults['person.' + k] = getattr(c.signed_in_person, k)
- defaults['registration.signup.announce'] = 1
- defaults['registration.checkin'] = 17
- defaults['registration.checkout'] = 24
- #
- # Fugly hack. If we aren't booking accommodation, then default the
- # product bought for Accommodation to 'I will organise my own'.
- #
- category = ProductCategory.find_by_name("Accommodation")
- if category and (len(category.products) == 0 or (len(category.products) == 1 and category.products[0].cost == 0)):
- field_name = 'products.category_%s' % category.name.replace("-","_")
- defaults[field_name] = category.products[0].id
- # Hacker-proof silly_description field
- c.silly_description, checksum = h.silly_description()
- defaults['registration.silly_description'] = c.silly_description
- defaults['registration.silly_description_checksum'] = checksum
- form = render("/registration/new.mako")
- return htmlfill.render(form, defaults)
- @validate(schema=edit_schema, form='new', post_only=True, on_get=True, variable_decode=True)
- def _new(self):
- if c.signed_in_person and c.signed_in_person.registration:
- redirect_to(action='_edit', id=c.signed_in_person.registration.id)
- result = self.form_result
- c.special_offer = None
- if 'special_offer' in result:
- c.special_offer = SpecialOffer.find_by_name(result['special_offer']['name'])
- if c.special_offer is not None and not c.special_offer.enabled:
- c.special_offer = None
- if c.special_offer is None and lca_info['conference_status'] is not 'open':
- if not h.auth.authorized(h.auth.has_organiser_role):
- redirect_to(action='status')
- else:
- # User is an organiser, so if the status is also 'debug' then they can register
- if lca_info['conference_status'] is not 'debug':
- redirect_to(action='status')
- # A blank registration
- c.registration = Registration()
- self.save_details(result)
- c.student_ticket = False
- c.infants = False
- c.children = False
- c.pp_children = False
- for rproduct in c.registration.products:
- if 'Ticket' in rproduct.product.category.name and 'Student' in rproduct.product.description:
- c.student_ticket = True
- elif 'Dinner' in rproduct.product.category.name:
- if 'Infant' == rproduct.product.description:
- c.infants = True
- elif 'Child' == rproduct.product.description:
- c.children = True
- elif rproduct.product.category.name == 'Partners Programme' and ('Child' in rproduct.product.description or 'Infant' in rproduct.product.description):
- c.pp_children = True
- email(
- c.person.email_address,
- render('registration/response.mako'))
- self.pay(c.registration.id, quiet=1)
- h.flash("Thank you for your registration!")
- if not c.person.paid():
- h.flash("To complete the registration process, please pay your invoice.")
- redirect_to(action='status')
- @dispatch_on(POST="_edit")
- def edit(self, id):
- if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
- # Raise a no_auth error
- h.auth.no_role()
- # If we're an organiser, then don't check payment status.
- if not h.auth.authorized(h.auth.has_organiser_role):
- able, response = self._able_to_edit()
- if not able:
- c.error = response
- return render("/registration/error.mako")
- c.registration = Registration.find_by_id(id)
- defaults = {}
- defaults.update(h.object_to_defaults(c.registration, 'registration'))
- defaults.update(h.object_to_defaults(c.registration.person, 'person'))
- for rproduct in c.registration.products:
- product = rproduct.product
- category_name = product.category.clean_name()
- if product.category.display == 'checkbox':
- if product.available():
- defaults['products.product_' + category_name + '_' + product.clean_description() + '_checkbox'] = '1'
- elif product.category.display == 'qty':
- if product.available() and rproduct.qty > 0:
- defaults['products.product_' + category_name + '_' + product.clean_description() + '_qty'] = rproduct.qty
- else:
- if product.available():
- defaults['products.category_' + category_name] = product.id
- # generate new silly description
- c.silly_description, checksum = h.silly_description()
- defaults['registration.silly_description'] = c.silly_description
- defaults['registration.silly_description_checksum'] = checksum
- # the partner fields are attached to the product in the form for some reason
- defaults['products.partner_name'] = c.registration.partner_name
- defaults['products.partner_email'] = c.registration.partner_email
- defaults['products.partner_mobile'] = c.registration.partner_mobile
- if c.registration.over18:
- defaults['registration.over18'] = 1
- else:
- defaults['registration.over18'] = 0
- if c.registration.shell in lca_rego['shells'] or c.registration.shell == '':
- defaults['registration.shell'] = c.registration.shell
- else:
- defaults['registration.shell'] = 'other'
- defaults['registration.shelltext'] = c.registration.shell
- if c.registration.editor in lca_rego['editors'] or c.registration.editor == '':
- defaults['registration.editor'] = c.registration.editor
- else:
- defaults['registration.editor'] = 'other'
- defaults['registration.editortext'] = c.registration.editor
- if c.registration.distro in lca_rego['distros'] or c.registration.distro == '':
- defaults['registration.distro'] = c.registration.distro
- else:
- defaults['registration.distro'] = 'other'
- defaults['registration.distrotext'] = c.registration.distro
- if c.registration.vcs in lca_rego['vcses']:
- defaults['registration.vcs'] = c.registration.vcs
- else:
- defaults['registration.vcs'] = 'other'
- defaults['registration.vcstext'] = c.registration.vcs
- form = render('/registration/edit.mako')
- if c.form_errors:
- return form
- else:
- return htmlfill.render(form, defaults)
- @validate(schema=edit_schema, form='edit', post_only=True, on_get=True, variable_decode=True)
- def _edit(self, id):
- # We need to recheck auth in here so we can pass in the id
- if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
- # Raise a no_auth error
- h.auth.no_role()
- c.registration = Registration.find_by_id(id)
- result = self.form_result
- c.special_offer = None
- self.save_details(result)
- self.pay(c.registration.id, quiet=1)
- h.flash("Thank you for updating your registration!")
- if not c.person.paid():
- #h.flash("To complete the registration process, please pay your invoice.")
- redirect_to(controller='registration', action='pay', id=c.registration.id)
- redirect_to(action='status')
- def save_details(self, result):
- # Store Registration details
- for k in result['registration']:
- if k in ('shell', 'editor', 'distro', 'vcs'):
- if result['registration'][k] == 'other':
- setattr(c.registration, k, result['registration'][k + 'text'])
- else:
- setattr(c.registration, k, result['registration'][k])
- else:
- setattr(c.registration, k, result['registration'][k])
- if result['registration']['over18'] == 1:
- setattr(c.registration, 'over18', True)
- else:
- setattr(c.registration, 'over18', False)
- # hacky method to make validating sane
- setattr(c.registration, 'partner_name', result['products']['partner_name'])
- setattr(c.registration, 'partner_email', result['products']['partner_email'])
- setattr(c.registration, 'partner_mobile', result['products']['partner_mobile'])
- # Check whether we're already signed in or not, and store person details
- if c.registration.person:
- c.person = c.registration.person
- else:
- c.person = c.signed_in_person
- for k in result['person']:
- setattr(c.person, k, result['person'][k])
- # Create person<->registration relationship
- c.registration.person = c.person
- # Deal with the special offer if any
- if c.special_offer is not None:
- special_registration = SpecialRegistration.find_by_person_and_offer(c.person.id, c.special_offer.id)
- special_registration.member_number = result['special_offer']['member_number']
- meta.Session.add(special_registration)
- # Always delete the current products
- c.registration.products = []
- # Store Product details
- for category in c.product_categories:
- clean_cat_name = category.clean_name()
- if category.display in ('radio', 'select'):
- #product = Product.find_by_cat_and_desc(category.id, result['products']['category_' + clean_cat_name])
- product = Product.find_by_id(result['products']['category_' + clean_cat_name])
- if product != None:
- rego_product = RegistrationProduct()
- rego_product.registration = c.registration
- rego_product.product = product
- #if product.category.name == 'Accommodation':
- # rego_product.qty = c.registration.checkout - c.registration.checkin
- #else:
- # rego_product.qty = 1
- rego_product.qty = 1
- c.registration.products.append(rego_product)
- elif category.display == 'checkbox':
- for product in category.products:
- clean_prod_desc = product.clean_description()
- if result['products']['product_' + clean_cat_name + '_' + clean_prod_desc + '_checkbox']:
- rego_product = RegistrationProduct()
- rego_product.registration = c.registration
- rego_product.product = product
- rego_product.qty = 1
- c.registration.products.append(rego_product)
- elif category.display == 'qty':
- for product in category.products:
- clean_prod_desc = product.clean_description()
- if result['products']['product_' + clean_cat_name + '_' + clean_prod_desc + '_qty'] not in [0, None]:
- rego_product = RegistrationProduct()
- rego_product.registration = c.registration
- rego_product.product = product
- rego_product.qty = result['products']['product_' + clean_cat_name + '_' + clean_prod_desc + '_qty']
- c.registration.products.append(rego_product)
- meta.Session.commit()
- def status(self, id=0):
- if int(id) == 0:
- if h.signed_in_person() and h.signed_in_person().registration:
- c.registration = h.signed_in_person().registration
- else:
- c.registration = None
- else:
- if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
- # Raise a no_auth error
- h.auth.no_role()
- c.registration = Registration.find_by_id(id, abort_404 = False)
- if c.registration is None:
- c.person = h.signed_in_person()
- else:
- c.person = c.registration.person
- return render("/registration/status.mako")
- def pay(self, id, quiet=0):
- if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
- # Raise a no_auth error
- h.auth.no_role()
- registration = Registration.find_by_id(id)
- # Checks all existing invoices and invalidates them if a product is not available
- self.check_invoices(registration.person.invoices)
- # If we have a manual invoice, don't try and re-generate invoices
- if not self.manual_invoice(registration.person.invoices):
- try:
- invoice = self._create_invoice(registration)
- except ProductUnavailable, inst:
- if quiet: return
- c.product = inst.product
- c.registration = registration
- return render('/registration/product_unavailable.mako')
- if registration.voucher:
- self.apply_voucher(invoice, registration.voucher)
- # complicated check to see whether invoice is already in the system
- new_invoice = invoice
- for old_invoice in registration.person.invoices:
- if old_invoice != new_invoice and not old_invoice.manual and not old_invoice.is_void:
- if self.invoices_identical(old_invoice, new_invoice):
- invoice = old_invoice
- if not quiet:
- redirect_to(controller='invoice', action='view', id=invoice.id)
- meta.Session.rollback()
- return
- if old_invoice.due_date < new_invoice.due_date:
- new_invoice.due_date = old_invoice.due_date
- old_invoice.void = "Registration Change"
- invoice.last_modification_timestamp = datetime.datetime.now()
- meta.Session.commit()
- if quiet: return
- redirect_to(controller='invoice', action='view', id=invoice.id)
- else:
- redirect_to(action='status')
- def check_invoices(self, invoices):
- for invoice in invoices:
- if not invoice.is_void and not invoice.manual and not invoice.is_paid:
- for ii in invoice.items:
- if ii.product and not self._product_available(ii.product, True, ii.qty):
- invoice.void = "Product " + ii.product.category.name + " - " + ii.product.description + " is no longer available"
- meta.Session.commit()
- def manual_invoice(self, invoices):
- for invoice in invoices:
- if not invoice.is_void and invoice.manual:
- return True
- return False
- def invoices_identical(self, invoice1, invoice2):
- if invoice1.total == invoice2.total:
- if len(invoice1.items) == len(invoice2.items):
- matched_products = 0
- invoice1_matched_items = dict()
- invoice2_matched_items = dict()
- for invoice1_item in invoice1.items:
- if invoice1_item.id in invoice1_matched_items:
- continue
- for invoice2_item in invoice2.items:
- if invoice2_item.id in invoice2_matched_items:
- continue
- if invoice1_item.product == invoice2_item.product and invoice1_item.description == invoice2_item.description and invoice1_item.qty == invoice2_item.qty and invoice1_item.cost == invoice2_item.cost:
- invoice1_matched_items[invoice1_item.id] = True
- invoice2_matched_items[invoice2_item.id] = True
- matched_products += 1
- break
- if len(invoice1.items) == matched_products:
- return True
- return False
- def _create_invoice(self, registration):
- # Create Invoice
- invoice = Invoice()
- invoice.person = registration.person
- invoice.manual = False
- invoice.void = None
- # Loop over the registration products and add them to the invoice.
- for rproduct in registration.products:
- if self._product_available(rproduct.product, True, rproduct.qty):
- ii = InvoiceItem(description=rproduct.product.category.name + ' - ' + rproduct.product.description, qty=rproduct.qty, cost=rproduct.product.cost)
- ii.invoice = invoice # automatically appends ii to invoice.items
- ii.product = rproduct.product
- product_expires = rproduct.product.available_until()
- if product_expires is not None:
- if invoice.due_date is None or product_expires < invoice.due_date:
- invoice.due_date = product_expires
- meta.Session.add(ii)
- else:
- for ii in invoice.items:
- meta.Session.expunge(ii)
- meta.Session.expunge(invoice)
- raise ProductUnavailable(rproduct.product)
- # Some products might in turn include other products. So the same
- # product might be available multiple times. Work out all the
- # freebies first.
- included = {}
- included_products = {}
- freebies = {}
- for ii in invoice.items:
- if ii.product and ii.product.included:
- for iproduct in ii.product.included:
- if iproduct.include_category.id not in included:
- included[iproduct.include_category.id] = 0
- included[iproduct.include_category.id] += iproduct.include_qty
- freebies[iproduct.include_category.id] = 0
- included_products[iproduct.include_category.id] = iproduct.include_category
- prices = {}
- # Check for included products
- for ii in invoice.items:
- if ii.product and ii.product.category.id in included:
- if ii.product.category.id not in prices:
- prices[ii.product.category.id] = []
- if included[ii.product.category.id] >= ii.qty:
- prices[ii.product.category.id].append(ii.qty * ii.product.cost)
- ii.free_qty = ii.qty
- freebies[ii.product.category.id] += ii.qty
- included[ii.product.category.id] -= ii.qty
- elif included[ii.product.category.id] > 0:
- prices[ii.product.category.id].append(included[ii.product.category.id] * ii.product.cost)
- ii.free_qty = included[ii.product.category.id]
- freebies[ii.product.category.id] = included[ii.product.category.id]
- included[ii.product.category.id] = 0
- for freebie in freebies:
- free_cost = 0
- if freebie in prices:
- for price in prices[freebie]:
- free_cost += price
- # We have included products, create a discount for the cost of them.
- # This is not perfect, products of different prices can be discounted,
- # and it can either favor the customer or LCA, depending on the order
- # of items on the invoice
- if free_cost > 0:
- discount_item = InvoiceItem(description="Discount for " + str(freebies[freebie]) + " included " + included_products[freebie].name, qty=1, cost=-free_cost)
- invoice.items.append(discount_item)
- meta.Session.add(discount_item)
- meta.Session.add(invoice)
- return invoice
- def apply_voucher(self, invoice, voucher):
- # Voucher code calculation
- for vproduct in voucher.products:
- for ii in invoice.items:
- # if we have a category match
- if ii.product and ii.product.category == vproduct.product.category:
- # The qty we will give
- if ii.qty < vproduct.qty:
- qty = ii.qty
- else:
- qty = vproduct.qty
- # the discount we will give
- max_discount = vproduct.product.cost * vproduct.percentage / 100
- if ii.product.cost >= max_discount:
- discount = max_discount
- else:
- discount = ii.product.cost
- discount_item = InvoiceItem(description="Discount Voucher (" + voucher.comment + ") for " + vproduct.product.description, qty=qty, cost=-discount)
- meta.Session.add(discount_item)
- invoice.items.append(discount_item)
- break
- @authorize(h.auth.has_organiser_role)
- def index(self):
- per_page = 20
- from webhelpers import paginate #Upgrade to new paginate
- filter = dict(request.GET)
- filter['role'] = []
- for key,value in request.GET.items():
- if key == 'role': filter['role'].append(value)
- filter['product'] = []
- for key,value in request.GET.items():
- if key == 'product': filter['product'].append(value)
- """
- registration_list = self.dbsession.query(self.model).order_by(self.model.c.id)
- if filter.has_key('role'):
- role_name = filter['role']
- if role_name == 'speaker':
- registration_list = registration_list.select_from(registration_tables.registration.join(core_tables.person)).filter(model.Person.proposals.any(accepted='1'))
- elif role_name == 'miniconf':
- registration_list = registration_list.select_from(registration_tables.registration.join(core_tables.person)).filter(model.Person.proposals.any(accepted='1'))
- elif role_name == 'volunteer':
- pass
- elif role_name != 'all':
- registration_list = registration_list.select_from(registration_tables.registration.join(core_tables.person)).filter(model.Person.roles.any(name=role_name))
- """
- if (len(filter) in [2,3,4] and filter.has_key('per_page') and (len(filter['role']) == 0 or 'all' in filter['role']) and filter['status'] == 'all' and (len(filter['product']) == 0 or 'all' in filter['product'])) or len(filter) < 2:
- # no actual filters to apply besides per_page, so we can get paginate to do the query
- registration_list = meta.Session.query(Registration).order_by(Registration.id).all()
- else:
- import copy
- registration_list_full = Registration.find_all()
- registration_list = copy.copy(registration_list_full)
- for registration in registration_list_full:
- if 'speaker' in filter['role'] and registration.person.is_speaker() is not True:
- registration_list.remove(registration)
- elif 'miniconf' in filter['role'] and registration.person.is_miniconf_org() is not True:
- registration_list.remove(registration)
- elif 'volunteer' in filter['role'] and registration.person.is_volunteer() is not True:
- registration_list.remove(registration)
- elif (len(filter['role']) - len(set(filter['role']) & set(['all', 'speaker', 'miniconf', 'volunteer']))) != 0 and len(set(filter['role']) & set([role.name for role in registration.person.roles])) == 0:
- registration_list.remove(registration)
- elif filter.has_key('status') and filter['status'] == 'paid' and not registration.person.paid():
- registration_list.remove(registration)
- elif filter.has_key('status') and filter['status'] == 'unpaid' and registration.person.paid():
- registration_list.remove(registration)
- elif filter.has_key('diet') and filter['diet'] == 'true' and (registration.diet == '' or registration.diet.lower() in ('n/a', 'none', 'nill', 'nil', 'no')):
- registration_list.remove(registration)
- elif filter.has_key('special_needs') and filter['special_needs'] == 'true' and (registration.special == '' or registration.special.lower() in ('n/a', 'none', 'nill', 'nil', 'no')):
- registration_list.remove(registration)
- elif filter.has_key('notes') and filter['notes'] == 'true' and len(registration.notes) == 0:
- registration_list.remove(registration)
- elif filter.has_key('under18') and filter['under18'] == 'true' and registration.over18:
- registration_list.remove(registration)
- elif filter.has_key('voucher') and filter['voucher'] == 'true' and not registration.voucher:
- registration_list.remove(registration)
- elif filter.has_key('manual_invoice') and filter['manual_invoice'] == 'true' and not (True in [invoice.manual for invoice in registration.person.invoices]):
- registration_list.remove(registration)
- elif filter.has_key('not_australian') and filter['not_australian'] == 'true' and registration.person.country == "AUSTRALIA":
- registration_list.remove(registration)
- elif len(filter['product']) > 0 and 'all' not in filter['product']:
- # has to be done last as it is an OR not an AND
- valid_invoices = []
- for invoice in registration.person.invoices:
- if not invoice.is_void:
- valid_invoices.append(invoice)
- if len(set([int(id) for id in filter['product']]) & set([x for subL in [[item.product_id for item in invoice.items] for invoice in valid_invoices] for x in subL])) == 0:
- registration_list.remove(registration)
- if filter.has_key('export') and filter['export'] == 'true':
- return self._export_list(registration_list)
- if filter.has_key('per_page'):
- try:
- per_page = int(filter['per_page'])
- except:
- pass
- if filter.has_key('page'):
- page = int(filter['page'])
- else:
- page = 1
- setattr(c, 'per_page', per_page)
- pagination = paginate.Page(registration_list, per_page = per_page, page = page)
- setattr(c, 'registration_pages', pagination)
- setattr(c, 'registration_collection', pagination.items)
- setattr(c, 'registration_request', filter)
- setattr(c, 'roles', Role.find_all())
- setattr(c, 'product_categories', ProductCategory.find_all())
- return render('/registration/list.mako')
- def _export_list(self, registration_list):
- columns = ['Rego', 'Firstname', 'Lastname', 'Email', 'Nick', 'Company', 'State', 'Country', 'Valid Invoices', 'Paid for Products', 'Accommodation', 'Speaker', 'Miniconf Org', 'Volunteer', 'Role(s)', 'Diet', 'Special Needs', 'Silly Description', 'Over 18']
- if type(registration_list) is not list:
- registration_list = registration_list.all()
- data = []
- for registration in registration_list:
- products = []
- invoices = []
- accommodation = []
- for product in registration.products:
- if product.product.category.name.lower() == "accommodation":
- accommodation.append(product.product.description)
- for invoice in registration.person.invoices:
- if invoice.is_paid and not invoice.is_void:
- invoices.append(str(invoice.id))
- for item in invoice.items:
- products.append(str(item.qty) + "x" + item.description)
- # Hack to fix mising fields
- if not registration.nick:
- registration.nick = ''
- if not registration.silly_description:
- registration.silly_description = ''
- data.append([registration.id,
- registration.person.firstname.encode('utf-8'),
- registration.person.lastname.encode('utf-8'),
- registration.person.email_address.encode('utf-8'),
- registration.nick.encode('utf-8'),
- registration.person.company.encode('utf-8'),
- registration.person.state.encode('utf-8'),
- registration.person.country.encode('utf-8'),
- ", ".join(invoices).encode('utf-8'),
- ", ".join(products).encode('utf-8'),
- ", ".join(accommodation).encode('utf-8'),
- #registration.checkin,
- #registration.checkout,
- registration.person.is_speaker(),
- registration.person.is_miniconf_org(),
- registration.person.is_volunteer(),
- ", ".join([role.name for role in registration.person.roles]),
- registration.diet.encode('utf-8'),
- registration.special.encode('utf-8'),
- registration.silly_description.encode('utf-8'),
- registration.over18])
- import csv, StringIO
- f = StringIO.StringIO()
- w = csv.writer(f)
- w.writerow(columns)
- w.writerows(data)
- res = Response(f.getvalue())
- res.headers['Content-type']='text/plain; charset=utf-8'
- res.headers['Content-Disposition']='attachment; filename="table.csv"'
- return res
- @authorize(h.auth.has_organiser_role)
- def generate_badges(self):
- defaults = dict(request.POST)
- stamp = False
- if defaults.has_key('stamp') and defaults['stamp']:
- stamp = defaults['stamp']
- c.text = ''
- data = []
- if request.method == 'POST' and defaults:
- if defaults['reg_id'] != '':
- reg_id_list = defaults['reg_id'].split("\n")
- regos = [(r.person.lastname.lower(), r.person.firstname.lower(), r)
- for r in Registration.find_by_ids(reg_id_list)]
- regos.sort()
- registration_list = [row[-1] for row in regos]
- if len(registration_list) != len(reg_id_list):
- c.text = 'Registration ID not found. Please check the <a href="/registration">registration list</a>.'
- return render('registration/generate_badges.mako')
- else:
- for registration in registration_list:
- data.append(self._registration_badge_data(registration, stamp))
- registration.person.badge_printed = True
- else:
- regos = [(r.person.lastname.lower(), r.person.firstname.lower(), r)
- for r in Registration.find_all()]
- regos.sort()
- registration_list = [row[-1] for row in regos]
- for registration in registration_list:
- append = False
- if registration.person.has_paid_ticket() and not registration.person.badge_printed:
- if defaults['type'] == 'all':
- append = True
- else:
- for invoice in registration.person.invoices:
- if invoice.is_paid and not invoice.is_void:
- for item in invoice.items:
- if defaults['type'] == 'concession' and item.description.startswith('Concession'):
- append = True
- elif defaults['type'] == 'hobby' and (item.description.find('Hobbyist') > -1 or item.description.find('Hobbiest') > -1):
- append = True
- elif defaults['type'] == 'professional' and (item.description.find('Professional') > -1 or item.description.startswith('Karoro')):
- append = True
- elif defaults['type'] == 'press' and item.description.startswith('Press'):
- append = True
- elif defaults['type'] == 'organiser' and item.description.startswith('Organiser'):
- append = True
- elif defaults['type'] == 'monday_tuesday' and item.description.find('Monday + Tuesday') > -1:
- append = True
- if defaults['type'] == 'speaker' and registration.person.is_speaker():
- append = True
- elif defaults['type'] == 'mc_organiser' and registration.person.is_miniconf_org():
- append = True
- elif defaults['type'] == 'volunteer' and registration.person.is_volunteer():
- append = True
- if append:
- data.append(self._registration_badge_data(registration, stamp))
- registration.person.badge_printed = True
- meta.Session.commit() # save badge printed data
- setattr(c, 'data', data)
- import os, tempfile
- c.index = 0
- files = []
- while c.index < len(c.data):
- while c.index + 4 > len(c.data):
- c.data.append(self._registration_badge_data(False))
- res = render('registration/badges_svg.mako')
- (svg_fd, svg) = tempfile.mkstemp('.svg')
- svg_f = os.fdopen(svg_fd, 'w')
- svg_f.write(res)
- svg_f.close()
- files.append(svg)
- c.index += 4
- (tar_fd, tar) = tempfile.mkstemp('.tar.gz')
- os.close(tar_fd)
- os.system('tar -zcvf %s %s' % (tar, " ".join(files)))
- tar_f = file(tar)
- res = Response(tar_f.read())
- tar_f.close()
- res.headers['Content-type'] = 'application/octet-stream'
- res.headers['Content-Disposition'] = ( 'attachment; filename=badges.tar.gz' )
- return res
- return render('registration/generate_badges.mako')
- def _registration_badge_data(self, registration, stamp = False):
- if registration:
- dinner_tickets = 0
- speakers_tickets = 0
- breakfast = 0
- pdns_ticket = False
- ticket = ''
- for invoice in registration.person.invoices:
- if invoice.is_paid and n