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] | ||||
| 	remote = git@gitlab.com:tchaypo/registrasion.git | ||||
| 	branch = lca2018 | ||||
| 	commit = 7cf314adae9f8de1519db63828f55a10aa09f0ee | ||||
| 	parent = 7c5c6c02f20ddcbc6c54b3065311880fce586192 | ||||
| 	commit = 3545a809e8e14014963c670709b6d0273c0e354a | ||||
| 	parent = 19e4185cd9433c8f743c32dde8aee09455db3982 | ||||
| 	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 itertools | ||||
| 
 | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db import transaction | ||||
| from django.db.models import Max | ||||
|  | @ -64,6 +64,12 @@ class CartController(object): | |||
|                 time_last_updated=timezone.now(), | ||||
|                 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) | ||||
| 
 | ||||
|     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): | ||||
|         for product in products: | ||||
|             field = forms.BooleanField( | ||||
|                 label='%s -- %s' % (product.name, product.price), | ||||
|                 label='%s -- $%s' % (product.name, product.price), | ||||
|                 required=False, | ||||
|             ) | ||||
|             cls.base_fields[cls.field_name(product)] = field | ||||
|  | @ -521,3 +521,58 @@ class InvoiceEmailForm(InvoicesWithProductAndStatusForm): | |||
|         choices=ACTION_CHOICES, | ||||
|         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, | ||||
|     ) | ||||
|     completed_registration = models.BooleanField(default=False) | ||||
|     guided_categories_complete = models.ManyToManyField("category") | ||||
|     guided_categories_complete = models.ManyToManyField("category", blank=True) | ||||
| 
 | ||||
| 
 | ||||
| class AttendeeProfileBase(models.Model): | ||||
|  |  | |||
|  | @ -75,6 +75,8 @@ class _ReportTemplateWrapper(object): | |||
|     def rows(self): | ||||
|         return self.report.rows(self.content_type) | ||||
| 
 | ||||
|     def count(self): | ||||
|         return self.report.count() | ||||
| 
 | ||||
| class BasicReport(Report): | ||||
| 
 | ||||
|  | @ -118,6 +120,9 @@ class ListReport(BasicReport): | |||
|                 for i, cell in enumerate(row) | ||||
|             ] | ||||
| 
 | ||||
|     def count(self): | ||||
|         return len(self._data) | ||||
| 
 | ||||
| 
 | ||||
| class QuerysetReport(BasicReport): | ||||
| 
 | ||||
|  | @ -158,6 +163,9 @@ class QuerysetReport(BasicReport): | |||
|             ] | ||||
| 
 | ||||
| 
 | ||||
|     def count(self): | ||||
|         return self._queryset.count() | ||||
| 
 | ||||
| class Links(Report): | ||||
| 
 | ||||
|     def __init__(self, title, links): | ||||
|  | @ -182,6 +190,8 @@ class Links(Report): | |||
|                 self._linked_text(content_type, url, link_text) | ||||
|             ] | ||||
| 
 | ||||
|     def count(self): | ||||
|         return len(self._links) | ||||
| 
 | ||||
| def report_view(title, form_type=None): | ||||
|     ''' 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 Case, When, Value | ||||
| from django.db.models.fields.related import RelatedField | ||||
| from django.db.models.fields import CharField | ||||
| from django.shortcuts import render | ||||
| 
 | ||||
| from registrasion.controllers.cart import CartController | ||||
| from registrasion.controllers.item import ItemController | ||||
| from registrasion.models import conditions | ||||
| from registrasion.models import commerce | ||||
| from registrasion.models import people | ||||
| from registrasion import util | ||||
|  | @ -30,6 +32,8 @@ from .reports import ListReport | |||
| from .reports import QuerysetReport | ||||
| from .reports import report_view | ||||
| 
 | ||||
| import bleach | ||||
| 
 | ||||
| 
 | ||||
| def CURRENCY(): | ||||
|     return models.DecimalField(decimal_places=2) | ||||
|  | @ -240,6 +244,83 @@ def group_by_cart_status(queryset, order, 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) | ||||
| def product_status(request, form): | ||||
|     ''' Summarises the inventory status of the given items, grouping by | ||||
|  | @ -329,7 +410,7 @@ def product_line_items(request, form): | |||
|         "user", | ||||
|         "user__attendee", | ||||
|         "user__attendee__attendeeprofilebase" | ||||
|     ).order_by("issue_time") | ||||
|     ).order_by("issue_time").distinct() | ||||
| 
 | ||||
|     headings = [ | ||||
|         '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 = invoices.filter(value=0) | ||||
|     zero_value_invoices = invoices.filter(value=0).distinct() | ||||
| 
 | ||||
|     times = itertools.chain( | ||||
|         (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: | ||||
|         return attendee_list(request) | ||||
| 
 | ||||
|     attendee = people.Attendee.objects.get(user__id=user_id) | ||||
|     name = attendee.attendeeprofilebase.attendee_name() | ||||
| 
 | ||||
|     reports = [] | ||||
| 
 | ||||
|     profile_data = [] | ||||
|     try: | ||||
|         attendee = people.Attendee.objects.get(user__id=user_id) | ||||
|         name = attendee.attendeeprofilebase.attendee_name() | ||||
| 
 | ||||
|         profile = people.AttendeeProfileBase.objects.get_subclass( | ||||
|             attendee=attendee | ||||
|         ) | ||||
|         fields = profile._meta.get_fields() | ||||
|     except people.AttendeeProfileBase.DoesNotExist: | ||||
|         name = attendee.user.username | ||||
|         fields = [] | ||||
| 
 | ||||
|     exclude = set(["attendeeprofilebase_ptr", "id"]) | ||||
|  | @ -489,11 +571,17 @@ def attendee(request, form, user_id=None): | |||
| 
 | ||||
|         if isinstance(field, models.ManyToManyField): | ||||
|             value = ", ".join(str(i) for i in value.all()) | ||||
|         elif isinstance(field, CharField): | ||||
|             value = bleach.clean(value) | ||||
| 
 | ||||
|         profile_data.append((field.verbose_name, value)) | ||||
| 
 | ||||
|     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)) | ||||
| 
 | ||||
|     reports.append(ListReport("Profile", ["", ""], profile_data)) | ||||
|  | @ -515,53 +603,65 @@ def attendee(request, form, user_id=None): | |||
|     reports.append(Links("Actions for " + name, links)) | ||||
| 
 | ||||
|     # Paid and pending  products | ||||
|     ic = ItemController(attendee.user) | ||||
|     reports.append(ListReport( | ||||
|         "Paid Products", | ||||
|         ["Product", "Quantity"], | ||||
|         [(pq.product, pq.quantity) for pq in ic.items_purchased()], | ||||
|     )) | ||||
|     reports.append(ListReport( | ||||
|         "Unpaid Products", | ||||
|         ["Product", "Quantity"], | ||||
|         [(pq.product, pq.quantity) for pq in ic.items_pending()], | ||||
|     )) | ||||
|     try: | ||||
|         ic = ItemController(attendee.user) | ||||
|         reports.append(ListReport( | ||||
|             "Paid Products", | ||||
|             ["Product", "Quantity"], | ||||
|             [(pq.product, pq.quantity) for pq in ic.items_purchased()], | ||||
|         )) | ||||
|         reports.append(ListReport( | ||||
|             "Unpaid Products", | ||||
|             ["Product", "Quantity"], | ||||
|             [(pq.product, pq.quantity) for pq in ic.items_pending()], | ||||
|         )) | ||||
|     except AttributeError: | ||||
|         pass | ||||
| 
 | ||||
|     # Invoices | ||||
|     invoices = commerce.Invoice.objects.filter( | ||||
|         user=attendee.user, | ||||
|     ) | ||||
|     reports.append(QuerysetReport( | ||||
|         "Invoices", | ||||
|         ["id", "get_status_display", "value"], | ||||
|         invoices, | ||||
|         headings=["Invoice ID", "Status", "Value"], | ||||
|         link_view=views.invoice, | ||||
|     )) | ||||
|     try: | ||||
|         invoices = commerce.Invoice.objects.filter( | ||||
|             user=attendee.user, | ||||
|         ) | ||||
|         reports.append(QuerysetReport( | ||||
|             "Invoices", | ||||
|             ["id", "get_status_display", "value"], | ||||
|             invoices, | ||||
|             headings=["Invoice ID", "Status", "Value"], | ||||
|             link_view=views.invoice, | ||||
|         )) | ||||
|     except AttrbuteError: | ||||
|         pass | ||||
| 
 | ||||
|     # Credit Notes | ||||
|     credit_notes = commerce.CreditNote.objects.filter( | ||||
|         invoice__user=attendee.user, | ||||
|     ).select_related("invoice", "creditnoteapplication", "creditnoterefund") | ||||
|     try: | ||||
|         credit_notes = commerce.CreditNote.objects.filter( | ||||
|             invoice__user=attendee.user, | ||||
|         ).select_related("invoice", "creditnoteapplication", "creditnoterefund") | ||||
| 
 | ||||
|     reports.append(QuerysetReport( | ||||
|         "Credit Notes", | ||||
|         ["id", "status", "value"], | ||||
|         credit_notes, | ||||
|         link_view=views.credit_note, | ||||
|     )) | ||||
|         reports.append(QuerysetReport( | ||||
|             "Credit Notes", | ||||
|             ["id", "status", "value"], | ||||
|             credit_notes, | ||||
|             link_view=views.credit_note, | ||||
|         )) | ||||
|     except AttributeError: | ||||
|         pass | ||||
| 
 | ||||
|     # All payments | ||||
|     payments = commerce.PaymentBase.objects.filter( | ||||
|         invoice__user=attendee.user, | ||||
|     ).select_related("invoice") | ||||
|     try: | ||||
|         payments = commerce.PaymentBase.objects.filter( | ||||
|             invoice__user=attendee.user, | ||||
|         ).select_related("invoice") | ||||
| 
 | ||||
|     reports.append(QuerysetReport( | ||||
|         "Payments", | ||||
|         ["invoice__id", "id", "reference", "amount"], | ||||
|         payments, | ||||
|         link_view=views.invoice, | ||||
|     )) | ||||
|         reports.append(QuerysetReport( | ||||
|             "Payments", | ||||
|             ["invoice__id", "id", "reference", "amount"], | ||||
|             payments, | ||||
|             link_view=views.invoice, | ||||
|         )) | ||||
|     except AttributeError: | ||||
|         pass | ||||
| 
 | ||||
|     return reports | ||||
| 
 | ||||
|  | @ -678,6 +778,7 @@ def attendee_data(request, form, user_id=None): | |||
|     category = product + "__category" | ||||
|     category_name = category + "__name" | ||||
| 
 | ||||
| 
 | ||||
|     if by_category: | ||||
|         grouping_fields = (category, category_name) | ||||
|         order_by = (category, ) | ||||
|  | @ -712,7 +813,7 @@ def attendee_data(request, form, user_id=None): | |||
|                     return None | ||||
|         else: | ||||
|             def display_field(value): | ||||
|                 return value | ||||
|                 return bleach.clean(value) | ||||
| 
 | ||||
|         status_count = lambda status: Case(When(  # noqa | ||||
|                 attendee__user__cart__status=status, | ||||
|  | @ -759,7 +860,7 @@ def attendee_data(request, form, user_id=None): | |||
|         if isinstance(field_type, models.ManyToManyField): | ||||
|             return [str(i) for i in attr.all()] or "" | ||||
|         else: | ||||
|             return attr | ||||
|             return bleach.clean(attr) | ||||
| 
 | ||||
|     headings = ["User ID", "Name", "Email", "Product", "Item Status"] | ||||
|     headings.extend(field_names) | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ try: | |||
| except ImportError: | ||||
|     from urllib.parse import urlencode | ||||
| 
 | ||||
| from operator import attrgetter | ||||
| 
 | ||||
| register = template.Library() | ||||
| 
 | ||||
| 
 | ||||
|  | @ -46,8 +48,9 @@ def missing_categories(context): | |||
|     for product, quantity in items: | ||||
|         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) | ||||
| 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 ( | ||||
|     amend_registration, | ||||
|     badge, | ||||
|     badges, | ||||
|     badger, | ||||
|     checkout, | ||||
|     credit_note, | ||||
|     edit_profile, | ||||
|  | @ -26,7 +26,8 @@ from .views import ( | |||
| public = [ | ||||
|     url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"), | ||||
|     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"^checkout$", checkout, name="checkout"), | ||||
|     url(r"^checkout/([0-9]+)$", checkout, name="checkout"), | ||||
|  | @ -42,9 +43,9 @@ public = [ | |||
|         name="invoice_access"), | ||||
|     url(r"^invoice_mailout$", invoice_mailout, name="invoice_mailout"), | ||||
|     url(r"^profile$", edit_profile, name="attendee_edit"), | ||||
|     url(r"^register$", guided_registration, name="guided_registration"), | ||||
|     url(r"^review$", review, name="review"), | ||||
|     url(r"^voucher$", voucher_code, name="voucher_code"), | ||||
|     url(r"^register$", guided_registration, name="guided_registration"), | ||||
|     url(r"^register/([0-9]+)$", guided_registration, | ||||
|         name="guided_registration"), | ||||
| ] | ||||
|  | @ -56,6 +57,7 @@ reports = [ | |||
|     url(r"^attendee_data/?$", rv.attendee_data, name="attendee_data"), | ||||
|     url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"), | ||||
|     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"^product_line_items/?$", | ||||
|  |  | |||
							
								
								
									
										166
									
								
								vendor/registrasion/registrasion/views.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										166
									
								
								vendor/registrasion/registrasion/views.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,6 @@ | |||
| import datetime | ||||
| import zipfile | ||||
| import os | ||||
| 
 | ||||
| from . import forms | ||||
| from . import util | ||||
|  | @ -32,6 +33,16 @@ from django.shortcuts import redirect | |||
| from django.shortcuts import render | ||||
| 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", | ||||
|  | @ -544,11 +555,13 @@ def _handle_products(request, category, products, prefix): | |||
|         # If category is required, the user must have at least one | ||||
|         # in an active+valid cart | ||||
|         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( | ||||
|                 product__category=category, | ||||
|                 cart=carts, | ||||
|                 cart=current_cart.cart, | ||||
|             ) | ||||
| 
 | ||||
|             if len(items) == 0: | ||||
|                 products_form.add_error( | ||||
|                     None, | ||||
|  | @ -1076,24 +1089,42 @@ def invoice_mailout(request): | |||
| 
 | ||||
|     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) | ||||
| 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 = User.objects.get(pk=user_id) | ||||
| 
 | ||||
|     rendered = render_badge(user) | ||||
|     response = HttpResponse(rendered) | ||||
|     # This will fail spectacularly -- will put exception handling in later ... | ||||
|     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-Disposition"] = 'inline; filename="badge.svg"' | ||||
|     return response | ||||
| 
 | ||||
| 
 | ||||
| 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. ''' | ||||
| 
 | ||||
|     category = request.GET.getlist("category", []) | ||||
|  | @ -1128,12 +1159,121 @@ def badges(request): | |||
|     return render(request, "registrasion/badges.html", data) | ||||
| 
 | ||||
| 
 | ||||
| def render_badge(user): | ||||
|     ''' Renders a single user's badge. ''' | ||||
| def collate_from_form(form): | ||||
|     ''' | ||||
|     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 = { | ||||
|         "user": user, | ||||
|     } | ||||
|     # Get the name bits ... | ||||
|     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') | ||||
|     return t.render(data) | ||||
|     # Free text -- only one line ... come on! | ||||
|     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