Merge branch 'chrisjrn/20161005'

This commit is contained in:
Christopher Neugebauer 2016-10-05 13:08:08 -07:00
commit 06e61fd92c
2 changed files with 136 additions and 35 deletions

View file

@ -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. '''

View file

@ -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",