git subrepo pull vendor/registrasion
subrepo: subdir: "vendor/registrasion" merged: "3545a80" upstream: origin: "git@gitlab.com:tchaypo/registrasion.git" branch: "lca2018" commit: "3545a80" git-subrepo: version: "0.3.1" origin: "???" commit: "???"
This commit is contained in:
		
							parent
							
								
									19e4185cd9
								
							
						
					
					
						commit
						3001324d5e
					
				
					 11 changed files with 773 additions and 69 deletions
				
			
		
							
								
								
									
										4
									
								
								vendor/registrasion/.gitrepo
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/registrasion/.gitrepo
									
										
									
									
										vendored
									
									
								
							|  | @ -6,6 +6,6 @@ | ||||||
| [subrepo] | [subrepo] | ||||||
| 	remote = git@gitlab.com:tchaypo/registrasion.git | 	remote = git@gitlab.com:tchaypo/registrasion.git | ||||||
| 	branch = lca2018 | 	branch = lca2018 | ||||||
| 	commit = 7cf314adae9f8de1519db63828f55a10aa09f0ee | 	commit = 3545a809e8e14014963c670709b6d0273c0e354a | ||||||
| 	parent = 7c5c6c02f20ddcbc6c54b3065311880fce586192 | 	parent = 19e4185cd9433c8f743c32dde8aee09455db3982 | ||||||
| 	cmdver = 0.3.1 | 	cmdver = 0.3.1 | ||||||
|  |  | ||||||
							
								
								
									
										386
									
								
								vendor/registrasion/registrasion/contrib/badger.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								vendor/registrasion/registrasion/contrib/badger.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,386 @@ | ||||||
|  | ''' | ||||||
|  | Generate Conference Badges | ||||||
|  | ========================== | ||||||
|  | 
 | ||||||
|  | Nearly all of the code in this was written by Richard Jones for the 2016 conference. | ||||||
|  | That code relied on the user supplying the attendee data in a CSV file, which Richard's | ||||||
|  | code then processed. | ||||||
|  | 
 | ||||||
|  | The main (and perhaps only real) difference, here, is that the attendee data are taken | ||||||
|  | directly from the database.  No CSV file is required. | ||||||
|  | 
 | ||||||
|  | This is now a library with functions / classes referenced by the generate_badges | ||||||
|  | management command, and by the tickets/badger and tickets/badge API functions. | ||||||
|  | ''' | ||||||
|  | import sys | ||||||
|  | import os | ||||||
|  | import csv | ||||||
|  | from lxml import etree | ||||||
|  | import tempfile | ||||||
|  | from copy import deepcopy | ||||||
|  | import subprocess | ||||||
|  | 
 | ||||||
|  | import pdb | ||||||
|  | 
 | ||||||
|  | from django.core.management.base import BaseCommand | ||||||
|  | 
 | ||||||
|  | from django.contrib.auth.models import User, Group | ||||||
|  | from pinaxcon.registrasion.models import AttendeeProfile | ||||||
|  | from registrasion.controllers.cart import CartController | ||||||
|  | from registrasion.controllers.invoice import InvoiceController | ||||||
|  | from registrasion.models import Voucher | ||||||
|  | from registrasion.models import Attendee | ||||||
|  | from registrasion.models import Product | ||||||
|  | from registrasion.models import Invoice | ||||||
|  | from symposion.speakers.models import Speaker | ||||||
|  | 
 | ||||||
|  | # A few unicode encodings ... | ||||||
|  | GLYPH_PLUS = '+' | ||||||
|  | GLYPH_GLASS = u'\ue001' | ||||||
|  | GLYPH_DINNER = u'\ue179' | ||||||
|  | GLYPH_SPEAKER = u'\ue122' | ||||||
|  | GLYPH_SPRINTS = u'\ue254' | ||||||
|  | GLYPH_CROWN = u'\ue211' | ||||||
|  | GLYPH_SNOWMAN = u'\u2603' | ||||||
|  | GLYPH_STAR = u'\ue007' | ||||||
|  | GLYPH_FLASH = u'\ue162' | ||||||
|  | GLYPH_EDU = u'\ue233' | ||||||
|  | 
 | ||||||
