Merge branch 'chrisjrn/attendee_manifest'
This commit is contained in:
		
						commit
						85398a5cf5
					
				
					 8 changed files with 244 additions and 95 deletions
				
			
		
							
								
								
									
										95
									
								
								registrasion/controllers/item.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								registrasion/controllers/item.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | ''' NEEDS TESTS ''' | ||||||
|  | 
 | ||||||
|  | from registrasion.models import commerce | ||||||
|  | from registrasion.models import inventory | ||||||
|  | 
 | ||||||
|  | from collections import namedtuple | ||||||
|  | from django.db.models import Case | ||||||
|  | from django.db.models import Q | ||||||
|  | from django.db.models import Sum | ||||||
|  | from django.db.models import When | ||||||
|  | from django.db.models import Value | ||||||
|  | 
 | ||||||
|  | _ProductAndQuantity = namedtuple("ProductAndQuantity", ["product", "quantity"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ProductAndQuantity(_ProductAndQuantity): | ||||||
|  |     ''' Class that holds a product and a quantity. | ||||||
|  | 
 | ||||||
|  |     Attributes: | ||||||
|  |         product (models.inventory.Product) | ||||||
|  | 
 | ||||||
|  |         quantity (int) | ||||||
|  | 
 | ||||||
|  |     ''' | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ItemController(object): | ||||||
|  | 
 | ||||||
|  |     def __init__(self, user): | ||||||
|  |         self.user = user | ||||||
|  | 
 | ||||||
|  |     def items_purchased(self, category=None): | ||||||
|  |         ''' Aggregates the items that this user has purchased. | ||||||
|  | 
 | ||||||
|  |         Arguments: | ||||||
|  |             category (Optional[models.inventory.Category]): the category | ||||||
|  |                 of items to restrict to. | ||||||
|  | 
 | ||||||
|  |         Returns: | ||||||
|  |             [ProductAndQuantity, ...]: A list of product-quantity pairs, | ||||||
|  |                 aggregating like products from across multiple invoices. | ||||||
|  | 
 | ||||||
|  |         ''' | ||||||
|  | 
 | ||||||
|  |         in_cart = ( | ||||||
|  |             Q(productitem__cart__user=self.user) & | ||||||
|  |             Q(productitem__cart__status=commerce.Cart.STATUS_PAID) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         quantities_in_cart = When( | ||||||
|  |             in_cart, | ||||||
|  |             then="productitem__quantity", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         quantities_or_zero = Case( | ||||||
|  |             quantities_in_cart, | ||||||
|  |             default=Value(0), | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         products = inventory.Product.objects | ||||||
|  | 
 | ||||||
|  |         if category: | ||||||
|  |             products = products.filter(category=category) | ||||||
|  | 
 | ||||||
|  |         products = products.select_related("category") | ||||||
|  |         products = products.annotate(quantity=Sum(quantities_or_zero)) | ||||||
|  |         products = products.filter(quantity__gt=0) | ||||||
|  | 
 | ||||||
|  |         out = [] | ||||||
|  |         for prod in products: | ||||||
|  |             out.append(ProductAndQuantity(prod, prod.quantity)) | ||||||
|  |         return out | ||||||
|  | 
 | ||||||
|  |     def items_pending(self): | ||||||
|  |         ''' Gets all of the items that the user has reserved, but has not yet | ||||||
|  |         paid for. | ||||||
|  | 
 | ||||||
|  |         Returns: | ||||||
|  |             [ProductAndQuantity, ...]: A list of product-quantity pairs for the | ||||||
|  |                 items that the user has not yet paid for. | ||||||
|  | 
 | ||||||
|  |         ''' | ||||||
|  | 
 | ||||||
|  |         all_items = commerce.ProductItem.objects.filter( | ||||||
|  |             cart__user=self.user, | ||||||
|  |             cart__status=commerce.Cart.STATUS_ACTIVE, | ||||||
|  |         ).select_related( | ||||||
|  |             "product", | ||||||
|  |             "product__category", | ||||||
|  |         ).order_by( | ||||||
|  |             "product__category__order", | ||||||
|  |             "product__order", | ||||||
|  |         ) | ||||||
|  |         return all_items | ||||||
|  | @ -347,16 +347,3 @@ class VoucherForm(forms.Form): | ||||||
|         help_text="If you have a voucher code, enter it here", |         help_text="If you have a voucher code, enter it here", | ||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Staff-facing forms. |  | ||||||
| 
 |  | ||||||
| class ProductAndCategoryForm(forms.Form): |  | ||||||
|     product = forms.ModelMultipleChoiceField( |  | ||||||
|         queryset=inventory.Product.objects.all(), |  | ||||||
|         required=False, |  | ||||||
|     ) |  | ||||||
|     category = forms.ModelMultipleChoiceField( |  | ||||||
|         queryset=inventory.Category.objects.all(), |  | ||||||
|         required=False, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								registrasion/reporting/forms.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								registrasion/reporting/forms.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | from registrasion.models import inventory | ||||||
|  | 
 | ||||||
|  | from django import forms | ||||||
|  | 
 | ||||||
|  | # Staff-facing forms. | ||||||
|  | 
 | ||||||
|  | class ProductAndCategoryForm(forms.Form): | ||||||
|  |     product = forms.ModelMultipleChoiceField( | ||||||
|  |         queryset=inventory.Product.objects.all(), | ||||||
|  |         required=False, | ||||||
|  |     ) | ||||||
|  |     category = forms.ModelMultipleChoiceField( | ||||||
|  |         queryset=inventory.Category.objects.all(), | ||||||
|  |         required=False, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UserIdForm(forms.Form): | ||||||
|  |     user = forms.IntegerField( | ||||||
|  |         label="User ID", | ||||||
|  |         required=False, | ||||||
|  |     ) | ||||||
|  | @ -13,6 +13,7 @@ _all_report_views = [] | ||||||
| class Report(object): | class Report(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, title, headings, data, link_view=None): |     def __init__(self, title, headings, data, link_view=None): | ||||||
|  |         self._title = title | ||||||
|         self._headings = headings |         self._headings = headings | ||||||
|         self._data = data |         self._data = data | ||||||
|         self._link_view = link_view |         self._link_view = link_view | ||||||
|  | @ -66,12 +67,15 @@ def report_view(title, form_type=None): | ||||||
|             else: |             else: | ||||||
|                 form = None |                 form = None | ||||||
| 
 | 
 | ||||||
|             report = view(request, form, *a, **k) |             reports = view(request, form, *a, **k) | ||||||
|  | 
 | ||||||
|  |             if isinstance(reports, Report): | ||||||
|  |                 reports = [reports] | ||||||
| 
 | 
 | ||||||
|             ctx = { |             ctx = { | ||||||
|                 "title": title, |                 "title": title, | ||||||
|                 "form": form, |                 "form": form, | ||||||
|                 "report": report, |                 "reports": reports, | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return render(request, "registrasion/report.html", ctx) |             return render(request, "registrasion/report.html", ctx) | ||||||
|  |  | ||||||
|  | @ -1,13 +1,16 @@ | ||||||
|  | import forms | ||||||
|  | 
 | ||||||
| from django.contrib.auth.decorators import user_passes_test | from django.contrib.auth.decorators import user_passes_test | ||||||
| from django.core.urlresolvers import reverse | from django.core.urlresolvers import reverse | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.db.models import F, Q | from django.db.models import F, Q | ||||||
| from django.db.models import Sum | from django.db.models import Count, Sum | ||||||
| from django.db.models import Case, When, Value | from django.db.models import Case, When, Value | ||||||
| from django.shortcuts import render | from django.shortcuts import render | ||||||
| 
 | 
 | ||||||
| from registrasion import forms | from registrasion.controllers.item import ItemController | ||||||
| from registrasion.models import commerce | from registrasion.models import commerce | ||||||
|  | from registrasion.models import people | ||||||
| from registrasion import views | from registrasion import views | ||||||
| 
 | 
 | ||||||
| from reports import get_all_reports | from reports import get_all_reports | ||||||
|  | @ -194,3 +197,110 @@ def credit_notes(request, form): | ||||||
|         ]) |         ]) | ||||||
| 
 | 
 | ||||||
|     return Report("Credit Notes", headings, data, link_view="credit_note") |     return Report("Credit Notes", headings, data, link_view="credit_note") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @report_view("Attendee", form_type=forms.UserIdForm) | ||||||
|  | def attendee(request, form, attendee_id=None): | ||||||
|  |     ''' Returns a list of all manifested attendees if no attendee is specified, | ||||||
|  |     else displays the attendee manifest. ''' | ||||||
|  | 
 | ||||||
|  |     if attendee_id is None and not form.has_changed(): | ||||||
|  |         return attendee_list(request) | ||||||
|  | 
 | ||||||
|  |     if attendee_id is None: | ||||||
|  |         attendee_id = form.user | ||||||
|  | 
 | ||||||
|  |     attendee = people.Attendee.objects.get(id=attendee_id) | ||||||
|  | 
 | ||||||
|  |     reports = [] | ||||||
|  | 
 | ||||||
|  |     # TODO: METADATA. | ||||||
|  | 
 | ||||||
|  |     ic = ItemController(attendee.user) | ||||||
|  |     # Paid products | ||||||
|  |     headings = ["Product", "Quantity"] | ||||||
|  |     data = [] | ||||||
|  | 
 | ||||||
|  |     for pq in ic.items_purchased(): | ||||||
|  |         data.append([ | ||||||
|  |             pq.product, | ||||||
|  |             pq.quantity, | ||||||
|  |         ]) | ||||||
|  | 
 | ||||||
|  |     reports.append(Report("Paid Products", headings, data)) | ||||||
|  | 
 | ||||||
|  |     # Unpaid products | ||||||
|  |     headings = ["Product", "Quantity"] | ||||||
|  |     data = [] | ||||||
|  | 
 | ||||||
|  |     for pq in ic.items_pending(): | ||||||
|  |         data.append([ | ||||||
|  |             pq.product, | ||||||
|  |             pq.quantity, | ||||||
|  |         ]) | ||||||
|  | 
 | ||||||
|  |     reports.append( Report("Unpaid Products", headings, data)) | ||||||
|  | 
 | ||||||
|  |     # Invoices | ||||||
|  |     headings = ["Invoice ID", "Status", "Value"] | ||||||
|  |     data = [] | ||||||
|  | 
 | ||||||
|  |     invoices = commerce.Invoice.objects.filter( | ||||||
|  |         user=attendee.user, | ||||||
|  |     ) | ||||||
|  |     for invoice in invoices: | ||||||
|  |         data.append([ | ||||||
|  |             invoice.id, invoice.get_status_display(), invoice.value, | ||||||
|  |         ]) | ||||||
|  | 
 | ||||||
|  |     reports.append(Report("Invoices", headings, data, link_view="invoice")) | ||||||
|  | 
 | ||||||
|  |     # Credit Notes | ||||||
|  |     headings = ["Note ID", "Status", "Value"] | ||||||
|  |     data = [] | ||||||
|  | 
 | ||||||
|  |     credit_notes = commerce.CreditNote.objects.filter( | ||||||
|  |         invoice__user=attendee.user, | ||||||
|  |     ) | ||||||
|  |     for credit_note in credit_notes: | ||||||
|  |         data.append([ | ||||||
|  |             credit_note.id, credit_note.status, credit_note.value, | ||||||
|  |         ]) | ||||||
|  | 
 | ||||||
|  |     reports.append( | ||||||
|  |         Report("Credit Notes", headings, data, link_view="credit_note") | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     return reports | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def attendee_list(request): | ||||||
|  |     ''' Returns a list of all attendees. ''' | ||||||
|  | 
 | ||||||
|  |     attendees = people.Attendee.objects.all().select_related( | ||||||
|  |         "attendeeprofilebase", | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     attendees = attendees.values("id", "user__email").annotate( | ||||||
|  |         has_registered=Count( | ||||||
|  |             Q(user__invoice__status=commerce.Invoice.STATUS_PAID) | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     headings = [ | ||||||
|  |         "User ID", "Email", "Has registered", | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     data = [] | ||||||
|  | 
 | ||||||
|  |     for attendee in attendees: | ||||||
|  |         data.append([ | ||||||
|  |             attendee["id"], | ||||||
|  |             attendee["user__email"], | ||||||
|  |             attendee["has_registered"], | ||||||
|  |         ]) | ||||||
|  | 
 | ||||||
|  |     # Sort by whether they've registered, then ID. | ||||||
|  |     data.sort(key=lambda attendee: (-attendee[2], attendee[0])) | ||||||
|  | 
 | ||||||
|  |     return Report("Attendees", headings, data, link_view="attendee") | ||||||
|  |  | ||||||
|  | @ -1,31 +1,12 @@ | ||||||
| from registrasion.models import commerce | from registrasion.models import commerce | ||||||
| from registrasion.models import inventory |  | ||||||
| from registrasion.controllers.category import CategoryController | from registrasion.controllers.category import CategoryController | ||||||
|  | from registrasion.controllers.item import ItemController | ||||||
| 
 | 
 | ||||||
| from collections import namedtuple |  | ||||||
| from django import template | from django import template | ||||||
| from django.db.models import Case |  | ||||||
| from django.db.models import Q |  | ||||||
| from django.db.models import Sum | from django.db.models import Sum | ||||||
| from django.db.models import When |  | ||||||
| from django.db.models import Value |  | ||||||
| 
 | 
 | ||||||
| register = template.Library() | register = template.Library() | ||||||
| 
 | 
 | ||||||
| _ProductAndQuantity = namedtuple("ProductAndQuantity", ["product", "quantity"]) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ProductAndQuantity(_ProductAndQuantity): |  | ||||||
|     ''' Class that holds a product and a quantity. |  | ||||||
| 
 |  | ||||||
|     Attributes: |  | ||||||
|         product (models.inventory.Product) |  | ||||||
| 
 |  | ||||||
|         quantity (int) |  | ||||||
| 
 |  | ||||||
|     ''' |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
| def available_categories(context): | def available_categories(context): | ||||||
|  | @ -67,71 +48,18 @@ def invoices(context): | ||||||
| 
 | 
 | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
| def items_pending(context): | def items_pending(context): | ||||||
|     ''' Gets all of the items that the user has reserved, but has not yet |     ''' Gets all of the items that the user from this context has reserved.''' | ||||||
|     paid for. |     return ItemController(context.request.user).items_pending() | ||||||
| 
 |  | ||||||
|     Returns: |  | ||||||
|         [ProductAndQuantity, ...]: A list of product-quantity pairs for the |  | ||||||
|             items that the user has not yet paid for. |  | ||||||
| 
 |  | ||||||
|     ''' |  | ||||||
| 
 |  | ||||||
|     all_items = commerce.ProductItem.objects.filter( |  | ||||||
|         cart__user=context.request.user, |  | ||||||
|         cart__status=commerce.Cart.STATUS_ACTIVE, |  | ||||||
|     ).select_related( |  | ||||||
|         "product", |  | ||||||
|         "product__category", |  | ||||||
|     ).order_by( |  | ||||||
|         "product__category__order", |  | ||||||
|         "product__order", |  | ||||||
|     ) |  | ||||||
|     return all_items |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @register.assignment_tag(takes_context=True) | @register.assignment_tag(takes_context=True) | ||||||
| def items_purchased(context, category=None): | def items_purchased(context, category=None): | ||||||
|     ''' Aggregates the items that this user has purchased. |     ''' Returns the items purchased for this user. ''' | ||||||
| 
 | 
 | ||||||
|     Arguments: |     return ItemController(context.request.user).items_purchased( | ||||||
|         category (Optional[models.inventory.Category]): the category of items |         category=category | ||||||
|             to restrict to. |  | ||||||
| 
 |  | ||||||
|     Returns: |  | ||||||
|         [ProductAndQuantity, ...]: A list of product-quantity pairs, |  | ||||||
|             aggregating like products from across multiple invoices. |  | ||||||
| 
 |  | ||||||
|     ''' |  | ||||||
| 
 |  | ||||||
|     in_cart = ( |  | ||||||
|         Q(productitem__cart__user=context.request.user) & |  | ||||||
|         Q(productitem__cart__status=commerce.Cart.STATUS_PAID) |  | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     quantities_in_cart = When( |  | ||||||
|         in_cart, |  | ||||||
|         then="productitem__quantity", |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     quantities_or_zero = Case( |  | ||||||
|         quantities_in_cart, |  | ||||||
|         default=Value(0), |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     products = inventory.Product.objects |  | ||||||
| 
 |  | ||||||
|     if category: |  | ||||||
|         products = products.filter(category=category) |  | ||||||
| 
 |  | ||||||
|     products = products.select_related("category") |  | ||||||
|     products = products.annotate(quantity=Sum(quantities_or_zero)) |  | ||||||
|     products = products.filter(quantity__gt=0) |  | ||||||
| 
 |  | ||||||
|     out = [] |  | ||||||
|     for prod in products: |  | ||||||
|         out.append(ProductAndQuantity(prod, prod.quantity)) |  | ||||||
|     return out |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| @register.filter | @register.filter | ||||||
| def multiply(value, arg): | def multiply(value, arg): | ||||||
|  |  | ||||||
|  | @ -37,6 +37,8 @@ public = [ | ||||||
| 
 | 
 | ||||||
| reports = [ | reports = [ | ||||||
|     url(r"^$", reporting_views.reports_list, name="reports_list"), |     url(r"^$", reporting_views.reports_list, name="reports_list"), | ||||||
|  |     url(r"^attendee/?$", reporting_views.attendee, name="attendee"), | ||||||
|  |     url(r"^attendee/([0-9]*)$", reporting_views.attendee, name="attendee"), | ||||||
|     url( |     url( | ||||||
|         r"^credit_notes/?$", |         r"^credit_notes/?$", | ||||||
|         reporting_views.credit_notes, |         reporting_views.credit_notes, | ||||||
|  |  | ||||||
|  | @ -747,6 +747,7 @@ def credit_note(request, note_id, access_code=None): | ||||||
| 
 | 
 | ||||||
|     ''' |     ''' | ||||||
| 
 | 
 | ||||||
|  |     note_id = int(note_id) | ||||||
|     current_note = CreditNoteController.for_id_or_404(note_id) |     current_note = CreditNoteController.for_id_or_404(note_id) | ||||||
| 
 | 
 | ||||||
|     apply_form = forms.ApplyCreditNoteForm( |     apply_form = forms.ApplyCreditNoteForm( | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer