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