PageRenderTime 29ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/zkpylons/controllers/registration.py

https://github.com/jhesketh/zookeepr
Python | 1039 lines | 1015 code | 22 blank | 2 comment | 16 complexity | 6f350d352d5d0e9e3e24ddba424c3e8a MD5 | raw file
  1. # coding=utf-8
  2. import logging
  3. import re
  4. from pylons import request, response, session, tmpl_context as c
  5. from zkpylons.lib.helpers import redirect_to
  6. from pylons.controllers.util import Response
  7. from pylons.decorators import validate
  8. from pylons.decorators.rest import dispatch_on
  9. from formencode import validators, htmlfill, Invalid
  10. from formencode.variabledecode import NestedVariables
  11. from zkpylons.lib.base import BaseController, render
  12. from zkpylons.lib.ssl_requirement import enforce_ssl
  13. from zkpylons.lib.validators import BaseSchema, DictSet, ProductInCategory, CheckboxQty
  14. from zkpylons.lib.validators import ProductQty, ProductMinMax, IAgreeValidator, CountryValidator
  15. # validators used from the database
  16. from zkpylons.lib.validators import ProDinner, PPDetails, PPChildrenAdult
  17. import zkpylons.lib.helpers as h
  18. from authkit.authorize.pylons_adaptors import authorize
  19. from authkit.permissions import ValidAuthKitUser
  20. from zkpylons.lib.mail import email
  21. from zkpylons.model import meta
  22. from zkpylons.model import Registration, Role, RegistrationProduct, Person
  23. from zkpylons.model import ProductCategory, Product, Voucher, Ceiling
  24. from zkpylons.model import Invoice, InvoiceItem
  25. from zkpylons.model.special_offer import SpecialOffer
  26. from zkpylons.model.special_registration import SpecialRegistration
  27. from zkpylons.config.lca_info import lca_info, lca_rego
  28. from zkpylons.controllers.person import PersonSchema
  29. log = logging.getLogger(__name__)
  30. import datetime
  31. class CheckAccomDates(validators.FormValidator):
  32. def __init__(self, checkin_name, checkout_name):
  33. super(self.__class__, self).__init__()
  34. self.checkin = checkin_name
  35. self.checkout = checkout_name
  36. def validate_python(self, values, state):
  37. if values[self.checkin] >= values[self.checkout]:
  38. error_dict = {
  39. self.checkin: "Your checkin date must be before your check out",
  40. self.checkout: "Your checkout date must be after your check in.",
  41. }
  42. raise Invalid(self.__class__.__name__, values, state, error_dict=error_dict)
  43. class VoucherValidator(validators.FancyValidator):
  44. def validate_python(self, value, state):
  45. voucher = Voucher.find_by_code(value)
  46. if voucher != None:
  47. if voucher.registration:
  48. if voucher.registration.person.id != h.signed_in_person().id:
  49. raise Invalid("Voucher code already in use!", value, state)
  50. elif value:
  51. raise Invalid("Unknown voucher code!", value, state)
  52. class SillyDescriptionChecksum(validators.FormValidator):
  53. def __init__(self, silly_name, checksum_name):
  54. super(self.__class__, self).__init__()
  55. self.__silly_name = silly_name
  56. self.__checksum_name = checksum_name
  57. def validate_python(self, values, state):
  58. silly_description = values.get(self.__silly_name, None)
  59. if silly_description is None:
  60. return
  61. checksum = h.silly_description_checksum(silly_description)
  62. if values.get(self.__checksum_name, None) != checksum:
  63. error_dict = {
  64. self.__silly_name: "Smart enough to hack the silly description, not smart enough to hack the checksum.",
  65. }
  66. raise Invalid(self.__class__.__name__, values, state, error_dict=error_dict)
  67. class OtherValidator(validators.String):
  68. def __init__(self, list, if_missing=None):
  69. super(self.__class__, self).__init__(if_missing=if_missing)
  70. self.__list = list
  71. def validate_python(self, value, state):
  72. if value is not None:
  73. for bad in self.__list:
  74. if bad in value.lower():
  75. raise Invalid(value + ' -- Nice try!', value, state)
  76. class ExistingPersonSchema(BaseSchema):
  77. company = validators.String()
  78. phone = validators.String()
  79. mobile = validators.String()
  80. address1 = validators.String(not_empty=True)
  81. address2 = validators.String()
  82. city = validators.String(not_empty=True)
  83. state = validators.String()
  84. postcode = validators.String(not_empty=True, min=3, max=10)
  85. country = CountryValidator(not_empty=True)
  86. i_agree = validators.Bool(if_missing=False)
  87. chained_validators = [IAgreeValidator("i_agree")]
  88. class RegistrationSchema(BaseSchema):
  89. over18 = validators.Int(min=0, max=1, not_empty=True)
  90. nick = validators.String(if_missing=None)
  91. shell = validators.String(if_missing=None)
  92. shelltext = OtherValidator(if_missing=None, list=('cmd', 'command'))
  93. editor = validators.String(if_missing=None)
  94. editortext = OtherValidator(if_missing=None, list=('word', 'write'))
  95. distro = validators.String(if_missing=None)
  96. distrotext = OtherValidator(if_missing=None, list=('window', 'xp', 'vista'))
  97. vcs = validators.String(if_missing=None)
  98. vcstext = OtherValidator(if_missing=None, list=('visual sourcesafe', 'bitkeeper'))
  99. silly_description = validators.String(if_missing=None)
  100. silly_description_checksum = validators.String(if_missing=None, strip=True)
  101. if lca_rego['pgp_collection'] != 'no':
  102. keyid = validators.String()
  103. planetfeed = validators.String(if_missing=None)
  104. voucher_code = VoucherValidator(if_empty=None)
  105. diet = validators.String()
  106. special = validators.String()
  107. signup = DictSet(if_missing=None)
  108. prevlca = DictSet(if_missing=None)
  109. chained_validators = [
  110. SillyDescriptionChecksum("silly_description", "silly_description_checksum"),
  111. ]
  112. class SpecialOfferSchema(BaseSchema):
  113. name = validators.String()
  114. member_number = validators.String()
  115. class NewRegistrationSchema(BaseSchema):
  116. person = ExistingPersonSchema()
  117. registration = RegistrationSchema()
  118. special_offer = SpecialOfferSchema()
  119. pre_validators = [NestedVariables]
  120. class ProductUnavailable(Exception):
  121. """Exception when a product isn't available
  122. Attributes:
  123. product -- the product that wasn't available
  124. """
  125. def __init__(self, product):
  126. self.product = product
  127. edit_schema = NewRegistrationSchema()
  128. class RegistrationController(BaseController):
  129. @enforce_ssl(required_all=True)
  130. @authorize(h.auth.is_activated_user)
  131. @authorize(h.auth.is_valid_user)
  132. def __before__(self, **kwargs):
  133. c.product_categories = ProductCategory.find_all()
  134. c.ceilings = {}
  135. for ceiling in Ceiling.find_all():
  136. c.ceilings[ceiling.name] = ceiling
  137. self._generate_product_schema()
  138. c.products = Product.find_all()
  139. c.product_available = self._product_available
  140. c.able_to_edit = self._able_to_edit
  141. c.manual_invoice = self.manual_invoice
  142. c.signed_in_person = h.signed_in_person()
  143. def _able_to_edit(self):
  144. for invoice in h.signed_in_person().invoices:
  145. if not invoice.is_void:
  146. if invoice.is_paid and invoice.total != 0:
  147. return False, "Sorry, you've already paid. Contact the team at " + h.lca_info['contact_email'] + " if you need anything changed."
  148. return True, "You can edit"
  149. def _product_available(self, product, stock=True, qty=0):
  150. # bool stock: care about if the product is in stock (ie sold out?)
  151. if not product.available(stock, qty):
  152. return False
  153. if product.auth is not None:
  154. exec("auth = " + product.auth)
  155. if not auth:
  156. return False
  157. return True
  158. def _generate_product_schema(self):
  159. # Since the form is arbitrarily defined by what product types there are, the validation
  160. # (aka schema) also needs to be dynamic.
  161. # Thus, this function generates a dynamic schema to validate a given set of products.
  162. #
  163. class ProductSchema(BaseSchema):
  164. # This schema is used to validate the products submitted by the
  165. # form. It is populated below
  166. # EG:
  167. # ProductSchema.add_field('count', validators.Int(min=1, max=100))
  168. # is the same as doing this inline:
  169. # count = validators.Int(min=1, max=100)
  170. #
  171. # 2009-05-07 Josh H: Not sure why, but there is a reason this
  172. # class is declaired within this method and not earlier on
  173. # like in the voucher controller. Or maybe I just did this
  174. # poorly...
  175. pass
  176. # placed here so prevalidator can refer to it. This means we need a hacky method to save it :S
  177. ProductSchema.add_field('partner_name', validators.String(if_missing=None))
  178. ProductSchema.add_field('partner_email', validators.Email(if_missing=None))
  179. ProductSchema.add_field('partner_mobile', validators.String(if_missing=None))
  180. # Go through each category and each product and add generic validation
  181. for category in c.product_categories:
  182. clean_cat_name = category.clean_name()
  183. if category.display in ('radio', 'select'):
  184. # min/max can't be calculated on this form. You should only have 1 selected.
  185. ProductSchema.add_field('category_' + clean_cat_name, ProductInCategory(category=category, not_empty=True))
  186. for product in category.products:
  187. if product.validate is not None:
  188. validator = eval(product.validate)
  189. validator.error_field_name = "error.%s" % category.clean_name()
  190. ProductSchema.add_chained_validator(validator)
  191. elif category.display == 'checkbox':
  192. product_fields = []
  193. for product in category.products:
  194. clean_prod_desc = product.clean_description()
  195. product_field_name = 'product_' + clean_cat_name + '_' + clean_prod_desc + '_checkbox'
  196. ProductSchema.add_field(product_field_name, CheckboxQty(product=product, if_missing=False))
  197. product_fields.append(product_field_name)
  198. if product.validate is not None:
  199. validator = eval(product.validate)
  200. validator.error_field_name = "error.%s" % category.clean_name()
  201. ProductSchema.add_chained_validator(validator)
  202. validator = self.min_max_validator(product_fields, category)
  203. ProductSchema.add_chained_validator(validator)
  204. elif category.display == 'qty':
  205. # qty
  206. product_fields = []
  207. for product in category.products:
  208. clean_prod_desc = product.clean_description()
  209. product_field_name = 'product_' + clean_cat_name + '_' + clean_prod_desc + '_qty'
  210. ProductSchema.add_field(product_field_name, ProductQty(product=product, if_missing=None))
  211. product_fields.append(product_field_name)
  212. if product.validate is not None:
  213. validator = eval(product.validate)
  214. validator.error_field_name = "error.%s" % category.clean_name()
  215. ProductSchema.add_chained_validator(validator)
  216. validator = self.min_max_validator(product_fields, category)
  217. ProductSchema.add_chained_validator(validator)
  218. edit_schema.add_field('products', ProductSchema)
  219. @classmethod
  220. def min_max_validator(cls, fields, category):
  221. return ProductMinMax(
  222. product_fields=fields,
  223. min_qty=category.min_qty,
  224. max_qty=category.max_qty,
  225. category_name=category.name,
  226. error_field_name="error.%s" % category.clean_name())
  227. def is_speaker(self):
  228. try:
  229. return c.signed_in_person.is_speaker()
  230. except:
  231. return False
  232. def is_miniconf_org(self):
  233. try:
  234. return c.signed_in_person.is_miniconf_org()
  235. except:
  236. return False
  237. def is_volunteer(self,product):
  238. try:
  239. 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
  240. except:
  241. return False
  242. def is_role(self, role):
  243. try:
  244. return c.signed_in_person.has_role(role)
  245. except:
  246. return False
  247. def is_same_person(self):
  248. return c.signed_in_person == c.registration.person
  249. @dispatch_on(POST="_new")
  250. def new(self):
  251. c.signed_in_person = h.signed_in_person()
  252. h.check_for_incomplete_profile(c.signed_in_person)
  253. if c.signed_in_person and c.signed_in_person.registration:
  254. redirect_to(action='edit', id=c.signed_in_person.registration.id)
  255. fields = dict(request.GET)
  256. c.special_offer = None
  257. if 'offer' in fields:
  258. c.special_offer = SpecialOffer.find_by_name(fields['offer'])
  259. if c.special_offer is not None:
  260. if c.special_offer.enabled:
  261. # Create the special_registration record
  262. special_registration = SpecialRegistration()
  263. special_registration.special_offer_id = c.special_offer.id
  264. special_registration.member_number = '' # TODO: switch to None
  265. special_registration.person_id = c.signed_in_person.id
  266. meta.Session.add(special_registration)
  267. meta.Session.commit()
  268. else:
  269. c.special_offer = None
  270. else:
  271. # The user alreay has used a special URL to register
  272. if c.signed_in_person.special_registration:
  273. c.special_offer = c.signed_in_person.special_registration[0].special_offer
  274. if c.special_offer is None and lca_info['conference_status'] is not 'open':
  275. if not h.auth.authorized(h.auth.has_organiser_role):
  276. redirect_to(action='status')
  277. else:
  278. # User is an organiser, so if the status is also 'debug' then they can register
  279. if lca_info['conference_status'] is not 'debug':
  280. redirect_to(action='status')
  281. defaults = {}
  282. if h.lca_rego['personal_info']['home_address'] == 'no':
  283. defaults['person.address1'] = 'not available'
  284. defaults['person.city'] = 'not available'
  285. defaults['person.postcode'] = 'not available'
  286. if c.signed_in_person:
  287. for k in ['address1', 'address2', 'city', 'state', 'postcode', 'country', 'phone', 'mobile', 'company', 'i_agree']:
  288. v = getattr(c.signed_in_person, k)
  289. if v is not None:
  290. defaults['person.' + k] = getattr(c.signed_in_person, k)
  291. defaults['registration.signup.announce'] = 1
  292. defaults['registration.checkin'] = 17
  293. defaults['registration.checkout'] = 24
  294. #
  295. # Fugly hack. If we aren't booking accommodation, then default the
  296. # product bought for Accommodation to 'I will organise my own'.
  297. #
  298. category = ProductCategory.find_by_name("Accommodation")
  299. if category and (len(category.products) == 0 or (len(category.products) == 1 and category.products[0].cost == 0)):
  300. field_name = 'products.category_%s' % category.name.replace("-","_")
  301. defaults[field_name] = category.products[0].id
  302. # Hacker-proof silly_description field
  303. c.silly_description, checksum = h.silly_description()
  304. defaults['registration.silly_description'] = c.silly_description
  305. defaults['registration.silly_description_checksum'] = checksum
  306. form = render("/registration/new.mako")
  307. return htmlfill.render(form, defaults)
  308. @validate(schema=edit_schema, form='new', post_only=True, on_get=True, variable_decode=True)
  309. def _new(self):
  310. if c.signed_in_person and c.signed_in_person.registration:
  311. redirect_to(action='_edit', id=c.signed_in_person.registration.id)
  312. result = self.form_result
  313. c.special_offer = None
  314. if 'special_offer' in result:
  315. c.special_offer = SpecialOffer.find_by_name(result['special_offer']['name'])
  316. if c.special_offer is not None and not c.special_offer.enabled:
  317. c.special_offer = None
  318. if c.special_offer is None and lca_info['conference_status'] is not 'open':
  319. if not h.auth.authorized(h.auth.has_organiser_role):
  320. redirect_to(action='status')
  321. else:
  322. # User is an organiser, so if the status is also 'debug' then they can register
  323. if lca_info['conference_status'] is not 'debug':
  324. redirect_to(action='status')
  325. # A blank registration
  326. c.registration = Registration()
  327. self.save_details(result)
  328. c.student_ticket = False
  329. c.infants = False
  330. c.children = False
  331. c.pp_children = False
  332. for rproduct in c.registration.products:
  333. if 'Ticket' in rproduct.product.category.name and 'Student' in rproduct.product.description:
  334. c.student_ticket = True
  335. elif 'Dinner' in rproduct.product.category.name:
  336. if 'Infant' == rproduct.product.description:
  337. c.infants = True
  338. elif 'Child' == rproduct.product.description:
  339. c.children = True
  340. elif rproduct.product.category.name == 'Partners Programme' and ('Child' in rproduct.product.description or 'Infant' in rproduct.product.description):
  341. c.pp_children = True
  342. email(
  343. c.person.email_address,
  344. render('registration/response.mako'))
  345. self.pay(c.registration.id, quiet=1)
  346. h.flash("Thank you for your registration!")
  347. if not c.person.paid():
  348. h.flash("To complete the registration process, please pay your invoice.")
  349. redirect_to(action='status')
  350. @dispatch_on(POST="_edit")
  351. def edit(self, id):
  352. if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
  353. # Raise a no_auth error
  354. h.auth.no_role()
  355. # If we're an organiser, then don't check payment status.
  356. if not h.auth.authorized(h.auth.has_organiser_role):
  357. able, response = self._able_to_edit()
  358. if not able:
  359. c.error = response
  360. return render("/registration/error.mako")
  361. c.registration = Registration.find_by_id(id)
  362. defaults = {}
  363. defaults.update(h.object_to_defaults(c.registration, 'registration'))
  364. defaults.update(h.object_to_defaults(c.registration.person, 'person'))
  365. for rproduct in c.registration.products:
  366. product = rproduct.product
  367. category_name = product.category.clean_name()
  368. if product.category.display == 'checkbox':
  369. if product.available():
  370. defaults['products.product_' + category_name + '_' + product.clean_description() + '_checkbox'] = '1'
  371. elif product.category.display == 'qty':
  372. if product.available() and rproduct.qty > 0:
  373. defaults['products.product_' + category_name + '_' + product.clean_description() + '_qty'] = rproduct.qty
  374. else:
  375. if product.available():
  376. defaults['products.category_' + category_name] = product.id
  377. # generate new silly description
  378. c.silly_description, checksum = h.silly_description()
  379. defaults['registration.silly_description'] = c.silly_description
  380. defaults['registration.silly_description_checksum'] = checksum
  381. # the partner fields are attached to the product in the form for some reason
  382. defaults['products.partner_name'] = c.registration.partner_name
  383. defaults['products.partner_email'] = c.registration.partner_email
  384. defaults['products.partner_mobile'] = c.registration.partner_mobile
  385. if c.registration.over18:
  386. defaults['registration.over18'] = 1
  387. else:
  388. defaults['registration.over18'] = 0
  389. if c.registration.shell in lca_rego['shells'] or c.registration.shell == '':
  390. defaults['registration.shell'] = c.registration.shell
  391. else:
  392. defaults['registration.shell'] = 'other'
  393. defaults['registration.shelltext'] = c.registration.shell
  394. if c.registration.editor in lca_rego['editors'] or c.registration.editor == '':
  395. defaults['registration.editor'] = c.registration.editor
  396. else:
  397. defaults['registration.editor'] = 'other'
  398. defaults['registration.editortext'] = c.registration.editor
  399. if c.registration.distro in lca_rego['distros'] or c.registration.distro == '':
  400. defaults['registration.distro'] = c.registration.distro
  401. else:
  402. defaults['registration.distro'] = 'other'
  403. defaults['registration.distrotext'] = c.registration.distro
  404. if c.registration.vcs in lca_rego['vcses']:
  405. defaults['registration.vcs'] = c.registration.vcs
  406. else:
  407. defaults['registration.vcs'] = 'other'
  408. defaults['registration.vcstext'] = c.registration.vcs
  409. form = render('/registration/edit.mako')
  410. if c.form_errors:
  411. return form
  412. else:
  413. return htmlfill.render(form, defaults)
  414. @validate(schema=edit_schema, form='edit', post_only=True, on_get=True, variable_decode=True)
  415. def _edit(self, id):
  416. # We need to recheck auth in here so we can pass in the id
  417. if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
  418. # Raise a no_auth error
  419. h.auth.no_role()
  420. c.registration = Registration.find_by_id(id)
  421. result = self.form_result
  422. c.special_offer = None
  423. self.save_details(result)
  424. self.pay(c.registration.id, quiet=1)
  425. h.flash("Thank you for updating your registration!")
  426. if not c.person.paid():
  427. #h.flash("To complete the registration process, please pay your invoice.")
  428. redirect_to(controller='registration', action='pay', id=c.registration.id)
  429. redirect_to(action='status')
  430. def save_details(self, result):
  431. # Store Registration details
  432. for k in result['registration']:
  433. if k in ('shell', 'editor', 'distro', 'vcs'):
  434. if result['registration'][k] == 'other':
  435. setattr(c.registration, k, result['registration'][k + 'text'])
  436. else:
  437. setattr(c.registration, k, result['registration'][k])
  438. else:
  439. setattr(c.registration, k, result['registration'][k])
  440. if result['registration']['over18'] == 1:
  441. setattr(c.registration, 'over18', True)
  442. else:
  443. setattr(c.registration, 'over18', False)
  444. # hacky method to make validating sane
  445. setattr(c.registration, 'partner_name', result['products']['partner_name'])
  446. setattr(c.registration, 'partner_email', result['products']['partner_email'])
  447. setattr(c.registration, 'partner_mobile', result['products']['partner_mobile'])
  448. # Check whether we're already signed in or not, and store person details
  449. if c.registration.person:
  450. c.person = c.registration.person
  451. else:
  452. c.person = c.signed_in_person
  453. for k in result['person']:
  454. setattr(c.person, k, result['person'][k])
  455. # Create person<->registration relationship
  456. c.registration.person = c.person
  457. # Deal with the special offer if any
  458. if c.special_offer is not None:
  459. special_registration = SpecialRegistration.find_by_person_and_offer(c.person.id, c.special_offer.id)
  460. special_registration.member_number = result['special_offer']['member_number']
  461. meta.Session.add(special_registration)
  462. # Always delete the current products
  463. c.registration.products = []
  464. # Store Product details
  465. for category in c.product_categories:
  466. clean_cat_name = category.clean_name()
  467. if category.display in ('radio', 'select'):
  468. #product = Product.find_by_cat_and_desc(category.id, result['products']['category_' + clean_cat_name])
  469. product = Product.find_by_id(result['products']['category_' + clean_cat_name])
  470. if product != None:
  471. rego_product = RegistrationProduct()
  472. rego_product.registration = c.registration
  473. rego_product.product = product
  474. #if product.category.name == 'Accommodation':
  475. # rego_product.qty = c.registration.checkout - c.registration.checkin
  476. #else:
  477. # rego_product.qty = 1
  478. rego_product.qty = 1
  479. c.registration.products.append(rego_product)
  480. elif category.display == 'checkbox':
  481. for product in category.products:
  482. clean_prod_desc = product.clean_description()
  483. if result['products']['product_' + clean_cat_name + '_' + clean_prod_desc + '_checkbox']:
  484. rego_product = RegistrationProduct()
  485. rego_product.registration = c.registration
  486. rego_product.product = product
  487. rego_product.qty = 1
  488. c.registration.products.append(rego_product)
  489. elif category.display == 'qty':
  490. for product in category.products:
  491. clean_prod_desc = product.clean_description()
  492. if result['products']['product_' + clean_cat_name + '_' + clean_prod_desc + '_qty'] not in [0, None]:
  493. rego_product = RegistrationProduct()
  494. rego_product.registration = c.registration
  495. rego_product.product = product
  496. rego_product.qty = result['products']['product_' + clean_cat_name + '_' + clean_prod_desc + '_qty']
  497. c.registration.products.append(rego_product)
  498. meta.Session.commit()
  499. def status(self, id=0):
  500. if int(id) == 0:
  501. if h.signed_in_person() and h.signed_in_person().registration:
  502. c.registration = h.signed_in_person().registration
  503. else:
  504. c.registration = None
  505. else:
  506. if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
  507. # Raise a no_auth error
  508. h.auth.no_role()
  509. c.registration = Registration.find_by_id(id, abort_404 = False)
  510. if c.registration is None:
  511. c.person = h.signed_in_person()
  512. else:
  513. c.person = c.registration.person
  514. return render("/registration/status.mako")
  515. def pay(self, id, quiet=0):
  516. if not h.auth.authorized(h.auth.Or(h.auth.is_same_zookeepr_registration(id), h.auth.has_organiser_role)):
  517. # Raise a no_auth error
  518. h.auth.no_role()
  519. registration = Registration.find_by_id(id)
  520. # Checks all existing invoices and invalidates them if a product is not available
  521. self.check_invoices(registration.person.invoices)
  522. # If we have a manual invoice, don't try and re-generate invoices
  523. if not self.manual_invoice(registration.person.invoices):
  524. try:
  525. invoice = self._create_invoice(registration)
  526. except ProductUnavailable, inst:
  527. if quiet: return
  528. c.product = inst.product
  529. c.registration = registration
  530. return render('/registration/product_unavailable.mako')
  531. if registration.voucher:
  532. self.apply_voucher(invoice, registration.voucher)
  533. # complicated check to see whether invoice is already in the system
  534. new_invoice = invoice
  535. for old_invoice in registration.person.invoices:
  536. if old_invoice != new_invoice and not old_invoice.manual and not old_invoice.is_void:
  537. if self.invoices_identical(old_invoice, new_invoice):
  538. invoice = old_invoice
  539. if not quiet:
  540. redirect_to(controller='invoice', action='view', id=invoice.id)
  541. meta.Session.rollback()
  542. return
  543. if old_invoice.due_date < new_invoice.due_date:
  544. new_invoice.due_date = old_invoice.due_date
  545. old_invoice.void = "Registration Change"
  546. invoice.last_modification_timestamp = datetime.datetime.now()
  547. meta.Session.commit()
  548. if quiet: return
  549. redirect_to(controller='invoice', action='view', id=invoice.id)
  550. else:
  551. redirect_to(action='status')
  552. def check_invoices(self, invoices):
  553. for invoice in invoices:
  554. if not invoice.is_void and not invoice.manual and not invoice.is_paid:
  555. for ii in invoice.items:
  556. if ii.product and not self._product_available(ii.product, True, ii.qty):
  557. invoice.void = "Product " + ii.product.category.name + " - " + ii.product.description + " is no longer available"
  558. meta.Session.commit()
  559. def manual_invoice(self, invoices):
  560. for invoice in invoices:
  561. if not invoice.is_void and invoice.manual:
  562. return True
  563. return False
  564. def invoices_identical(self, invoice1, invoice2):
  565. if invoice1.total == invoice2.total:
  566. if len(invoice1.items) == len(invoice2.items):
  567. matched_products = 0
  568. invoice1_matched_items = dict()
  569. invoice2_matched_items = dict()
  570. for invoice1_item in invoice1.items:
  571. if invoice1_item.id in invoice1_matched_items:
  572. continue
  573. for invoice2_item in invoice2.items:
  574. if invoice2_item.id in invoice2_matched_items:
  575. continue
  576. 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:
  577. invoice1_matched_items[invoice1_item.id] = True
  578. invoice2_matched_items[invoice2_item.id] = True
  579. matched_products += 1
  580. break
  581. if len(invoice1.items) == matched_products:
  582. return True
  583. return False
  584. def _create_invoice(self, registration):
  585. # Create Invoice
  586. invoice = Invoice()
  587. invoice.person = registration.person
  588. invoice.manual = False
  589. invoice.void = None
  590. # Loop over the registration products and add them to the invoice.
  591. for rproduct in registration.products:
  592. if self._product_available(rproduct.product, True, rproduct.qty):
  593. ii = InvoiceItem(description=rproduct.product.category.name + ' - ' + rproduct.product.description, qty=rproduct.qty, cost=rproduct.product.cost)
  594. ii.invoice = invoice # automatically appends ii to invoice.items
  595. ii.product = rproduct.product
  596. product_expires = rproduct.product.available_until()
  597. if product_expires is not None:
  598. if invoice.due_date is None or product_expires < invoice.due_date:
  599. invoice.due_date = product_expires
  600. meta.Session.add(ii)
  601. else:
  602. for ii in invoice.items:
  603. meta.Session.expunge(ii)
  604. meta.Session.expunge(invoice)
  605. raise ProductUnavailable(rproduct.product)
  606. # Some products might in turn include other products. So the same
  607. # product might be available multiple times. Work out all the
  608. # freebies first.
  609. included = {}
  610. included_products = {}
  611. freebies = {}
  612. for ii in invoice.items:
  613. if ii.product and ii.product.included:
  614. for iproduct in ii.product.included:
  615. if iproduct.include_category.id not in included:
  616. included[iproduct.include_category.id] = 0
  617. included[iproduct.include_category.id] += iproduct.include_qty
  618. freebies[iproduct.include_category.id] = 0
  619. included_products[iproduct.include_category.id] = iproduct.include_category
  620. prices = {}
  621. # Check for included products
  622. for ii in invoice.items:
  623. if ii.product and ii.product.category.id in included:
  624. if ii.product.category.id not in prices:
  625. prices[ii.product.category.id] = []
  626. if included[ii.product.category.id] >= ii.qty:
  627. prices[ii.product.category.id].append(ii.qty * ii.product.cost)
  628. ii.free_qty = ii.qty
  629. freebies[ii.product.category.id] += ii.qty
  630. included[ii.product.category.id] -= ii.qty
  631. elif included[ii.product.category.id] > 0:
  632. prices[ii.product.category.id].append(included[ii.product.category.id] * ii.product.cost)
  633. ii.free_qty = included[ii.product.category.id]
  634. freebies[ii.product.category.id] = included[ii.product.category.id]
  635. included[ii.product.category.id] = 0
  636. for freebie in freebies:
  637. free_cost = 0
  638. if freebie in prices:
  639. for price in prices[freebie]:
  640. free_cost += price
  641. # We have included products, create a discount for the cost of them.
  642. # This is not perfect, products of different prices can be discounted,
  643. # and it can either favor the customer or LCA, depending on the order
  644. # of items on the invoice
  645. if free_cost > 0:
  646. discount_item = InvoiceItem(description="Discount for " + str(freebies[freebie]) + " included " + included_products[freebie].name, qty=1, cost=-free_cost)
  647. invoice.items.append(discount_item)
  648. meta.Session.add(discount_item)
  649. meta.Session.add(invoice)
  650. return invoice
  651. def apply_voucher(self, invoice, voucher):
  652. # Voucher code calculation
  653. for vproduct in voucher.products:
  654. for ii in invoice.items:
  655. # if we have a category match
  656. if ii.product and ii.product.category == vproduct.product.category:
  657. # The qty we will give
  658. if ii.qty < vproduct.qty:
  659. qty = ii.qty
  660. else:
  661. qty = vproduct.qty
  662. # the discount we will give
  663. max_discount = vproduct.product.cost * vproduct.percentage / 100
  664. if ii.product.cost >= max_discount:
  665. discount = max_discount
  666. else:
  667. discount = ii.product.cost
  668. discount_item = InvoiceItem(description="Discount Voucher (" + voucher.comment + ") for " + vproduct.product.description, qty=qty, cost=-discount)
  669. meta.Session.add(discount_item)
  670. invoice.items.append(discount_item)
  671. break
  672. @authorize(h.auth.has_organiser_role)
  673. def index(self):
  674. per_page = 20
  675. from webhelpers import paginate #Upgrade to new paginate
  676. filter = dict(request.GET)
  677. filter['role'] = []
  678. for key,value in request.GET.items():
  679. if key == 'role': filter['role'].append(value)
  680. filter['product'] = []
  681. for key,value in request.GET.items():
  682. if key == 'product': filter['product'].append(value)
  683. """
  684. registration_list = self.dbsession.query(self.model).order_by(self.model.c.id)
  685. if filter.has_key('role'):
  686. role_name = filter['role']
  687. if role_name == 'speaker':
  688. registration_list = registration_list.select_from(registration_tables.registration.join(core_tables.person)).filter(model.Person.proposals.any(accepted='1'))
  689. elif role_name == 'miniconf':
  690. registration_list = registration_list.select_from(registration_tables.registration.join(core_tables.person)).filter(model.Person.proposals.any(accepted='1'))
  691. elif role_name == 'volunteer':
  692. pass
  693. elif role_name != 'all':
  694. registration_list = registration_list.select_from(registration_tables.registration.join(core_tables.person)).filter(model.Person.roles.any(name=role_name))
  695. """
  696. 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:
  697. # no actual filters to apply besides per_page, so we can get paginate to do the query
  698. registration_list = meta.Session.query(Registration).order_by(Registration.id).all()
  699. else:
  700. import copy
  701. registration_list_full = Registration.find_all()
  702. registration_list = copy.copy(registration_list_full)
  703. for registration in registration_list_full:
  704. if 'speaker' in filter['role'] and registration.person.is_speaker() is not True:
  705. registration_list.remove(registration)
  706. elif 'miniconf' in filter['role'] and registration.person.is_miniconf_org() is not True:
  707. registration_list.remove(registration)
  708. elif 'volunteer' in filter['role'] and registration.person.is_volunteer() is not True:
  709. registration_list.remove(registration)
  710. 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:
  711. registration_list.remove(registration)
  712. elif filter.has_key('status') and filter['status'] == 'paid' and not registration.person.paid():
  713. registration_list.remove(registration)
  714. elif filter.has_key('status') and filter['status'] == 'unpaid' and registration.person.paid():
  715. registration_list.remove(registration)
  716. elif filter.has_key('diet') and filter['diet'] == 'true' and (registration.diet == '' or registration.diet.lower() in ('n/a', 'none', 'nill', 'nil', 'no')):
  717. registration_list.remove(registration)
  718. 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')):
  719. registration_list.remove(registration)
  720. elif filter.has_key('notes') and filter['notes'] == 'true' and len(registration.notes) == 0:
  721. registration_list.remove(registration)
  722. elif filter.has_key('under18') and filter['under18'] == 'true' and registration.over18:
  723. registration_list.remove(registration)
  724. elif filter.has_key('voucher') and filter['voucher'] == 'true' and not registration.voucher:
  725. registration_list.remove(registration)
  726. elif filter.has_key('manual_invoice') and filter['manual_invoice'] == 'true' and not (True in [invoice.manual for invoice in registration.person.invoices]):
  727. registration_list.remove(registration)
  728. elif filter.has_key('not_australian') and filter['not_australian'] == 'true' and registration.person.country == "AUSTRALIA":
  729. registration_list.remove(registration)
  730. elif len(filter['product']) > 0 and 'all' not in filter['product']:
  731. # has to be done last as it is an OR not an AND
  732. valid_invoices = []
  733. for invoice in registration.person.invoices:
  734. if not invoice.is_void:
  735. valid_invoices.append(invoice)
  736. 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:
  737. registration_list.remove(registration)
  738. if filter.has_key('export') and filter['export'] == 'true':
  739. return self._export_list(registration_list)
  740. if filter.has_key('per_page'):
  741. try:
  742. per_page = int(filter['per_page'])
  743. except:
  744. pass
  745. if filter.has_key('page'):
  746. page = int(filter['page'])
  747. else:
  748. page = 1
  749. setattr(c, 'per_page', per_page)
  750. pagination = paginate.Page(registration_list, per_page = per_page, page = page)
  751. setattr(c, 'registration_pages', pagination)
  752. setattr(c, 'registration_collection', pagination.items)
  753. setattr(c, 'registration_request', filter)
  754. setattr(c, 'roles', Role.find_all())
  755. setattr(c, 'product_categories', ProductCategory.find_all())
  756. return render('/registration/list.mako')
  757. def _export_list(self, registration_list):
  758. 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']
  759. if type(registration_list) is not list:
  760. registration_list = registration_list.all()
  761. data = []
  762. for registration in registration_list:
  763. products = []
  764. invoices = []
  765. accommodation = []
  766. for product in registration.products:
  767. if product.product.category.name.lower() == "accommodation":
  768. accommodation.append(product.product.description)
  769. for invoice in registration.person.invoices:
  770. if invoice.is_paid and not invoice.is_void:
  771. invoices.append(str(invoice.id))
  772. for item in invoice.items:
  773. products.append(str(item.qty) + "x" + item.description)
  774. # Hack to fix mising fields
  775. if not registration.nick:
  776. registration.nick = ''
  777. if not registration.silly_description:
  778. registration.silly_description = ''
  779. data.append([registration.id,
  780. registration.person.firstname.encode('utf-8'),
  781. registration.person.lastname.encode('utf-8'),
  782. registration.person.email_address.encode('utf-8'),
  783. registration.nick.encode('utf-8'),
  784. registration.person.company.encode('utf-8'),
  785. registration.person.state.encode('utf-8'),
  786. registration.person.country.encode('utf-8'),
  787. ", ".join(invoices).encode('utf-8'),
  788. ", ".join(products).encode('utf-8'),
  789. ", ".join(accommodation).encode('utf-8'),
  790. #registration.checkin,
  791. #registration.checkout,
  792. registration.person.is_speaker(),
  793. registration.person.is_miniconf_org(),
  794. registration.person.is_volunteer(),
  795. ", ".join([role.name for role in registration.person.roles]),
  796. registration.diet.encode('utf-8'),
  797. registration.special.encode('utf-8'),
  798. registration.silly_description.encode('utf-8'),
  799. registration.over18])
  800. import csv, StringIO
  801. f = StringIO.StringIO()
  802. w = csv.writer(f)
  803. w.writerow(columns)
  804. w.writerows(data)
  805. res = Response(f.getvalue())
  806. res.headers['Content-type']='text/plain; charset=utf-8'
  807. res.headers['Content-Disposition']='attachment; filename="table.csv"'
  808. return res
  809. @authorize(h.auth.has_organiser_role)
  810. def generate_badges(self):
  811. defaults = dict(request.POST)
  812. stamp = False
  813. if defaults.has_key('stamp') and defaults['stamp']:
  814. stamp = defaults['stamp']
  815. c.text = ''
  816. data = []
  817. if request.method == 'POST' and defaults:
  818. if defaults['reg_id'] != '':
  819. reg_id_list = defaults['reg_id'].split("\n")
  820. regos = [(r.person.lastname.lower(), r.person.firstname.lower(), r)
  821. for r in Registration.find_by_ids(reg_id_list)]
  822. regos.sort()
  823. registration_list = [row[-1] for row in regos]
  824. if len(registration_list) != len(reg_id_list):
  825. c.text = 'Registration ID not found. Please check the <a href="/registration">registration list</a>.'
  826. return render('registration/generate_badges.mako')
  827. else:
  828. for registration in registration_list:
  829. data.append(self._registration_badge_data(registration, stamp))
  830. registration.person.badge_printed = True
  831. else:
  832. regos = [(r.person.lastname.lower(), r.person.firstname.lower(), r)
  833. for r in Registration.find_all()]
  834. regos.sort()
  835. registration_list = [row[-1] for row in regos]
  836. for registration in registration_list:
  837. append = False
  838. if registration.person.has_paid_ticket() and not registration.person.badge_printed:
  839. if defaults['type'] == 'all':
  840. append = True
  841. else:
  842. for invoice in registration.person.invoices:
  843. if invoice.is_paid and not invoice.is_void:
  844. for item in invoice.items:
  845. if defaults['type'] == 'concession' and item.description.startswith('Concession'):
  846. append = True
  847. elif defaults['type'] == 'hobby' and (item.description.find('Hobbyist') > -1 or item.description.find('Hobbiest') > -1):
  848. append = True
  849. elif defaults['type'] == 'professional' and (item.description.find('Professional') > -1 or item.description.startswith('Karoro')):
  850. append = True
  851. elif defaults['type'] == 'press' and item.description.startswith('Press'):
  852. append = True
  853. elif defaults['type'] == 'organiser' and item.description.startswith('Organiser'):
  854. append = True
  855. elif defaults['type'] == 'monday_tuesday' and item.description.find('Monday + Tuesday') > -1:
  856. append = True
  857. if defaults['type'] == 'speaker' and registration.person.is_speaker():
  858. append = True
  859. elif defaults['type'] == 'mc_organiser' and registration.person.is_miniconf_org():
  860. append = True
  861. elif defaults['type'] == 'volunteer' and registration.person.is_volunteer():
  862. append = True
  863. if append:
  864. data.append(self._registration_badge_data(registration, stamp))
  865. registration.person.badge_printed = True
  866. meta.Session.commit() # save badge printed data
  867. setattr(c, 'data', data)
  868. import os, tempfile
  869. c.index = 0
  870. files = []
  871. while c.index < len(c.data):
  872. while c.index + 4 > len(c.data):
  873. c.data.append(self._registration_badge_data(False))
  874. res = render('registration/badges_svg.mako')
  875. (svg_fd, svg) = tempfile.mkstemp('.svg')
  876. svg_f = os.fdopen(svg_fd, 'w')
  877. svg_f.write(res)
  878. svg_f.close()
  879. files.append(svg)
  880. c.index += 4
  881. (tar_fd, tar) = tempfile.mkstemp('.tar.gz')
  882. os.close(tar_fd)
  883. os.system('tar -zcvf %s %s' % (tar, " ".join(files)))
  884. tar_f = file(tar)
  885. res = Response(tar_f.read())
  886. tar_f.close()
  887. res.headers['Content-type'] = 'application/octet-stream'
  888. res.headers['Content-Disposition'] = ( 'attachment; filename=badges.tar.gz' )
  889. return res
  890. return render('registration/generate_badges.mako')
  891. def _registration_badge_data(self, registration, stamp = False):
  892. if registration:
  893. dinner_tickets = 0
  894. speakers_tickets = 0
  895. breakfast = 0
  896. pdns_ticket = False
  897. ticket = ''
  898. for invoice in registration.person.invoices:
  899. if invoice.is_paid and n