From 2ed0a47f15bde1dfd1bdc4281043d21d0e99d3c9 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Tue, 20 Sep 2016 13:36:49 +1000 Subject: [PATCH] Adds attendance by field report Fixes #93 --- registrasion/reporting/forms.py | 19 ++++++ registrasion/reporting/views.py | 103 ++++++++++++++++++++++++++++++-- registrasion/urls.py | 1 + 3 files changed, 119 insertions(+), 4 deletions(-) diff --git a/registrasion/reporting/forms.py b/registrasion/reporting/forms.py index 8543209b..2e983491 100644 --- a/registrasion/reporting/forms.py +++ b/registrasion/reporting/forms.py @@ -29,3 +29,22 @@ class UserIdForm(forms.Form): label="User ID", required=False, ) + + +def model_fields_form_factory(model): + ''' Creates a form for specifying fields from a model to display. ''' + + fields = model._meta.get_fields() + + choices = [] + for field in fields: + if hasattr(field, "verbose_name"): + choices.append((field.name, field.verbose_name)) + + class ModelFieldsForm(forms.Form): + fields = forms.MultipleChoiceField( + choices=choices, + required=False, + ) + + return ModelFieldsForm diff --git a/registrasion/reporting/views.py b/registrasion/reporting/views.py index 3e2fa514..628102b7 100644 --- a/registrasion/reporting/views.py +++ b/registrasion/reporting/views.py @@ -364,6 +364,12 @@ def credit_notes(request, form): ) +class AttendeeListReport(ListReport): + + def get_link(self, argument): + return reverse(self._link_view) + "?user=%d" % int(argument) + + @report_view("Attendee", form_type=forms.UserIdForm) def attendee(request, form, user_id=None): ''' Returns a list of all manifested attendees if no attendee is specified, @@ -484,9 +490,98 @@ def attendee_list(request): # Sort by whether they've registered, then ID. data.sort(key=lambda a: (-a[3], a[0])) - class Report(ListReport): + return AttendeeListReport("Attendees", headings, data, link_view=attendee) - def get_link(self, argument): - return reverse(self._link_view) + "?user=%d" % int(argument) - return Report("Attendees", headings, data, link_view=attendee) +ProfileForm = forms.model_fields_form_factory(AttendeeProfile) +class ProductCategoryProfileForm(forms.ProductAndCategoryForm, ProfileForm): + pass + + +@report_view( + "Attendees By Product/Category", + form_type=ProductCategoryProfileForm, +) +def attendee_data(request, form, user_id=None): + ''' Lists attendees for a given product/category selection along with + profile data.''' + + status_display = { + commerce.Cart.STATUS_ACTIVE: "Unpaid", + commerce.Cart.STATUS_PAID: "Paid", + commerce.Cart.STATUS_RELEASED: "Refunded", + } + + output = [] + + products = form.cleaned_data["product"] + categories = form.cleaned_data["category"] + fields = form.cleaned_data["fields"] + name_field = AttendeeProfile.name_field() + + items = commerce.ProductItem.objects.filter( + Q(product__in=products) | Q(product__category__in=categories), + ).exclude( + cart__status=commerce.Cart.STATUS_RELEASED + ).select_related( + "cart", "product" + ).order_by("cart__status") + + # Get all of the relevant attendee profiles in one hit. + profiles = AttendeeProfile.objects.filter( + attendee__user__cart__productitem__in=items + ).select_related("attendee__user") + by_user = {} + for profile in profiles: + by_user[profile.attendee.user] = profile + + for field in fields: + field_verbose = AttendeeProfile._meta.get_field(field).verbose_name + + cart = "attendee__user__cart" + cart_status = cart + "__status" + product = cart + "__productitem__product" + product_name = product + "__name" + category_name = product + "__category__name" + + p = profiles.order_by(product, field).values( + cart_status, product, product_name, category_name, field + ).annotate(count=Count("id")) + output.append(ListReport( + "Grouped by %s" % field_verbose, + ["Product", "Status", field_verbose, "count"], + [ + ( + "%s - %s" % (i[category_name], i[product_name]), + status_display[i[cart_status]], + i[field], + i["count"] or 0, + ) + for i in p + ], + )) + + # DO the report for individual attendees + + field_names = [ + AttendeeProfile._meta.get_field(field).verbose_name for field in fields + ] + + headings = ["User ID", "Name", "Product", "Item Status"] + field_names + data = [] + for item in items: + profile = by_user[item.cart.user] + line = [ + item.cart.user.id, + getattr(profile, name_field), + item.product, + status_display[item.cart.status], + ] + [ + getattr(profile, field) for field in fields + ] + data.append(line) + + output.append(AttendeeListReport( + "Attendees by item with profile data", headings, data, link_view=attendee + )) + return output diff --git a/registrasion/urls.py b/registrasion/urls.py index d6850ef7..d7e440df 100644 --- a/registrasion/urls.py +++ b/registrasion/urls.py @@ -43,6 +43,7 @@ public = [ reports = [ url(r"^$", rv.reports_list, name="reports_list"), url(r"^attendee/?$", rv.attendee, name="attendee"), + 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"^discount_status/?$", rv.discount_status, name="discount_status"),