Merge branch 'chrisjrn/better_reports'
This commit is contained in:
		
						commit
						4ad96286c3
					
				
					 3 changed files with 220 additions and 94 deletions
				
			
		|  | @ -4,6 +4,7 @@ from django import forms | |||
| 
 | ||||
| # Staff-facing forms. | ||||
| 
 | ||||
| 
 | ||||
| class ProductAndCategoryForm(forms.Form): | ||||
|     product = forms.ModelMultipleChoiceField( | ||||
|         queryset=inventory.Product.objects.all(), | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| from django.contrib.auth.decorators import user_passes_test | ||||
| from django.shortcuts import render | ||||
| from django.core.urlresolvers import reverse | ||||
| from functools import wraps | ||||
| 
 | ||||
| from registrasion import views | ||||
|  | @ -12,34 +13,172 @@ _all_report_views = [] | |||
| 
 | ||||
| class Report(object): | ||||
| 
 | ||||
|     def __init__(self, title, headings, data, link_view=None): | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
|     def title(): | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def headings(): | ||||
|         ''' Returns the headings for the report. ''' | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def rows(content_type): | ||||
|         ''' | ||||
| 
 | ||||
|         Arguments: | ||||
|             content_type (str): The content-type for the output format of this | ||||
|             report. | ||||
| 
 | ||||
|         Returns: | ||||
|             An iterator, which yields each row of the data. Each row should | ||||
|             be an iterable containing the cells, rendered appropriately for | ||||
|             content_type. | ||||
|         ''' | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def _linked_text(self, content_type, address, text): | ||||
|         ''' | ||||
| 
 | ||||
|         Returns: | ||||
|             an HTML linked version of text, if the content_type for this report | ||||
|             is HTMLish, otherwise, the text. | ||||
|         ''' | ||||
| 
 | ||||
|         if content_type == "text/html": | ||||
|             return Report._html_link(address, text) | ||||
|         else: | ||||
|             return text | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _html_link(address, text): | ||||
|         return '<a href="%s">%s</a>' % (address, text) | ||||
| 
 | ||||
| 
 | ||||
| class _ReportTemplateWrapper(object): | ||||
|     ''' Used internally to pass `Report` objects to templates. They effectively | ||||
|     are used to specify the content_type for a report. ''' | ||||
| 
 | ||||
|     def __init__(self, content_type, report): | ||||
|         self.content_type = content_type | ||||
|         self.report = report | ||||
| 
 | ||||
|     def title(self): | ||||
|         return self.report.title() | ||||
| 
 | ||||
|     def headings(self): | ||||
|         return self.report.headings() | ||||
| 
 | ||||
|     def rows(self): | ||||
|         return self.report.rows(self.content_type) | ||||
| 
 | ||||
| 
 | ||||
| class BasicReport(Report): | ||||
| 
 | ||||
|     def __init__(self, title, headings, link_view=None): | ||||
|         super(BasicReport, self).__init__() | ||||
|         self._title = title | ||||
|         self._headings = headings | ||||
|         self._data = data | ||||
|         self._link_view = link_view | ||||
| 
 | ||||
|     @property | ||||
|     def title(self): | ||||
|         ''' Returns the title for this report. ''' | ||||
|         return self._title | ||||
| 
 | ||||
|     @property | ||||
|     def headings(self): | ||||
|         ''' Returns the headings for the table. ''' | ||||
|         return self._headings | ||||
| 
 | ||||
|     @property | ||||
|     def data(self): | ||||
|     def cell_text(self, content_type, index, text): | ||||
|         if index > 0 or not self._link_view: | ||||
|             return text | ||||
|         else: | ||||
|             address = self.get_link(text) | ||||
|             return self._linked_text(content_type, address, text) | ||||
| 
 | ||||
|     def get_link(self, argument): | ||||
|         return reverse(self._link_view, args=[argument]) | ||||
| 
 | ||||
| 
 | ||||
| class ListReport(BasicReport): | ||||
| 
 | ||||
|     def __init__(self, title, headings, data, link_view=None): | ||||
|         super(ListReport, self).__init__(title, headings, link_view=link_view) | ||||
|         self._data = data | ||||
| 
 | ||||
|     def rows(self, content_type): | ||||
|         ''' Returns the data rows for the table. ''' | ||||
|         return self._data | ||||
| 
 | ||||
|     @property | ||||
|     def link_view(self): | ||||
|         ''' Returns the URL name or the view callable that can be used to | ||||
|         view the row's detail. The left-most value is passed into `reverse` | ||||
|         as an argument. ''' | ||||
|         for row in self._data: | ||||
|             yield [ | ||||
|                 self.cell_text(content_type, i, cell) | ||||
|                 for i, cell in enumerate(row) | ||||
|             ] | ||||
| 
 | ||||
|         return self._link_view | ||||
| 
 | ||||
| class QuerysetReport(BasicReport): | ||||
| 
 | ||||
|     def __init__(self, title, attributes, queryset, headings=None, | ||||
|                  link_view=None): | ||||
|         super(QuerysetReport, self).__init__( | ||||
|             title, headings, link_view=link_view | ||||
|         ) | ||||
|         self._attributes = attributes | ||||
|         self._queryset = queryset | ||||
| 
 | ||||
|     def headings(self): | ||||
|         if self._headings is not None: | ||||
|             return self._headings | ||||
| 
 | ||||
|         return [ | ||||
|             " ".join(i.split("_")).capitalize() for i in self._attributes | ||||
|         ] | ||||
| 
 | ||||
|     def rows(self, content_type): | ||||
| 
 | ||||
|         def rgetattr(item, attr): | ||||
|             for i in attr.split("__"): | ||||
|                 item = getattr(item, i) | ||||
| 
 | ||||
|             if callable(item): | ||||
|                 try: | ||||
|                     return item() | ||||
|                 except TypeError: | ||||
|                     pass | ||||
| 
 | ||||
|             return item | ||||
| 
 | ||||
|         for row in self._queryset: | ||||
|             yield [ | ||||
|                 self.cell_text(content_type, i, rgetattr(row, attribute)) | ||||
|                 for i, attribute in enumerate(self._attributes) | ||||
|             ] | ||||
| 
 | ||||
| 
 | ||||
| class Links(Report): | ||||
| 
 | ||||
|     def __init__(self, title, links): | ||||
|         ''' | ||||
|         Arguments: | ||||
|             links ([tuple, ...]): a list of 2-tuples: | ||||
|                 (url, link_text) | ||||
| 
 | ||||
|         ''' | ||||
|         self._title = title | ||||
|         self._links = links | ||||
| 
 | ||||
|     def title(self): | ||||
|         return self._title | ||||
| 
 | ||||
|     def headings(self): | ||||
|         return [] | ||||
| 
 | ||||
|     def rows(self, content_type): | ||||
|         print self._links | ||||
|         for url, link_text in self._links: | ||||
|             yield [ | ||||
|                 self._linked_text(content_type, url, link_text) | ||||
|             ] | ||||
| 
 | ||||
| 
 | ||||
| def report_view(title, form_type=None): | ||||
|  | @ -72,6 +211,11 @@ def report_view(title, form_type=None): | |||
|             if isinstance(reports, Report): | ||||
|                 reports = [reports] | ||||
| 
 | ||||
|             reports = [ | ||||
|                 _ReportTemplateWrapper("text/html", report) | ||||
|                 for report in reports | ||||
|             ] | ||||
| 
 | ||||
|             ctx = { | ||||
|                 "title": title, | ||||
|                 "form": form, | ||||
|  |  | |||
|  | @ -14,7 +14,9 @@ from registrasion.models import people | |||
| from registrasion import views | ||||
| 
 | ||||
| from reports import get_all_reports | ||||
| from reports import Report | ||||
| from reports import Links | ||||
| from reports import ListReport | ||||
| from reports import QuerysetReport | ||||
| from reports import report_view | ||||
| 
 | ||||
| 
 | ||||
|  | @ -90,7 +92,7 @@ def items_sold(request, form): | |||
|         "(TOTAL)", "--", "--", total_income, | ||||
|     ]) | ||||
| 
 | ||||
