Merge branch 'chrisjrn/20161005'
This commit is contained in:
		
						commit
						06e61fd92c
					
				
					 2 changed files with 136 additions and 35 deletions
				
			
		|  | @ -8,6 +8,13 @@ from django import forms | ||||||
| # Reporting forms. | # Reporting forms. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def mix_form(*a): | ||||||
|  |     ''' Creates a new form class out of all the supplied forms ''' | ||||||
|  | 
 | ||||||
|  |     bases = tuple(a) | ||||||
|  |     return type("MixForm", bases, {}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class DiscountForm(forms.Form): | class DiscountForm(forms.Form): | ||||||
|     discount = forms.ModelMultipleChoiceField( |     discount = forms.ModelMultipleChoiceField( | ||||||
|         queryset=conditions.DiscountBase.objects.all(), |         queryset=conditions.DiscountBase.objects.all(), | ||||||
|  | @ -17,7 +24,7 @@ class DiscountForm(forms.Form): | ||||||
| 
 | 
 | ||||||
| class ProductAndCategoryForm(forms.Form): | class ProductAndCategoryForm(forms.Form): | ||||||
|     product = forms.ModelMultipleChoiceField( |     product = forms.ModelMultipleChoiceField( | ||||||
|         queryset=inventory.Product.objects.all(), |         queryset=inventory.Product.objects.select_related("category"), | ||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
|     category = forms.ModelMultipleChoiceField( |     category = forms.ModelMultipleChoiceField( | ||||||
|  | @ -40,6 +47,22 @@ class ProposalKindForm(forms.Form): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class GroupByForm(forms.Form): | ||||||
|  |     GROUP_BY_CATEGORY = "category" | ||||||
|  |     GROUP_BY_PRODUCT = "product" | ||||||
|  | 
 | ||||||
|  |     choices = ( | ||||||
|  |         (GROUP_BY_CATEGORY, "Category"), | ||||||
|  |         (GROUP_BY_PRODUCT, "Product"), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     group_by = forms.ChoiceField( | ||||||
|  |         label="Group by", | ||||||
|  |         choices=choices, | ||||||
|  |         required=False, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def model_fields_form_factory(model): | def model_fields_form_factory(model): | ||||||
|     ''' Creates a form for specifying fields from a model to display. ''' |     ''' Creates a form for specifying fields from a model to display. ''' | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import forms | ||||||
| 
 | 
 | ||||||
| import collections | import collections | ||||||
| import datetime | import datetime | ||||||
|  | import itertools | ||||||
| 
 | 
 | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib.auth.decorators import user_passes_test | from django.contrib.auth.decorators import user_passes_test | ||||||
|  | @ -11,6 +12,7 @@ from django.db import models | ||||||
| from django.db.models import F, Q | 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.shortcuts import render | from django.shortcuts import render | ||||||
| 
 | 
 | ||||||
| from registrasion.controllers.item import ItemController | from registrasion.controllers.item import ItemController | ||||||
|  | @ -318,21 +320,33 @@ def paid_invoices_by_date(request, form): | ||||||
|     categories = form.cleaned_data["category"] |     categories = form.cleaned_data["category"] | ||||||
| 
 | 
 | ||||||
|     invoices = commerce.Invoice.objects.filter( |     invoices = commerce.Invoice.objects.filter( | ||||||
|         Q(lineitem__product__in=products) | Q(lineitem__product__category__in=categories), |         ( | ||||||
|  |             Q(lineitem__product__in=products) | | ||||||
|  |             Q(lineitem__product__category__in=categories) | ||||||
|  |         ), | ||||||
|         status=commerce.Invoice.STATUS_PAID, |         status=commerce.Invoice.STATUS_PAID, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  |     # Invoices with payments will be paid at the time of their latest payment | ||||||
|     payments = commerce.PaymentBase.objects.all() |     payments = commerce.PaymentBase.objects.all() | ||||||
|     payments = payments.filter( |     payments = payments.filter( | ||||||
|         invoice__in=invoices, |         invoice__in=invoices, | ||||||
|     ) |     ) | ||||||
|     payments = payments.order_by("invoice") |     payments = payments.order_by("invoice") | ||||||
|     invoice_max_time = payments.values("invoice").annotate(max_time=Max("time")) |     invoice_max_time = payments.values("invoice").annotate( | ||||||
|  |         max_time=Max("time") | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     # Zero-value invoices will have no payments, so they're paid at issue time | ||||||
|  |     zero_value_invoices = invoices.filter(value=0) | ||||||
|  | 
 | ||||||
|  |     times = itertools.chain( | ||||||
|  |         (line["max_time"] for line in invoice_max_time), | ||||||
|  |         (invoice.issue_time for invoice in zero_value_invoices), | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     by_date = collections.defaultdict(int) |     by_date = collections.defaultdict(int) | ||||||
| 
 |     for time in times: | ||||||
|     for line in invoice_max_time: |  | ||||||
|         time = line["max_time"] |  | ||||||
|         date = datetime.datetime( |         date = datetime.datetime( | ||||||
|             year=time.year, month=time.month, day=time.day |             year=time.year, month=time.month, day=time.day | ||||||
|         ) |         ) | ||||||
|  | @ -440,7 +454,8 @@ def attendee(request, form, user_id=None): | ||||||
|     # Credit Notes |     # Credit Notes | ||||||
|     credit_notes = commerce.CreditNote.objects.filter( |     credit_notes = commerce.CreditNote.objects.filter( | ||||||
|         invoice__user=attendee.user, |         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"], | ||||||
|  | @ -451,7 +466,8 @@ def attendee(request, form, user_id=None): | ||||||
|     # All payments |     # All payments | ||||||
|     payments = commerce.PaymentBase.objects.filter( |     payments = commerce.PaymentBase.objects.filter( | ||||||
|         invoice__user=attendee.user, |         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"], | ||||||
|  | @ -465,11 +481,18 @@ def attendee(request, form, user_id=None): | ||||||
| def attendee_list(request): | def attendee_list(request): | ||||||
|     ''' Returns a list of all attendees. ''' |     ''' Returns a list of all attendees. ''' | ||||||
| 
 | 
 | ||||||
|     attendees = people.Attendee.objects.all().select_related( |     attendees = people.Attendee.objects.select_related( | ||||||
|         "attendeeprofilebase", |         "attendeeprofilebase", | ||||||
|         "user", |         "user", | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  |     profiles = AttendeeProfile.objects.filter( | ||||||
|  |         attendee__in=attendees | ||||||
|  |     ).select_related( | ||||||
|  |         "attendee", "attendee__user", | ||||||
|  |     ) | ||||||
|  |     profiles_by_attendee = dict((i.attendee, i) for i in profiles) | ||||||
|  | 
 | ||||||
|     attendees = attendees.annotate( |     attendees = attendees.annotate( | ||||||
|         has_registered=Count( |         has_registered=Count( | ||||||
|             Q(user__invoice__status=commerce.Invoice.STATUS_PAID) |             Q(user__invoice__status=commerce.Invoice.STATUS_PAID) | ||||||
|  | @ -485,8 +508,8 @@ def attendee_list(request): | ||||||
|     for a in attendees: |     for a in attendees: | ||||||
|         data.append([ |         data.append([ | ||||||
|             a.user.id, |             a.user.id, | ||||||
|             a.attendeeprofilebase.attendee_name() |             (profiles_by_attendee[a].attendee_name() | ||||||
|                 if hasattr(a, "attendeeprofilebase") else "", |                 if a in profiles_by_attendee else ""), | ||||||
|             a.user.email, |             a.user.email, | ||||||
|             a.has_registered > 0, |             a.has_registered > 0, | ||||||
|         ]) |         ]) | ||||||
|  | @ -498,13 +521,12 @@ def attendee_list(request): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ProfileForm = forms.model_fields_form_factory(AttendeeProfile) | ProfileForm = forms.model_fields_form_factory(AttendeeProfile) | ||||||
| class ProductCategoryProfileForm(forms.ProductAndCategoryForm, ProfileForm): |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| @report_view( | @report_view( | ||||||
|     "Attendees By Product/Category", |     "Attendees By Product/Category", | ||||||
|     form_type=ProductCategoryProfileForm, |     form_type=forms.mix_form( | ||||||
|  |         forms.ProductAndCategoryForm, ProfileForm, forms.GroupByForm | ||||||
|  |     ), | ||||||
| ) | ) | ||||||
| def attendee_data(request, form, user_id=None): | def attendee_data(request, form, user_id=None): | ||||||
|     ''' Lists attendees for a given product/category selection along with |     ''' Lists attendees for a given product/category selection along with | ||||||
|  | @ -518,6 +540,8 @@ def attendee_data(request, form, user_id=None): | ||||||
| 
 | 
 | ||||||
|     output = [] |     output = [] | ||||||
| 
 | 
 | ||||||
|  |     by_category = form.cleaned_data["group_by"] == forms.GroupByForm.GROUP_BY_CATEGORY | ||||||
|  | 
 | ||||||
|     products = form.cleaned_data["product"] |     products = form.cleaned_data["product"] | ||||||
|     categories = form.cleaned_data["category"] |     categories = form.cleaned_data["category"] | ||||||
|     fields = form.cleaned_data["fields"] |     fields = form.cleaned_data["fields"] | ||||||
|  | @ -528,26 +552,65 @@ def attendee_data(request, form, user_id=None): | ||||||
|     ).exclude( |     ).exclude( | ||||||
|         cart__status=commerce.Cart.STATUS_RELEASED |         cart__status=commerce.Cart.STATUS_RELEASED | ||||||
|     ).select_related( |     ).select_related( | ||||||
|         "cart", "product" |         "cart", "cart__user", "product", "product__category", | ||||||
|     ).order_by("cart__status") |     ).order_by("cart__status") | ||||||
| 
 | 
 | ||||||
|  |     # Make sure we select all of the related fields | ||||||
|  |     related_fields = set( | ||||||
|  |         field for field in fields | ||||||
|  |         if isinstance(AttendeeProfile._meta.get_field(field), RelatedField) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|     # Get all of the relevant attendee profiles in one hit. |     # Get all of the relevant attendee profiles in one hit. | ||||||
|     profiles = AttendeeProfile.objects.filter( |     profiles = AttendeeProfile.objects.filter( | ||||||
|         attendee__user__cart__productitem__in=items |         attendee__user__cart__productitem__in=items | ||||||
|     ).select_related("attendee__user") |     ).select_related("attendee__user").prefetch_related(*related_fields) | ||||||
|     by_user = {} |     by_user = {} | ||||||
|     for profile in profiles: |     for profile in profiles: | ||||||
|         by_user[profile.attendee.user] = profile |         by_user[profile.attendee.user] = profile | ||||||
| 
 | 
 | ||||||
|  |     cart = "attendee__user__cart" | ||||||
|  |     cart_status = cart + "__status" | ||||||
|  |     product = cart + "__productitem__product" | ||||||
|  |     product_name = product + "__name" | ||||||
|  |     category = product + "__category" | ||||||
|  |     category_name = category + "__name" | ||||||
|  | 
 | ||||||
|  |     if by_category: | ||||||
|  |         grouping_fields = (category, category_name) | ||||||
|  |         order_by = (category, ) | ||||||
|  |         first_column = "Category" | ||||||
|  |         group_name = lambda i: "%s" % (i[category_name], ) | ||||||
|  |     else: | ||||||
|  |         grouping_fields = (product, product_name, category_name) | ||||||
|  |         order_by = (category, ) | ||||||
|  |         first_column = "Product" | ||||||
|  |         group_name = lambda i: "%s - %s" % (i[category_name], i[product_name]) | ||||||
|  | 
 | ||||||
|     # Group the responses per-field. |     # Group the responses per-field. | ||||||
|     for field in fields: |     for field in fields: | ||||||
|         field_verbose = AttendeeProfile._meta.get_field(field).verbose_name |         concrete_field = AttendeeProfile._meta.get_field(field) | ||||||
|  |         field_verbose = concrete_field.verbose_name | ||||||
| 
 | 
 | ||||||
|         cart = "attendee__user__cart" |         # Render the correct values for related fields | ||||||
|         cart_status = cart + "__status" |         if field in related_fields: | ||||||
|         product = cart + "__productitem__product" |             # Get all of the IDs that will appear | ||||||
|         product_name = product + "__name" |             all_ids = profiles.order_by(field).values(field) | ||||||
|         category_name = product + "__category__name" |             all_ids = [i[field] for i in all_ids if i[field] is not None] | ||||||
|  |             # Get all of the concrete objects for those IDs | ||||||
|  |             model = concrete_field.related_model | ||||||
|  |             all_objects = model.objects.filter(id__in=all_ids) | ||||||
|  |             all_objects_by_id = dict((i.id, i) for i in all_objects) | ||||||
|  | 
 | ||||||
|  |             # Define a function to render those IDs. | ||||||
|  |             def display_field(value): | ||||||
|  |                 if value in all_objects_by_id: | ||||||
|  |                     return all_objects_by_id[value] | ||||||
|  |                 else: | ||||||
|  |                     return None | ||||||
|  |         else: | ||||||
|  |             def display_field(value): | ||||||
|  |                 return value | ||||||
| 
 | 
 | ||||||
|         status_count = lambda status: Case(When( |         status_count = lambda status: Case(When( | ||||||
|                 attendee__user__cart__status=status, |                 attendee__user__cart__status=status, | ||||||
|  | @ -559,23 +622,25 @@ def attendee_data(request, form, user_id=None): | ||||||
|         paid_count = status_count(commerce.Cart.STATUS_PAID) |         paid_count = status_count(commerce.Cart.STATUS_PAID) | ||||||
|         unpaid_count = status_count(commerce.Cart.STATUS_ACTIVE) |         unpaid_count = status_count(commerce.Cart.STATUS_ACTIVE) | ||||||
| 
 | 
 | ||||||
|         p = profiles.order_by(product, field).values( |         groups = profiles.order_by( | ||||||
|             product, product_name, category_name, field |             *(order_by + (field, )) | ||||||
|  |         ).values( | ||||||
|  |             *(grouping_fields + (field, )) | ||||||
|         ).annotate( |         ).annotate( | ||||||
|             paid_count=Sum(paid_count), |             paid_count=Sum(paid_count), | ||||||
|             unpaid_count=Sum(unpaid_count), |             unpaid_count=Sum(unpaid_count), | ||||||
|         ) |         ) | ||||||
|         output.append(ListReport( |         output.append(ListReport( | ||||||
|             "Grouped by %s" % field_verbose, |             "Grouped by %s" % field_verbose, | ||||||
|             ["Product", field_verbose, "paid", "unpaid"], |             [first_column, field_verbose, "paid", "unpaid"], | ||||||
|             [ |             [ | ||||||
|                 ( |                 ( | ||||||
|                     "%s - %s" % (i[category_name], i[product_name]), |                     group_name(group), | ||||||
|                     i[field], |                     display_field(group[field]), | ||||||
|                     i["paid_count"] or 0, |                     group["paid_count"] or 0, | ||||||
|                     i["unpaid_count"] or 0, |                     group["unpaid_count"] or 0, | ||||||
|                 ) |                 ) | ||||||
|                 for i in p |                 for group in groups | ||||||
|             ], |             ], | ||||||
|         )) |         )) | ||||||
| 
 | 
 | ||||||
|  | @ -585,6 +650,15 @@ def attendee_data(request, form, user_id=None): | ||||||
|         AttendeeProfile._meta.get_field(field).verbose_name for field in fields |         AttendeeProfile._meta.get_field(field).verbose_name for field in fields | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|  |     def display_field(profile, field): | ||||||
|  |         field_type = AttendeeProfile._meta.get_field(field) | ||||||
|  |         attr = getattr(profile, field) | ||||||
|  | 
 | ||||||
|  |         if isinstance(field_type, models.ManyToManyField): | ||||||
|  |             return [str(i) for i in attr.all()] or "" | ||||||
|  |         else: | ||||||
|  |             return attr | ||||||
|  | 
 | ||||||
|     headings = ["User ID", "Name", "Email", "Product", "Item Status"] + field_names |     headings = ["User ID", "Name", "Email", "Product", "Item Status"] + field_names | ||||||
|     data = [] |     data = [] | ||||||
|     for item in items: |     for item in items: | ||||||
|  | @ -596,7 +670,7 @@ def attendee_data(request, form, user_id=None): | ||||||
|             item.product, |             item.product, | ||||||
|             status_display[item.cart.status], |             status_display[item.cart.status], | ||||||
|         ] + [ |         ] + [ | ||||||
|             getattr(profile, field) for field in fields |             display_field(profile, field) for field in fields | ||||||
|         ] |         ] | ||||||
|         data.append(line) |         data.append(line) | ||||||
| 
 | 
 | ||||||
|  | @ -616,7 +690,7 @@ def speaker_registrations(request, form): | ||||||
|     kinds = form.cleaned_data["kind"] |     kinds = form.cleaned_data["kind"] | ||||||
| 
 | 
 | ||||||
|     presentations = schedule_models.Presentation.objects.filter( |     presentations = schedule_models.Presentation.objects.filter( | ||||||
|         proposal_base__kind=kinds, |         proposal_base__kind__in=kinds, | ||||||
|     ).exclude( |     ).exclude( | ||||||
|         cancelled=True, |         cancelled=True, | ||||||
|     ) |     ) | ||||||
|  | @ -628,9 +702,13 @@ def speaker_registrations(request, form): | ||||||
| 
 | 
 | ||||||
|     paid_carts = commerce.Cart.objects.filter(status=commerce.Cart.STATUS_PAID) |     paid_carts = commerce.Cart.objects.filter(status=commerce.Cart.STATUS_PAID) | ||||||
| 
 | 
 | ||||||
|     paid_carts = Case(When(cart__in=paid_carts, then=Value(1)), default=Value(0), output_field=models.IntegerField()) |     paid_carts = Case( | ||||||
|  |         When(cart__in=paid_carts, then=Value(1)), | ||||||
|  |         default=Value(0), | ||||||
|  |         output_field=models.IntegerField(), | ||||||
|  |     ) | ||||||
|     users = users.annotate(paid_carts=Sum(paid_carts)) |     users = users.annotate(paid_carts=Sum(paid_carts)) | ||||||
|     users=users.order_by("paid_carts") |     users = users.order_by("paid_carts") | ||||||
| 
 | 
 | ||||||
|     return QuerysetReport( |     return QuerysetReport( | ||||||
|         "Speaker Registration Status", |         "Speaker Registration Status", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer