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…
Reference in a new issue