|  | # Some company names are too long to fit on the badge, so, we | ||||||
|  | # define abbreviations here. | ||||||
|  | overrides = { | ||||||
|  |  "Optiver Pty. Ltd.": "Optiver", | ||||||
|  |  "IRESS Market Tech": "IRESS", | ||||||
|  |  "The Bureau of Meteorology": "BoM", | ||||||
|  |  "Google Australia": "Google", | ||||||
|  |  "Facebook Inc.": "Facebook", | ||||||
|  |  "Rhapsody Solutions Pty Ltd": "Rhapsody Solutions", | ||||||
|  |  "PivotNine Pty Ltd": "PivotNine", | ||||||
|  |  "SEEK Ltd.": "SEEK", | ||||||
|  |  "UNSW Australia": "UNSW", | ||||||
|  |  "Dev Demand Co": "Dev Demand", | ||||||
|  |  "Cascode Labs Pty Ltd": "Cascode Labs", | ||||||
|  |  "CyberHound Pty Ltd": "CyberHound", | ||||||
|  |  "Self employed Contractor": "", | ||||||
|  |  "Data Processors Pty Lmt": "Data Processors", | ||||||
|  |  "Bureau of Meterology": "BoM", | ||||||
|  |  "Google Australia Pty Ltd": "Google", | ||||||
|  |  # "NSW Rural Doctors Network": "", | ||||||
|  |  "Sense of Security Pty Ltd": "Sense of Security", | ||||||
|  |  "Hewlett Packard Enterprose": "HPE", | ||||||
|  |  "Hewlett Packard Enterprise": "HPE", | ||||||
|  |  "CISCO SYSTEMS INDIA PVT LTD": "CISCO", | ||||||
|  |  "The University of Melbourne": "University of Melbourne", | ||||||
|  |  "Peter MacCallum Cancer Centre": "Peter Mac", | ||||||
|  |  "Commonwealth Bank of Australia": "CBA", | ||||||
|  |  "VLSCI, University of Melbourne": "VLSCI", | ||||||
|  |  "Australian Bureau of Meteorology": "BoM", | ||||||
|  |  "Bureau of Meteorology": "BoM", | ||||||
|  |  "Australian Synchrotron | ANSTO": "Australian Synchrotron", | ||||||
|  |  "Bureau of Meteorology, Australia": "BoM", | ||||||
|  |  "QUT Digital Media Research Centre": "QUT", | ||||||
|  |  "Dyn - Dynamic Network Services Inc": "Dyn", | ||||||
|  |  "The Australian National University": "ANU", | ||||||
|  |  "Murdoch Childrens Research Institute": "MCRI", | ||||||
|  |  "Centenary Institute, University of Sydney": "Centenary Institute", | ||||||
|  |  "Synchrotron Light Source Australia Pty Ltd": "Australian Synchrotron", | ||||||
|  |  "Australian Communication and Media Authority": "ACMA", | ||||||
|  |  "Dept. of Education - Camden Haven High School": "Camden Haven High School", | ||||||
|  |  "Australian Government - Bureau of Meteorology": "BoM", | ||||||
|  |  "The Walter and Eliza Hall Institute of Medical Research": "WEHI", | ||||||
|  |  "Dept. Parliamentary Services, Australian Parliamentary Library": "Dept. Parliamentary Services", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def text_size(text, prev=9999): | ||||||
|  |     ''' | ||||||
|  |     Calculate the length of a text string as it relates to font size. | ||||||
|  |     ''' | ||||||
|  |     n = len(text) | ||||||
|  |     size = int(min(48, max(28, 28 + 30 * (1 - (n-8) / 11.)))) | ||||||
|  |     return min(prev, size) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def set_text(soup, text_id, text, resize=None): | ||||||
|  |     ''' | ||||||
|  |     Set the text value of an element (via beautiful soup calls). | ||||||
|  |     ''' | ||||||
|  |     elem = soup.find(".//*[@id='%s']/{http://www.w3.org/2000/svg}tspan" % text_id) | ||||||
|  |     if elem is None: | ||||||
|  |         raise ValueError('could not find tag id=%s' % text_id) | ||||||
|  |     elem.text = text | ||||||
|  |     if resize: | ||||||
|  |         style = elem.get('style') | ||||||
|  |         elem.set('style', style.replace('font-size:60px', 'font-size:%dpx' % resize)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def set_colour(soup, slice_id, colour): | ||||||
|  |     ''' | ||||||
|  |     Set colour of an element (using beautiful soup calls). | ||||||
|  |     ''' | ||||||
|  |     elem = soup.find(".//*[@id='%s']" % slice_id) | ||||||
|  |     if elem is None: | ||||||
|  |         raise ValueError('could not find tag id=%s' % slice_id) | ||||||
|  |     style = elem.get('style') | ||||||
|  |     elem.set('style', style.replace('fill:#316a9a', 'fill:#%s' % colour)) | ||||||
|  | 
 | ||||||
|  | Volunteers = Group.objects.filter(name='Conference volunteers').first().user_set.all() | ||||||
|  | Organisers = Group.objects.filter(name='Conference organisers').first().user_set.all() | ||||||
|  | 
 | ||||||
|  | def is_volunteer(attendee): | ||||||
|  |     ''' | ||||||
|  |     Returns True if attendee is in the Conference volunteers group. | ||||||
|  |     False otherwise. | ||||||
|  |     ''' | ||||||
|  |     return attendee.user in Volunteers | ||||||
|  | 
 | ||||||
|  | def is_organiser(attendee): | ||||||
|  |     ''' | ||||||
|  |     Returns True if attendee is in the Conference volunteers group. | ||||||
|  |     False otherwise. | ||||||
|  |     ''' | ||||||
|  |     return attendee.user in Organisers | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def svg_badge(soup, data, n): | ||||||
|  |     ''' | ||||||
|  |     Do the actual "heavy lifting" to create the badge SVG | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     # Python2/3 compat ... | ||||||
|  |     try: | ||||||
|  |         xx = filter(None, [1, 2, None, 3])[2] | ||||||
|  |         filter_None = lambda lst: filter(None, lst) | ||||||
|  |     except (TypeError,): | ||||||
|  |         filter_None = lambda lst: list(filter(None, lst)) | ||||||
|  | 
 | ||||||
|  |     side = 'lr'[n] | ||||||
|  |     for tb in 'tb': | ||||||
|  |         part = tb + side | ||||||
|  |         lines = [data['firstname'], data['lastname']] | ||||||
|  |         if data['promote_company']: | ||||||
|  |             lines.append(data['company']) | ||||||
|  |         lines.extend([data['line1'], data['line2']]) | ||||||
|  |         lines = filter_None(lines)[:4] | ||||||
|  | 
 | ||||||
|  |         lines.extend('' for n in range(4-len(lines))) | ||||||
|  |         prev = 9999 | ||||||
|  |         for m, line in enumerate(lines): | ||||||
|  |             size = text_size(line, prev) | ||||||
|  |             set_text(soup, 'line-%s-%s' % (part, m), line, size) | ||||||
|  |             prev = size | ||||||
|  | 
 | ||||||
|  |         lines = [] | ||||||
|  |         if data['organiser']: | ||||||
|  |             lines.append('Organiser') | ||||||
|  |             set_colour(soup, 'colour-' + part, '319a51') | ||||||
|  |         elif data['volunteer']: | ||||||
|  |             lines.append('Volunteer') | ||||||
|  |             set_colour(soup, 'colour-' + part, '319a51') | ||||||
|  |         if data['speaker']: | ||||||
|  |             lines.append('Speaker') | ||||||
|  | 
 | ||||||
|  |         special = bool(lines) | ||||||
|  | 
 | ||||||
|  |         if 'Friday Only' in data['ticket']: | ||||||
|  |             # lines.append('Friday Only') | ||||||
|  |             set_colour(soup, 'colour-' + part, 'a83f3f') | ||||||
|  | 
 | ||||||
|  |         if 'Contributor' in data['ticket']: | ||||||
|  |             lines.append('Contributor') | ||||||
|  |         elif 'Professional' in data['ticket'] and not data['organiser']: | ||||||
|  |             lines.append('Professional') | ||||||
|  |         elif 'Sponsor' in data['ticket'] and not data['organiser']: | ||||||
|  |             lines.append('Sponsor') | ||||||
|  |         elif 'Enthusiast' in data['ticket'] and not data['organiser']: | ||||||
|  |             lines.append('Enthusiast') | ||||||
|  |         elif data['ticket'] == 'Speaker' and not data['speaker']: | ||||||
|  |             lines.append('Speaker') | ||||||
|  |         elif not special: | ||||||
|  |             if data['ticket']: | ||||||
|  |                 lines.append(data['ticket']) | ||||||
|  |             elif data['friday']: | ||||||
|  |                 lines.append('Friday Only') | ||||||
|  |                 set_colour(soup, 'colour-' + part, 'a83f3f') | ||||||
|  |             else: | ||||||
|  |                 lines.append('Tutorial Only') | ||||||
|  |                 set_colour(soup, 'colour-' + part, 'a83f3f') | ||||||
|  | 
 | ||||||
|  |         if data['friday'] and data['ticket'] and not data['organiser']: | ||||||
|  |             lines.append('Fri, Sat and Sun') | ||||||
|  |             if not data['volunteer']: | ||||||
|  |                 set_colour(soup, 'colour-' + part, '71319a') | ||||||
|  | 
 | ||||||
|  |         if len(lines) > 3: | ||||||
|  |             raise ValueError('lines = %s' % (lines,)) | ||||||
|  | 
 | ||||||
|  |         for n in range(3 - len(lines)): | ||||||
|  |             lines.insert(0, '') | ||||||
|  |         for m, line in enumerate(lines): | ||||||
|  |             size = text_size(line) | ||||||
|  |             set_text(soup, 'tags-%s-%s' % (part, m), line, size) | ||||||
|  | 
 | ||||||
|  |         icons = [] | ||||||
|  |         if data['sprints']: | ||||||
|  |             icons.append(GLYPH_SPRINTS) | ||||||
|  |         if data['tutorial']: | ||||||
|  |             icons.append(GLYPH_EDU) | ||||||
|  | 
 | ||||||
|  |         set_text(soup, 'icons-' + part, ' '.join(icons)) | ||||||
|  |         set_text(soup, 'shirt-' + side, '; '.join(data['shirts'])) | ||||||
|  |         set_text(soup, 'email-' + side, data['email']) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def collate(options): | ||||||
|  |     # If specific usernames were given on the command line, just use those. | ||||||
|  |     # Otherwise, use the entire list of attendees. | ||||||
|  |     users = User.objects.filter(invoice__status=Invoice.STATUS_PAID) | ||||||
|  |     if options['usernames']: | ||||||
|  |         users = users.filter(username__in=options['usernames']) | ||||||
|  | 
 | ||||||
|  |     # Iterate through the attendee list to generate the badges. | ||||||
|  |     for n, user in enumerate(users.distinct()): | ||||||
|  |         ap = user.attendee.attendeeprofilebase.attendeeprofile | ||||||
|  |         data = dict() | ||||||
|  | 
 | ||||||
|  |         at_nm = ap.name.split() | ||||||
|  |         if at_nm[0].lower() in 'mr dr ms mrs miss'.split(): | ||||||
|  |             at_nm[0] = at_nm[0] + ' ' + at_nm[1] | ||||||
|  |             del at_nm[1] | ||||||
|  |         if at_nm: | ||||||
|  |             data['firstname'] = at_nm[0] | ||||||
|  |             data['lastname'] = ' '.join(at_nm[1:]) | ||||||
|  |         else: | ||||||
|  |             print ("ERROR:", ap.attendee.user, 'has no name') | ||||||
|  |             continue | ||||||
|  | 
 | ||||||
|  |         data['line1'] = ap.free_text_1 | ||||||
|  |         data['line2'] = ap.free_text_2 | ||||||
|  | 
 | ||||||
|  |         data['email'] = user.email | ||||||
|  |         data['over18'] = ap.of_legal_age | ||||||
|  |         speaker = Speaker.objects.filter(user=user).first() | ||||||
|  |         if speaker is None: | ||||||
|  |             data['speaker'] = False | ||||||
|  |         else: | ||||||
|  |             data['speaker'] = bool(speaker.proposals.filter(result__status='accepted')) | ||||||
|  | 
 | ||||||
|  |         data['paid'] = data['friday'] = data['sprints'] = data['tutorial'] = False | ||||||
|  |         data['shirts'] = [] | ||||||
|  |         data['ticket'] = '' | ||||||
|  | 
 | ||||||
|  |         # look over all the invoices, yes | ||||||
|  |         for inv in Invoice.objects.filter(user_id=ap.attendee.user.id): | ||||||
|  |             if not inv.is_paid: | ||||||
|  |                 continue | ||||||
|  |             cart = inv.cart | ||||||
|  |             if cart is None: | ||||||
|  |                 continue | ||||||
|  |             data['paid'] = True | ||||||
|  |             if cart.productitem_set.filter(product__category__name__startswith="Specialist Day").exists(): | ||||||
|  |                 data['friday'] = True | ||||||
|  |             if cart.productitem_set.filter(product__category__name__startswith="Sprint Ticket").exists(): | ||||||
|  |                 data['sprints'] = True | ||||||
|  |             if cart.productitem_set.filter(product__category__name__contains="Tutorial").exists(): | ||||||
|  |                 data['tutorial'] = True | ||||||
|  |             t = cart.productitem_set.filter(product__category__name__startswith="Conference Ticket") | ||||||
|  |             if t.exists(): | ||||||
|  |                 product = t.first().product.name | ||||||
|  |                 if 'SOLD OUT' not in product: | ||||||
|  |                     data['ticket'] = product | ||||||
|  |             elif cart.productitem_set.filter(product__category__name__contains="Specialist Day Only").exists(): | ||||||
|  |                 data['ticket'] = 'Specialist Day Only' | ||||||
|  | 
 | ||||||
|  |             data['shirts'].extend(ts.product.name for ts in cart.productitem_set.filter( | ||||||
|  |                 product__category__name__startswith="T-Shirt")) | ||||||
|  | 
 | ||||||
|  |         if not data['paid']: | ||||||
|  |             print ("INFO:", ap.attendee.user, 'not paid!') | ||||||
|  |             continue | ||||||
|  | 
 | ||||||
|  |         if not data['ticket'] and not (data['friday'] or data['tutorial']): | ||||||
|  |             print ("ERROR:", ap.attendee.user, 'no conference ticket!') | ||||||
|  |             continue | ||||||
|  | 
 | ||||||
|  |         data['company'] = overrides.get(ap.company, ap.company).strip() | ||||||
|  | 
 | ||||||
|  |         data['volunteer'] = is_volunteer(ap.attendee) | ||||||
|  |         data['organiser'] = is_organiser(ap.attendee) | ||||||
|  | 
 | ||||||
|  |         if 'Specialist Day Only' in data['ticket']: | ||||||
|  |             data['ticket'] = 'Friday Only' | ||||||
|  | 
 | ||||||
|  |         if 'Conference Organiser' in data['ticket']: | ||||||
|  |             data['ticket'] = '' | ||||||
|  | 
 | ||||||
|  |         if 'Conference Volunteer' in data['ticket']: | ||||||
|  |             data['ticket'] = '' | ||||||
|  | 
 | ||||||
|  |         data['promote_company'] = ( | ||||||
|  |             data['organiser'] or data['volunteer'] or data['speaker'] or | ||||||
|  |             'Sponsor' in data['ticket'] or | ||||||
|  |             'Contributor' in data['ticket'] or | ||||||
|  |             'Professional' in data['ticket'] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         yield data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def generate_stats(options): | ||||||
|  |     stats = { | ||||||
|  |         'firstname': [], | ||||||
|  |         'lastname': [], | ||||||
|  |         'company': [], | ||||||
|  |     } | ||||||
|  |     for badge in collate(options): | ||||||
|  |         stats['firstname'].append((len(badge['firstname']), badge['firstname'])) | ||||||
|  |         stats['lastname'].append((len(badge['lastname']), badge['lastname'])) | ||||||
|  |         if badge['promote_company']: | ||||||
|  |             stats['company'].append((len(badge['company']), badge['company'])) | ||||||
|  | 
 | ||||||
|  |     stats['firstname'].sort() | ||||||
|  |     stats['lastname'].sort() | ||||||
|  |     stats['company'].sort() | ||||||
|  | 
 | ||||||
|  |     for l, s in stats['firstname']: | ||||||
|  |         print ('%2d %s' % (l, s)) | ||||||
|  |     for l, s in stats['lastname']: | ||||||
|  |         print ('%2d %s' % (l, s)) | ||||||
|  |     for l, s in stats['company']: | ||||||
|  |         print ('%2d %s' % (l, s)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def generate_badges(options): | ||||||
|  |     names = list() | ||||||
|  | 
 | ||||||
|  |     orig = etree.parse(options['template']) | ||||||
|  |     tree = deepcopy(orig) | ||||||
|  |     root = tree.getroot() | ||||||
|  | 
 | ||||||
|  |     for n, data in enumerate(collate(options)): | ||||||
|  |         svg_badge(root, data, n % 2) | ||||||
|  |         if n % 2: | ||||||
|  |             name = os.path.abspath( | ||||||
|  |                 os.path.join(options['out_dir'], 'badge-%d.svg' % n)) | ||||||
|  |             tree.write(name) | ||||||
|  |             names.append(name) | ||||||
|  |             tree = deepcopy(orig) | ||||||
|  |             root = tree.getroot() | ||||||
|  | 
 | ||||||
|  |     if not n % 2: | ||||||
|  |         name = os.path.abspath( | ||||||
|  |             os.path.join(options['out_dir'], 'badge-%d.svg' % n)) | ||||||
|  |         tree.write(name) | ||||||
|  |         names.append(name) | ||||||
|  | 
 | ||||||
|  |     return 0 | ||||||
|  | 
 | ||||||
|  | class InvalidTicketChoiceError(Exception): | ||||||
|  |     ''' | ||||||
|  |     Exception thrown when they chosen ticket isn't valid.  This | ||||||
|  |     happens either if the ticket choice is 0 (default: Chose a ticket), | ||||||
|  |     or is greater than the index if the last ticket choice in the | ||||||
|  |     dropdown list. | ||||||
|  |     ''' | ||||||
|  |     def __init__(self, message="Please choose a VALID ticket."): | ||||||
|  |         super(InvalidTicketChoiceError, self).__init__(message,) | ||||||
|  | @ -9,7 +9,7 @@ import datetime | ||||||
| import functools | import functools | ||||||
| import itertools | import itertools | ||||||
| 
 | 
 | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| from django.db import transaction | from django.db import transaction | ||||||
| from django.db.models import Max | from django.db.models import Max | ||||||
|  | @ -64,6 +64,12 @@ class CartController(object): | ||||||
|                 time_last_updated=timezone.now(), |                 time_last_updated=timezone.now(), | ||||||
|                 reservation_duration=datetime.timedelta(), |                 reservation_duration=datetime.timedelta(), | ||||||
|             ) |             ) | ||||||
|  |         except MultipleObjectsReturned: | ||||||
|  |             # Get the one that looks "newest". | ||||||
|  |             existing = commerce.Cart.objects.filter( | ||||||
|  |                 user=user, | ||||||
|  |                 status=commerce.Cart.STATUS_ACTIVE, | ||||||
|  |             ).order_by('-time_last_updated').first() | ||||||
|         return cls(existing) |         return cls(existing) | ||||||
| 
 | 
 | ||||||
|     def _fail_if_cart_is_not_active(self): |     def _fail_if_cart_is_not_active(self): | ||||||
|  |  | ||||||
							
								
								
									
										57
									
								
								vendor/registrasion/registrasion/forms.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										57
									
								
								vendor/registrasion/registrasion/forms.py
									
										
									
									
										vendored
									
									
								
							|  | @ -263,7 +263,7 @@ class _CheckboxProductsForm(_ProductsForm): | ||||||
|     def set_fields(cls, category, products): |     def set_fields(cls, category, products): | ||||||
|         for product in products: |         for product in products: | ||||||
|             field = forms.BooleanField( |             field = forms.BooleanField( | ||||||
|                 label='%s -- %s' % (product.name, product.price), |                 label='%s -- $%s' % (product.name, product.price), | ||||||
|                 required=False, |                 required=False, | ||||||
|             ) |             ) | ||||||
|             cls.base_fields[cls.field_name(product)] = field |             cls.base_fields[cls.field_name(product)] = field | ||||||
|  | @ -521,3 +521,58 @@ class InvoiceEmailForm(InvoicesWithProductAndStatusForm): | ||||||
|         choices=ACTION_CHOICES, |         choices=ACTION_CHOICES, | ||||||
|         initial=ACTION_PREVIEW, |         initial=ACTION_PREVIEW, | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | from registrasion.contrib.badger import InvalidTicketChoiceError | ||||||
|  | 
 | ||||||
|  | def ticket_selection(): | ||||||
|  |    return list(enumerate(['!!! NOT A VALID TICKET !!!'] + \ | ||||||
|  |                          [p.name for p in inventory.Product.objects.\ | ||||||
|  |                             filter(category__name__contains="Ticket").\ | ||||||
|  |                             exclude(name__contains="Organiser").order_by('id')])) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TicketSelectionField(forms.ChoiceField): | ||||||
|  | 
 | ||||||
|  |     def validate(self, value): | ||||||
|  |         super(TicketSelectionField, self).validate(value) | ||||||
|  | 
 | ||||||
|  |         result = int(self.to_python(value)) | ||||||
|  |         if result <= 0 or result > len(list(self.choices)): | ||||||
|  |             raise InvalidTicketChoiceError() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BadgeForm(forms.Form): | ||||||
|  |     ''' | ||||||
|  |     A form for creating one-off badges at rego desk. | ||||||
|  |     ''' | ||||||
|  |     required_css_class = 'label-required' | ||||||
|  | 
 | ||||||
|  |     name = forms.CharField(label="Name", max_length=60, required=True) | ||||||
|  |     email = forms.EmailField(label="Email", max_length=60, required=False) | ||||||
|  |     company = forms.CharField(label="Company", max_length=60, required=False) | ||||||
|  |     free_text_1 = forms.CharField(label="Free Text", max_length=60, required=False) | ||||||
|  |     free_text_2 = forms.CharField(label="Free Text", max_length=60, required=False) | ||||||
|  | 
 | ||||||
|  |     ticket = TicketSelectionField(label="Select a Ticket", choices=ticket_selection) | ||||||
|  | 
 | ||||||
|  |     paid = forms.BooleanField(label="Paid", required=False) | ||||||
|  |     over18 = forms.BooleanField(label="Over 18", required=False) | ||||||
|  |     speaker = forms.BooleanField(label="Speaker", required=False) | ||||||
|  |     tutorial = forms.BooleanField(label="Tutorial Ticket", required=False) | ||||||
|  |     friday = forms.BooleanField(label="Specialist Day", required=False) | ||||||
|  |     sprints = forms.BooleanField(label="Sprints", required=False) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def is_valid(self): | ||||||
|  |         valid = super(BadgeForm, self).is_valid() | ||||||
|  | 
 | ||||||
|  |         if not valid: | ||||||
|  |             return valid | ||||||
|  | 
 | ||||||
|  |         if self.data['ticket'] == '0':  # Invalid ticket type! | ||||||
|  |             self.add_error('ticket', 'Please select a VALID ticket type.') | ||||||
|  |             return False | ||||||
|  | 
 | ||||||
|  |         return True | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								vendor/registrasion/registrasion/generate_badges.py
									
										
									
									
										vendored
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								vendor/registrasion/registrasion/generate_badges.py
									
										
									
									
										vendored
									
									
										Symbolic link
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | ../../../website/pinaxcon/registrasion/management/commands/generate_badges.py | ||||||
|  | @ -43,7 +43,7 @@ class Attendee(models.Model): | ||||||
|         db_index=True, |         db_index=True, | ||||||
|     ) |     ) | ||||||
|     completed_registration = models.BooleanField(default=False) |     completed_registration = models.BooleanField(default=False) | ||||||
|     guided_categories_complete = models.ManyToManyField("category") |     guided_categories_complete = models.ManyToManyField("category", blank=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AttendeeProfileBase(models.Model): | class AttendeeProfileBase(models.Model): | ||||||
|  |  | ||||||
|  | @ -75,6 +75,8 @@ class _ReportTemplateWrapper(object): | ||||||
|     def rows(self): |     def rows(self): | ||||||
|         return self.report.rows(self.content_type) |         return self.report.rows(self.content_type) | ||||||
| 
 | 
 | ||||||
|  |     def count(self): | ||||||
|  |         return self.report.count() | ||||||
| 
 | 
 | ||||||
| class BasicReport(Report): | class BasicReport(Report): | ||||||
| 
 | 
 | ||||||
|  | @ -118,6 +120,9 @@ class ListReport(BasicReport): | ||||||
|                 for i, cell in enumerate(row) |                 for i, cell in enumerate(row) | ||||||
|             ] |             ] | ||||||
| 
 | 
 | ||||||
|  |     def count(self): | ||||||
|  |         return len(self._data) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class QuerysetReport(BasicReport): | class QuerysetReport(BasicReport): | ||||||
| 
 | 
 | ||||||
|  | @ -158,6 +163,9 @@ class QuerysetReport(BasicReport): | ||||||
|             ] |             ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     def count(self): | ||||||
|  |         return self._queryset.count() | ||||||
|  | 
 | ||||||
| class Links(Report): | class Links(Report): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, title, links): |     def __init__(self, title, links): | ||||||
|  | @ -182,6 +190,8 @@ class Links(Report): | ||||||
|                 self._linked_text(content_type, url, link_text) |                 self._linked_text(content_type, url, link_text) | ||||||
|             ] |             ] | ||||||
| 
 | 
 | ||||||
|  |     def count(self): | ||||||
|  |         return len(self._links) | ||||||
| 
 | 
 | ||||||
| def report_view(title, form_type=None): | def report_view(title, form_type=None): | ||||||
|     ''' Decorator that converts a report view function into something that |     ''' Decorator that converts a report view function into something that | ||||||
|  |  | ||||||
							
								
								
									
										195
									
								
								vendor/registrasion/registrasion/reporting/views.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										195
									
								
								vendor/registrasion/registrasion/reporting/views.py
									
										
									
									
										vendored
									
									
								
							|  | @ -13,10 +13,12 @@ from django.db.models import F, Q | ||||||
| from django.db.models import Count, Max, Sum | from django.db.models import Count, Max, Sum | ||||||
| from django.db.models import Case, When, Value | from django.db.models import Case, When, Value | ||||||
| from django.db.models.fields.related import RelatedField | from django.db.models.fields.related import RelatedField | ||||||
|  | from django.db.models.fields import CharField | ||||||
| from django.shortcuts import render | from django.shortcuts import render | ||||||
| 
 | 
 | ||||||
| from registrasion.controllers.cart import CartController | from registrasion.controllers.cart import CartController | ||||||
| from registrasion.controllers.item import ItemController | from registrasion.controllers.item import ItemController | ||||||
|  | from registrasion.models import conditions | ||||||
| from registrasion.models import commerce | from registrasion.models import commerce | ||||||
| from registrasion.models import people | from registrasion.models import people | ||||||
| from registrasion import util | from registrasion import util | ||||||
|  | @ -30,6 +32,8 @@ from .reports import ListReport | ||||||
| from .reports import QuerysetReport | from .reports import QuerysetReport | ||||||
| from .reports import report_view | from .reports import report_view | ||||||
| 
 | 
 | ||||||
|  | import bleach | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def CURRENCY(): | def CURRENCY(): | ||||||
|     return models.DecimalField(decimal_places=2) |     return models.DecimalField(decimal_places=2) | ||||||
|  | @ -240,6 +244,83 @@ def group_by_cart_status(queryset, order, values): | ||||||
|     return values |     return values | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @report_view("Limits") | ||||||
|  | def limits(request, form): | ||||||
|  |     ''' Shows the summary of sales against stock limits. ''' | ||||||
|  | 
 | ||||||
|  |     line_items = commerce.ProductItem.objects.filter( | ||||||
|  |         cart__status=commerce.Invoice.STATUS_PAID, | ||||||
|  |     ).values( | ||||||
|  |         "product", "product__name", | ||||||
|  |     ).annotate( | ||||||
|  |         total_quantity=Sum("quantity") | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     quantities = collections.defaultdict(int) | ||||||
|  |     for line_item in line_items.all(): | ||||||
|  |         quantities[line_item['product__name']] += line_item['total_quantity'] | ||||||
|  | 
 | ||||||
|  |     limits = conditions.TimeOrStockLimitFlag.objects.all().order_by("-limit") | ||||||
|  | 
 | ||||||
|  |     headings = ["Product", "Quantity"] | ||||||
|  | 
 | ||||||
|  |     reports = [] | ||||||
|  |     for limit in limits: | ||||||
|  |         data = [] | ||||||
|  |         total = 0 | ||||||
|  |         for product in limit.products.all(): | ||||||
|  |             if product.name in quantities: | ||||||
|  |                 total += quantities[product.name] | ||||||
|  |                 data.append([product.name, quantities[product.name]]) | ||||||
|  |         if limit.limit: | ||||||
|  |             data.append(['(TOTAL)', '%s/%s' % (total, limit.limit)]) | ||||||
|  |         else: | ||||||
|  |             data.append(['(TOTAL)', total]) | ||||||
|  | 
 | ||||||
|  |         description = limit.description | ||||||
|  |         extras = [] | ||||||
|  |         if limit.start_time: | ||||||
|  |             extras.append('Starts: %s' % (limit.start_time)) | ||||||
|  | 
 | ||||||
|  |         if limit.end_time: | ||||||
|  |             extras.append('Ends: %s' % (limit.end_time)) | ||||||
|  | 
 | ||||||
|  |         if extras: | ||||||
|  |             description += ' (' + ', '.join(extras) + ')' | ||||||
|  | 
 | ||||||
|  |         reports.append(ListReport(description, headings, data)) | ||||||
|  | 
 | ||||||
|  |     # now get discount items | ||||||
|  |     discount_items = conditions.DiscountBase.objects.select_subclasses() | ||||||
|  | 
 | ||||||
|  |     data = [] | ||||||
|  |     for discount in discount_items.all(): | ||||||
|  |         quantity = 0 | ||||||
|  |         for item in discount.discountitem_set.filter(cart__status=2): | ||||||
|  |             quantity += item.quantity | ||||||
|  | 
 | ||||||
|  |         description = discount.description | ||||||
|  |         extras = [] | ||||||
|  |         if getattr(discount, 'start_time', None): | ||||||
|  |             extras.append('Starts: %s' % (discount.start_time)) | ||||||
|  | 
 | ||||||
|  |         if getattr(discount, 'end_time', None): | ||||||
|  |             extras.append('Ends: %s' % (discount.end_time)) | ||||||
|  | 
 | ||||||
|  |         if extras: | ||||||
|  |             description += ' (' + ', '.join(extras) + ')' | ||||||
|  | 
 | ||||||
|  |         if getattr(discount, 'limit', None): | ||||||
|  |             data.append([description, '%s/%s' % (quantity, discount.limit)]) | ||||||
|  |         else: | ||||||
|  |             data.append([description, quantity]) | ||||||
|  | 
 | ||||||
|  |     headings = ["Discount", "Quantity"] | ||||||
|  |     reports.append(ListReport('Discounts', headings, data)) | ||||||
|  | 
 | ||||||
|  |     return reports | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @report_view("Product status", form_type=forms.ProductAndCategoryForm) | @report_view("Product status", form_type=forms.ProductAndCategoryForm) | ||||||
| def product_status(request, form): | def product_status(request, form): | ||||||
|     ''' Summarises the inventory status of the given items, grouping by |     ''' Summarises the inventory status of the given items, grouping by | ||||||
|  | @ -329,7 +410,7 @@ def product_line_items(request, form): | ||||||
|         "user", |         "user", | ||||||
|         "user__attendee", |         "user__attendee", | ||||||
|         "user__attendee__attendeeprofilebase" |         "user__attendee__attendeeprofilebase" | ||||||
|     ).order_by("issue_time") |     ).order_by("issue_time").distinct() | ||||||
| 
 | 
 | ||||||
|     headings = [ |     headings = [ | ||||||
|         'Invoice', 'Invoice Date', 'Attendee', 'Qty', 'Product', 'Status' |         'Invoice', 'Invoice Date', 'Attendee', 'Qty', 'Product', 'Status' | ||||||
|  | @ -386,7 +467,7 @@ def paid_invoices_by_date(request, form): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     # Zero-value invoices will have no payments, so they're paid at issue time |     # Zero-value invoices will have no payments, so they're paid at issue time | ||||||
|     zero_value_invoices = invoices.filter(value=0) |     zero_value_invoices = invoices.filter(value=0).distinct() | ||||||
| 
 | 
 | ||||||
|     times = itertools.chain( |     times = itertools.chain( | ||||||
|         (line["max_time"] for line in invoice_max_time), |         (line["max_time"] for line in invoice_max_time), | ||||||
|  | @ -464,18 +545,19 @@ def attendee(request, form, user_id=None): | ||||||
|     if user_id is None: |     if user_id is None: | ||||||
|         return attendee_list(request) |         return attendee_list(request) | ||||||
| 
 | 
 | ||||||
|     attendee = people.Attendee.objects.get(user__id=user_id) |  | ||||||
|     name = attendee.attendeeprofilebase.attendee_name() |  | ||||||
| 
 |  | ||||||
|     reports = [] |     reports = [] | ||||||
| 
 | 
 | ||||||
|     profile_data = [] |     profile_data = [] | ||||||
|     try: |     try: | ||||||
|  |         attendee = people.Attendee.objects.get(user__id=user_id) | ||||||
|  |         name = attendee.attendeeprofilebase.attendee_name() | ||||||
|  | 
 | ||||||
|         profile = people.AttendeeProfileBase.objects.get_subclass( |         profile = people.AttendeeProfileBase.objects.get_subclass( | ||||||
|             attendee=attendee |             attendee=attendee | ||||||
|         ) |         ) | ||||||
|         fields = profile._meta.get_fields() |         fields = profile._meta.get_fields() | ||||||
|     except people.AttendeeProfileBase.DoesNotExist: |     except people.AttendeeProfileBase.DoesNotExist: | ||||||
|  |         name = attendee.user.username | ||||||
|         fields = [] |         fields = [] | ||||||
| 
 | 
 | ||||||
|     exclude = set(["attendeeprofilebase_ptr", "id"]) |     exclude = set(["attendeeprofilebase_ptr", "id"]) | ||||||
|  | @ -489,11 +571,17 @@ def attendee(request, form, user_id=None): | ||||||
| 
 | 
 | ||||||
|         if isinstance(field, models.ManyToManyField): |         if isinstance(field, models.ManyToManyField): | ||||||
|             value = ", ".join(str(i) for i in value.all()) |             value = ", ".join(str(i) for i in value.all()) | ||||||
|  |         elif isinstance(field, CharField): | ||||||
|  |             value = bleach.clean(value) | ||||||
| 
 | 
 | ||||||
|         profile_data.append((field.verbose_name, value)) |         profile_data.append((field.verbose_name, value)) | ||||||
| 
 | 
 | ||||||
|     cart = CartController.for_user(attendee.user) |     cart = CartController.for_user(attendee.user) | ||||||
|     reservation = cart.cart.reservation_duration + cart.cart.time_last_updated |     try: | ||||||
|  |         reservation = cart.cart.reservation_duration + cart.cart.time_last_updated | ||||||
|  |     except AttributeError:  # No reservation_duration set -- default to 24h | ||||||
|  |         reservation = datetime.datetime.now() + datetime.timedelta(hours=24) | ||||||
|  | 
 | ||||||
|     profile_data.append(("Current cart reserved until", reservation)) |     profile_data.append(("Current cart reserved until", reservation)) | ||||||
| 
 | 
 | ||||||
|     reports.append(ListReport("Profile", ["", ""], profile_data)) |     reports.append(ListReport("Profile", ["", ""], profile_data)) | ||||||
|  | @ -515,53 +603,65 @@ def attendee(request, form, user_id=None): | ||||||
|     reports.append(Links("Actions for " + name, links)) |     reports.append(Links("Actions for " + name, links)) | ||||||
| 
 | 
 | ||||||
|     # Paid and pending  products |     # Paid and pending  products | ||||||
|     ic = ItemController(attendee.user) |     try: | ||||||
|     reports.append(ListReport( |         ic = ItemController(attendee.user) | ||||||
|         "Paid Products", |         reports.append(ListReport( | ||||||
|         ["Product", "Quantity"], |             "Paid Products", | ||||||
|         [(pq.product, pq.quantity) for pq in ic.items_purchased()], |             ["Product", "Quantity"], | ||||||
|     )) |             [(pq.product, pq.quantity) for pq in ic.items_purchased()], | ||||||
|     reports.append(ListReport( |         )) | ||||||
|         "Unpaid Products", |         reports.append(ListReport( | ||||||
|         ["Product", "Quantity"], |             "Unpaid Products", | ||||||
|         [(pq.product, pq.quantity) for pq in ic.items_pending()], |             ["Product", "Quantity"], | ||||||
|     )) |             [(pq.product, pq.quantity) for pq in ic.items_pending()], | ||||||
|  |         )) | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
| 
 | 
 | ||||||
|     # Invoices |     # Invoices | ||||||
|     invoices = commerce.Invoice.objects.filter( |     try: | ||||||
|         user=attendee.user, |         invoices = commerce.Invoice.objects.filter( | ||||||
|     ) |             user=attendee.user, | ||||||
|     reports.append(QuerysetReport( |         ) | ||||||
|         "Invoices", |         reports.append(QuerysetReport( | ||||||
|         ["id", "get_status_display", "value"], |             "Invoices", | ||||||
|         invoices, |             ["id", "get_status_display", "value"], | ||||||
|         headings=["Invoice ID", "Status", "Value"], |             invoices, | ||||||
|         link_view=views.invoice, |             headings=["Invoice ID", "Status", "Value"], | ||||||
|     )) |             link_view=views.invoice, | ||||||
|  |         )) | ||||||
|  |     except AttrbuteError: | ||||||
|  |         pass | ||||||
| 
 | 
 | ||||||
|     # Credit Notes |     # Credit Notes | ||||||
|     credit_notes = commerce.CreditNote.objects.filter( |     try: | ||||||
|         invoice__user=attendee.user, |         credit_notes = commerce.CreditNote.objects.filter( | ||||||
|     ).select_related("invoice", "creditnoteapplication", "creditnoterefund") |             invoice__user=attendee.user, | ||||||
|  |         ).select_related("invoice", "creditnoteapplication", "creditnoterefund") | ||||||
| 
 | 
 | ||||||
|     reports.append(QuerysetReport( |         reports.append(QuerysetReport( | ||||||
|         "Credit Notes", |             "Credit Notes", | ||||||
|         ["id", "status", "value"], |             ["id", "status", "value"], | ||||||
|         credit_notes, |             credit_notes, | ||||||
|         link_view=views.credit_note, |             link_view=views.credit_note, | ||||||
|     )) |         )) | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
| 
 | 
 | ||||||
|     # All payments |     # All payments | ||||||
|     payments = commerce.PaymentBase.objects.filter( |     try: | ||||||
|         invoice__user=attendee.user, |         payments = commerce.PaymentBase.objects.filter( | ||||||
|     ).select_related("invoice") |             invoice__user=attendee.user, | ||||||
|  |         ).select_related("invoice") | ||||||
| 
 | 
 | ||||||
|     reports.append(QuerysetReport( |         reports.append(QuerysetReport( | ||||||
|         "Payments", |             "Payments", | ||||||
|         ["invoice__id", "id", "reference", "amount"], |             ["invoice__id", "id", "reference", "amount"], | ||||||
|         payments, |             payments, | ||||||
|         link_view=views.invoice, |             link_view=views.invoice, | ||||||
|     )) |         )) | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
| 
 | 
 | ||||||
|     return reports |     return reports | ||||||
| 
 | 
 | ||||||
|  | @ -678,6 +778,7 @@ def attendee_data(request, form, user_id=None): | ||||||
|     category = product + "__category" |     category = product + "__category" | ||||||
|     category_name = category + "__name" |     category_name = category + "__name" | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     if by_category: |     if by_category: | ||||||
|         grouping_fields = (category, category_name) |         grouping_fields = (category, category_name) | ||||||
|         order_by = (category, ) |         order_by = (category, ) | ||||||
|  | @ -712,7 +813,7 @@ def attendee_data(request, form, user_id=None): | ||||||
|                     return None |                     return None | ||||||
|         else: |         else: | ||||||
|             def display_field(value): |             def display_field(value): | ||||||
|                 return value |                 return bleach.clean(value) | ||||||
| 
 | 
 | ||||||
|         status_count = lambda status: Case(When(  # noqa |         status_count = lambda status: Case(When(  # noqa | ||||||
|                 attendee__user__cart__status=status, |                 attendee__user__cart__status=status, | ||||||
|  | @ -759,7 +860,7 @@ def attendee_data(request, form, user_id=None): | ||||||
|         if isinstance(field_type, models.ManyToManyField): |         if isinstance(field_type, models.ManyToManyField): | ||||||
|             return [str(i) for i in attr.all()] or "" |             return [str(i) for i in attr.all()] or "" | ||||||
|         else: |         else: | ||||||
|             return attr |             return bleach.clean(attr) | ||||||
| 
 | 
 | ||||||
|     headings = ["User ID", "Name", "Email", "Product", "Item Status"] |     headings = ["User ID", "Name", "Email", "Product", "Item Status"] | ||||||
|     headings.extend(field_names) |     headings.extend(field_names) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,8 @@ try: | ||||||
| except ImportError: | except ImportError: | ||||||
|     from urllib.parse import urlencode |     from urllib.parse import urlencode | ||||||
| 
 | 
 | ||||||
|  | from operator import attrgetter | ||||||
|  | 
 | ||||||
| register = template.Library() | register = template.Library() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -46,8 +48,9 @@ def missing_categories(context): | ||||||
|     for product, quantity in items: |     for product, quantity in items: | ||||||
|         categories_held.add(product.category) |         categories_held.add(product.category) | ||||||
| 
 | 
 | ||||||
|     return categories_available - categories_held |     missing = categories_available - categories_held | ||||||
| 
 | 
 | ||||||
|  |     return sorted(set(i for i in missing), key=attrgetter("order")) | ||||||
| 
 | 
 | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
| def available_credit(context): | def available_credit(context): | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								vendor/registrasion/registrasion/urls.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/registrasion/registrasion/urls.py
									
										
									
									
										vendored
									
									
								
							|  | @ -6,7 +6,7 @@ from django.conf.urls import url | ||||||
| from .views import ( | from .views import ( | ||||||
|     amend_registration, |     amend_registration, | ||||||
|     badge, |     badge, | ||||||
|     badges, |     badger, | ||||||
|     checkout, |     checkout, | ||||||
|     credit_note, |     credit_note, | ||||||
|     edit_profile, |     edit_profile, | ||||||
|  | @ -26,7 +26,8 @@ from .views import ( | ||||||
| public = [ | public = [ | ||||||
|     url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"), |     url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"), | ||||||
|     url(r"^badge/([0-9]+)$", badge, name="badge"), |     url(r"^badge/([0-9]+)$", badge, name="badge"), | ||||||
|     url(r"^badges$", badges, name="badges"), |     url(r"^badger/([A-Za-z0-9]+)$", badger, name="badger"), | ||||||
|  |     url(r"^badger/", badger, name="badger"), | ||||||
|     url(r"^category/([0-9]+)$", product_category, name="product_category"), |     url(r"^category/([0-9]+)$", product_category, name="product_category"), | ||||||
|     url(r"^checkout$", checkout, name="checkout"), |     url(r"^checkout$", checkout, name="checkout"), | ||||||
|     url(r"^checkout/([0-9]+)$", checkout, name="checkout"), |     url(r"^checkout/([0-9]+)$", checkout, name="checkout"), | ||||||
|  | @ -42,9 +43,9 @@ public = [ | ||||||
|         name="invoice_access"), |         name="invoice_access"), | ||||||
|     url(r"^invoice_mailout$", invoice_mailout, name="invoice_mailout"), |     url(r"^invoice_mailout$", invoice_mailout, name="invoice_mailout"), | ||||||
|     url(r"^profile$", edit_profile, name="attendee_edit"), |     url(r"^profile$", edit_profile, name="attendee_edit"), | ||||||
|     url(r"^register$", guided_registration, name="guided_registration"), |  | ||||||
|     url(r"^review$", review, name="review"), |     url(r"^review$", review, name="review"), | ||||||
|     url(r"^voucher$", voucher_code, name="voucher_code"), |     url(r"^voucher$", voucher_code, name="voucher_code"), | ||||||
|  |     url(r"^register$", guided_registration, name="guided_registration"), | ||||||
|     url(r"^register/([0-9]+)$", guided_registration, |     url(r"^register/([0-9]+)$", guided_registration, | ||||||
|         name="guided_registration"), |         name="guided_registration"), | ||||||
| ] | ] | ||||||
|  | @ -56,6 +57,7 @@ reports = [ | ||||||
|     url(r"^attendee_data/?$", rv.attendee_data, name="attendee_data"), |     url(r"^attendee_data/?$", rv.attendee_data, name="attendee_data"), | ||||||
|     url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"), |     url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"), | ||||||
|     url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"), |     url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"), | ||||||
|  |     url(r"^limits/?$", rv.limits, name="reconciliation"), | ||||||
|     url(r"^manifest/?$", rv.manifest, name="manifest"), |     url(r"^manifest/?$", rv.manifest, name="manifest"), | ||||||
|     url( |     url( | ||||||
|         r"^product_line_items/?$", |         r"^product_line_items/?$", | ||||||
|  |  | ||||||
							
								
								
									
										166
									
								
								vendor/registrasion/registrasion/views.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										166
									
								
								vendor/registrasion/registrasion/views.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,6 @@ | ||||||
| import datetime | import datetime | ||||||
| import zipfile | import zipfile | ||||||
|  | import os | ||||||
| 
 | 
 | ||||||
| from . import forms | from . import forms | ||||||
| from . import util | from . import util | ||||||
|  | @ -32,6 +33,16 @@ from django.shortcuts import redirect | ||||||
| from django.shortcuts import render | from django.shortcuts import render | ||||||
| from django.template import Context, Template, loader | from django.template import Context, Template, loader | ||||||
| 
 | 
 | ||||||
|  | from lxml import etree | ||||||
|  | from copy import deepcopy | ||||||
|  | 
 | ||||||
|  | from registrasion.forms import BadgeForm, ticket_selection | ||||||
|  | from registrasion.contrib.badger import ( | ||||||
|  |                                          collate, | ||||||
|  |                                          svg_badge, | ||||||
|  |                                          InvalidTicketChoiceError | ||||||
|  |                                          ) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| _GuidedRegistrationSection = namedtuple( | _GuidedRegistrationSection = namedtuple( | ||||||
|     "GuidedRegistrationSection", |     "GuidedRegistrationSection", | ||||||
|  | @ -544,11 +555,13 @@ def _handle_products(request, category, products, prefix): | ||||||
|         # If category is required, the user must have at least one |         # If category is required, the user must have at least one | ||||||
|         # in an active+valid cart |         # in an active+valid cart | ||||||
|         if category.required: |         if category.required: | ||||||
|             carts = commerce.Cart.objects.filter(user=request.user) |             carts = commerce.Cart.objects.filter(user=request.user, | ||||||
|  |                                                  status=commerce.Cart.STATUS_ACTIVE) | ||||||
|             items = commerce.ProductItem.objects.filter( |             items = commerce.ProductItem.objects.filter( | ||||||
|                 product__category=category, |                 product__category=category, | ||||||
|                 cart=carts, |                 cart=current_cart.cart, | ||||||
|             ) |             ) | ||||||
|  | 
 | ||||||
|             if len(items) == 0: |             if len(items) == 0: | ||||||
|                 products_form.add_error( |                 products_form.add_error( | ||||||
|                     None, |                     None, | ||||||
|  | @ -1076,24 +1089,42 @@ def invoice_mailout(request): | ||||||
| 
 | 
 | ||||||
|     return render(request, "registrasion/invoice_mailout.html", data) |     return render(request, "registrasion/invoice_mailout.html", data) | ||||||
| 
 | 
 | ||||||
|  | def _get_badge_template_name(): | ||||||
|  |     return os.path.join(settings.PROJECT_ROOT, 'pinaxcon', 'templates', | ||||||
|  |                         settings.BADGER_DEFAULT_SVG) | ||||||
| 
 | 
 | ||||||
| @user_passes_test(_staff_only) | @user_passes_test(_staff_only) | ||||||
| def badge(request, user_id): | def badge(request, user_id): | ||||||
|     ''' Renders a single user's badge (SVG). ''' |     ''' | ||||||
|  |     Renders a single user's badge (SVG). | ||||||
|  | 
 | ||||||
|  |     This does little more than call Richard Jones' collate and svg_badge | ||||||
|  |     functions found in generate_badges. | ||||||
|  |     ''' | ||||||
| 
 | 
 | ||||||
|     user_id = int(user_id) |     user_id = int(user_id) | ||||||
|     user = User.objects.get(pk=user_id) |     user = User.objects.get(pk=user_id) | ||||||
| 
 | 
 | ||||||
|     rendered = render_badge(user) |     # This will fail spectacularly -- will put exception handling in later ... | ||||||
|     response = HttpResponse(rendered) |     user_data = list(collate({'usernames': [user.username]}))[0] | ||||||
| 
 | 
 | ||||||
|  |     orig = etree.parse(_get_badge_template_name()) | ||||||
|  |     tree = deepcopy(orig) | ||||||
|  |     root = tree.getroot() | ||||||
|  | 
 | ||||||
|  |     svg_badge(root, user_data, 0) | ||||||
|  | 
 | ||||||
|  |     response = HttpResponse(etree.tostring(root)) | ||||||
|     response["Content-Type"] = "image/svg+xml" |     response["Content-Type"] = "image/svg+xml" | ||||||
|     response["Content-Disposition"] = 'inline; filename="badge.svg"' |     response["Content-Disposition"] = 'inline; filename="badge.svg"' | ||||||
|     return response |     return response | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def badges(request): | def badges(request): | ||||||
|     ''' Either displays a form containing a list of users with badges to |     ''' | ||||||
|  |     *** NOT USED FOR PYCONAU 2017 MELBOURNE  (I.e., not supported in badger module.) *** | ||||||
|  | 
 | ||||||
|  |     Either displays a form containing a list of users with badges to | ||||||
|     render, or returns a .zip file containing their badges. ''' |     render, or returns a .zip file containing their badges. ''' | ||||||
| 
 | 
 | ||||||
|     category = request.GET.getlist("category", []) |     category = request.GET.getlist("category", []) | ||||||
|  | @ -1128,12 +1159,121 @@ def badges(request): | ||||||
|     return render(request, "registrasion/badges.html", data) |     return render(request, "registrasion/badges.html", data) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def render_badge(user): | def collate_from_form(form): | ||||||
|     ''' Renders a single user's badge. ''' |     ''' | ||||||
|  |     Does what collate does, but using form data as its input source | ||||||
|  |     rather than User record. | ||||||
|  |     ''' | ||||||
|  |         # Build the thing we'll pass to svg_badge() later. | ||||||
|  |     data = dict() | ||||||
| 
 | 
 | ||||||
|     data = { |     # Get the name bits ... | ||||||
|         "user": user, |     at_nm = form.data['name'].split() | ||||||
|     } |     if at_nm[0].lower() in 'mr dr ms mrs miss'.split(): | ||||||
|  |         at_nm[0] = at_nm[0] + ' ' + at_nm[1] | ||||||
|  |         del at_nm[1] | ||||||
|  |     if at_nm: | ||||||
|  |         data['firstname'] = at_nm[0] | ||||||
|  |         data['lastname'] = ''.join(at_nm[1:]) | ||||||
|  |     else:  # Can't happen -- form validator will check for this. | ||||||
|  |         pass | ||||||
| 
 | 
 | ||||||
|     t = loader.get_template('registrasion/badge.svg') |     # Free text -- only one line ... come on! | ||||||
|     return t.render(data) |     data['line1'] = form.data['free_text_1'] | ||||||
|  |     data['line2'] = form.data['free_text_2'] | ||||||
|  | 
 | ||||||
|  |     # Email ... | ||||||
|  |     data['email'] = form.data['email'] | ||||||
|  | 
 | ||||||
|  |     # Don't think we want to allow ad hoc organiser tickets ... | ||||||
|  |     data['organiser'] = False | ||||||
|  | 
 | ||||||
|  |     # Punt on shirts for now ... | ||||||
|  |     data['shirts'] = list() | ||||||
|  | 
 | ||||||
|  |     # Lots booleans ... | ||||||
|  |     for key in ['over18', 'paid', 'friday', 'speaker', 'tutorial', | ||||||
|  |                 'sprints', 'company',]: | ||||||
|  |         data[key] = form.data.get(key, False) | ||||||
|  | 
 | ||||||
|  |     # This will throw InvalidTicketChoiceError if the ticket | ||||||
|  |     # choice isn't found in the ticket list or is the | ||||||
|  |     # "Plese select a valid tickt" choice.  (I.e., they forgot | ||||||
|  |     # to choose a ticket.) | ||||||
|  |     data['ticket'] = ticket_selection()[int(form.data['ticket'])][1] | ||||||
|  | 
 | ||||||
|  |     data['volunteer'] = data['ticket'].find("Volunteer") >= 0 | ||||||
|  | 
 | ||||||
|  |     if 'Specialist Day Only' in data['ticket']: | ||||||
|  |         data['ticket'] = 'Friday Only' | ||||||
|  |         data['friday'] = True | ||||||
|  | 
 | ||||||
|  |     if 'Conference Organiser' in data['ticket']: | ||||||
|  |         data['ticket'] = '' | ||||||
|  | 
 | ||||||
|  |     if 'Conference Volunteer' in data['ticket']: | ||||||
|  |         data['ticket'] = '' | ||||||
|  | 
 | ||||||
|  |     data['promote_company'] = ( | ||||||
|  |         data['organiser'] or data['volunteer'] or data['speaker'] or | ||||||
|  |         'Sponsor' in data['ticket'] or | ||||||
|  |         'Contributor' in data['ticket'] or | ||||||
|  |         'Professional' in data['ticket'] | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     return data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user_passes_test(_staff_only) | ||||||
|  | def badger(request, username=None): | ||||||
|  |     ''' | ||||||
|  |     Renders a single user's badge from data supplied on | ||||||
|  |     a form rather than from Attendee data. | ||||||
|  | 
 | ||||||
|  |     If *username* is provided in the URL, an attempt | ||||||
|  |     will be made to look up this user and fill in the | ||||||
|  |     badge details from the User and Attendee records. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     if username is not None: | ||||||
|  | 
 | ||||||
|  |         # We have a username.  Try to populate our badge data | ||||||
|  |         # from User/Attendee model. | ||||||
|  |         try: | ||||||
|  |             data = collate({'usernames': [username,]}).next() | ||||||
|  |         except: # No matching User record (probably) ... just put up a blank form | ||||||
|  |             return render(request, settings.BADGER_DEFAULT_FORM, {'form': BadgeForm}) | ||||||
|  |     else: | ||||||
|  |         form = BadgeForm(request.POST) | ||||||
|  | 
 | ||||||
|  |         if len(form.data) == 0:  # Empty or request to put up the form. | ||||||
|  |             return render(request, settings.BADGER_DEFAULT_FORM, {'form': BadgeForm}) | ||||||
|  | 
 | ||||||
|  |         try: | ||||||
|  |             if form.is_valid(): | ||||||
|  |                data = collate_from_form(form) | ||||||
|  | 
 | ||||||
|  |         except InvalidTicketChoiceError: | ||||||
|  |             form.add_error('ticket', 'Please select a VALID ticket type!') | ||||||
|  |             return render(request, settings.BADGER_DEFAULT_FORM, {'form': form}) | ||||||
|  | 
 | ||||||
|  |         except TypeError as e: | ||||||
|  |             form.add_error(e.message) | ||||||
|  |             return render(request, settings.BADGER_DEFAULT_FORM, {'form': form}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     # We should have valid data if we get this far. | ||||||
|  |     # Fill in the template and return the resulting SVG object. | ||||||
|  |     orig = etree.parse(_get_badge_template_name()) | ||||||
|  |     tree = deepcopy(orig) | ||||||
|  |     root = tree.getroot() | ||||||
|  | 
 | ||||||
|  |     # Generate the badge (svg) | ||||||
|  |     svg_badge(root, data, 0) | ||||||
|  | 
 | ||||||
|  |     # Ship it back to the user... | ||||||
|  |     response = HttpResponse(etree.tostring(root)) | ||||||
|  | 
 | ||||||
|  |     response["Content-Type"] = "image/svg+xml" | ||||||
|  |     response["Content-Disposition"] = 'inline; filename="badge.svg"' | ||||||
|  |     return response | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 James Polley
						James Polley