|     return Report("Paid items", headings, data) | ||||
|     return ListReport("Paid items", headings, data) | ||||
| 
 | ||||
| 
 | ||||
| @report_view("Reconcilitation") | ||||
|  | @ -128,7 +130,7 @@ def reconciliation(request, form): | |||
|         sales["total"] - payments["total"] - ucn["total"], | ||||
|     ]) | ||||
| 
 | ||||
|     return Report("Sales and Payments", headings, data) | ||||
|     return ListReport("Sales and Payments", headings, data) | ||||
| 
 | ||||
| 
 | ||||
| @report_view("Product status", form_type=forms.ProductAndCategoryForm) | ||||
|  | @ -211,7 +213,7 @@ def product_status(request, form): | |||
|             item["total_refunded"], | ||||
|         ]) | ||||
| 
 | ||||
|     return Report("Inventory", headings, data) | ||||
|     return ListReport("Inventory", headings, data) | ||||
| 
 | ||||
| 
 | ||||
| @report_view("Credit notes") | ||||
|  | @ -225,20 +227,13 @@ def credit_notes(request, form): | |||
|         "invoice__user__attendee__attendeeprofilebase", | ||||
|     ) | ||||
| 
 | ||||
|     headings = [ | ||||
|         "id", "Owner", "Status", "Value", | ||||
|     ] | ||||
| 
 | ||||
|     data = [] | ||||
|     for note in notes: | ||||
|         data.append([ | ||||
|             note.id, | ||||
|             note.invoice.user.attendee.attendeeprofilebase.invoice_recipient(), | ||||
|             note.status, | ||||
|             note.value, | ||||
|         ]) | ||||
| 
 | ||||
|     return Report("Credit Notes", headings, data, link_view="credit_note") | ||||
|     return QuerysetReport( | ||||
|         "Credit Notes", | ||||
|         ["id", "invoice__user__attendee__attendeeprofilebase__invoice_recipient", "status", "value"],  # NOQA | ||||
|         notes, | ||||
|         headings=["id", "Owner", "Status", "Value"], | ||||
|         link_view=views.credit_note, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @report_view("Attendee", form_type=forms.UserIdForm) | ||||
|  | @ -253,82 +248,63 @@ def attendee(request, form, user_id=None): | |||
|         user_id = form.cleaned_data["user"] | ||||
| 
 | ||||
|     attendee = people.Attendee.objects.get(user__id=user_id) | ||||
|     name = attendee.attendeeprofilebase.attendee_name() | ||||
| 
 | ||||
|     reports = [] | ||||
| 
 | ||||
|     # TODO: METADATA. | ||||
|     links = [] | ||||
|     links.append(( | ||||
|         reverse(views.amend_registration, args=[user_id]), | ||||
|         "Amend current cart", | ||||
|     )) | ||||
|     reports.append(Links("Actions for " + name, links)) | ||||
| 
 | ||||
|     # Paid and pending  products | ||||
|     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)) | ||||
|     reports.append(ListReport( | ||||
|         "Paid Products", | ||||
|         ["Product", "Quantity"], | ||||
|         [(pq.product, pq.quantity) for pq in ic.items_purchased()], | ||||
|     )) | ||||
|     reports.append(ListReport( | ||||
|         "Unpaid Products", | ||||
|         ["Product", "Quantity"], | ||||
|         [(pq.product, pq.quantity) for pq in ic.items_pending()], | ||||
|     )) | ||||
| 
 | ||||
|     # 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")) | ||||
|     reports.append(QuerysetReport( | ||||
|         "Invoices", | ||||
|         ["id", "get_status_display", "value"], | ||||
|         invoices, | ||||
|         headings=["Invoice ID", "Status", "Value"], | ||||
|         link_view=views.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") | ||||
|     ) | ||||
|     reports.append(QuerysetReport( | ||||
|         "Credit Notes", | ||||
|         ["id", "status", "value"], | ||||
|         credit_notes, | ||||
|         link_view=views.credit_note, | ||||
|     )) | ||||
| 
 | ||||
|     # All payments | ||||
|     headings = ["To Invoice", "Payment ID", "Reference", "Amount"] | ||||
|     data = [] | ||||
| 
 | ||||
|     payments = commerce.PaymentBase.objects.filter( | ||||
|         invoice__user=attendee.user, | ||||
|     ) | ||||
|     for payment in payments: | ||||
|         data.append([ | ||||
|             payment.invoice.id, payment.id, payment.reference, payment.amount, | ||||
|         ]) | ||||
| 
 | ||||
|     reports.append( | ||||
|         Report("Payments", headings, data, link_view="invoice") | ||||
|     ) | ||||
| 
 | ||||
|     reports.append(QuerysetReport( | ||||
|         "Payments", | ||||
|         ["invoice__id", "id", "reference", "amount"], | ||||
|         payments, | ||||
|         link_view=views.invoice, | ||||
|     )) | ||||
| 
 | ||||
|     return reports | ||||
| 
 | ||||
|  | @ -353,15 +329,20 @@ def attendee_list(request): | |||
| 
 | ||||
|     data = [] | ||||
| 
 | ||||
|     for attendee in attendees: | ||||
|     for a in attendees: | ||||
|         data.append([ | ||||
|             attendee.user.id, | ||||
|             attendee.attendeeprofilebase.attendee_name(), | ||||
|             attendee.user.email, | ||||
|             attendee.has_registered > 0, | ||||
|             a.user.id, | ||||
|             a.attendeeprofilebase.attendee_name(), | ||||
|             a.user.email, | ||||
|             a.has_registered > 0, | ||||
|         ]) | ||||
| 
 | ||||
|     # Sort by whether they've registered, then ID. | ||||
|     data.sort(key=lambda attendee: (-attendee[3], attendee[0])) | ||||
|     data.sort(key=lambda a: (-a[3], a[0])) | ||||
| 
 | ||||
|     return Report("Attendees", headings, data, link_view="attendee") | ||||
|     class Report(ListReport): | ||||
| 
 | ||||
|         def get_link(self, argument): | ||||
|             return reverse(self._link_view) + "?user=%d" % int(argument) | ||||
| 
 | ||||
|     return Report("Attendees", headings, data, link_view=attendee) | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer