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", | ||||
|         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): | ||||
| 
 | ||||
|     def __init__(self, title, headings, data, link_view=None): | ||||
|         self._title = title | ||||
|         self._headings = headings | ||||
|         self._data = data | ||||
|         self._link_view = link_view | ||||
|  | @ -66,12 +67,15 @@ def report_view(title, form_type=None): | |||
|             else: | ||||
|                 form = None | ||||
| 
 | ||||
|             report = view(request, form, *a, **k) | ||||
|             reports = view(request, form, *a, **k) | ||||
| 
 | ||||
|             if isinstance(reports, Report): | ||||
|                 reports = [reports] | ||||
| 
 | ||||
|             ctx = { | ||||
|                 "title": title, | ||||
|                 "form": form, | ||||
|                 "report": report, | ||||
|                 "reports": reports, | ||||
|             } | ||||
| 
 | ||||
|             return render(request, "registrasion/report.html", ctx) | ||||
|  |  | |||
|  | @ -1,13 +1,16 @@ | |||
| import forms | ||||
| 
 | ||||
| from django.contrib.auth.decorators import user_passes_test | ||||
| from django.core.urlresolvers import reverse | ||||
| from django.db import models | ||||
| 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.shortcuts import render | ||||
| 
 | ||||
| from registrasion import forms | ||||
| from registrasion.controllers.item import ItemController | ||||
| from registrasion.models import commerce | ||||
| from registrasion.models import people | ||||
| from registrasion import views | ||||
| 
 | ||||
| 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") | ||||
| 
 | ||||
| 
 | ||||
| @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 inventory | ||||
| from registrasion.controllers.category import CategoryController | ||||
| from registrasion.controllers.item import ItemController | ||||
| 
 | ||||
| from collections import namedtuple | ||||
| 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 When | ||||
| from django.db.models import Value | ||||
| 
 | ||||
| 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) | ||||
| def available_categories(context): | ||||
|  | @ -67,71 +48,18 @@ def invoices(context): | |||
| 
 | ||||
| @register.assignment_tag(takes_context=True) | ||||
| def items_pending(context): | ||||
|     ''' 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=context.request.user, | ||||
|         cart__status=commerce.Cart.STATUS_ACTIVE, | ||||
|     ).select_related( | ||||
|         "product", | ||||
|         "product__category", | ||||
|     ).order_by( | ||||
|         "product__category__order", | ||||
|         "product__order", | ||||
|     ) | ||||
|     return all_items | ||||
|     ''' Gets all of the items that the user from this context has reserved.''' | ||||
|     return ItemController(context.request.user).items_pending() | ||||
| 
 | ||||
| 
 | ||||
| @register.assignment_tag(takes_context=True) | ||||
| def items_purchased(context, category=None): | ||||
|     ''' Aggregates the items that this user has purchased. | ||||
|     ''' Returns the items purchased for this user. ''' | ||||
| 
 | ||||
|     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=context.request.user) & | ||||
|         Q(productitem__cart__status=commerce.Cart.STATUS_PAID) | ||||
|     return ItemController(context.request.user).items_purchased( | ||||
|         category=category | ||||
|     ) | ||||
| 
 | ||||
|     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 | ||||
| def multiply(value, arg): | ||||
|  |  | |||
|  | @ -37,6 +37,8 @@ public = [ | |||
| 
 | ||||
| reports = [ | ||||
|     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( | ||||
|         r"^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) | ||||
| 
 | ||||
|     apply_form = forms.ApplyCreditNoteForm( | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer