Merge branch 'chrisjrn/20161207'

This commit is contained in:
Christopher Neugebauer 2016-12-07 18:05:03 +11:00
commit f4e11ee53f
6 changed files with 161 additions and 9 deletions

View file

@ -4,6 +4,7 @@ from registrasion.models import inventory
from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Q
class ApplyCreditNoteForm(forms.Form):
@ -14,22 +15,39 @@ class ApplyCreditNoteForm(forms.Form):
self.user = user
super(ApplyCreditNoteForm, self).__init__(*a, **k)
self.fields["invoice"].choices = self._unpaid_invoices_for_user
self.fields["invoice"].choices = self._unpaid_invoices
def _unpaid_invoices_for_user(self):
def _unpaid_invoices(self):
invoices = commerce.Invoice.objects.filter(
status=commerce.Invoice.STATUS_UNPAID,
user=self.user,
)
).select_related("user")
invoices_annotated = [invoice.__dict__ for invoice in invoices]
users = dict((inv.user.id, inv.user) for inv in invoices)
for invoice in invoices_annotated:
invoice.update({
"user_id": users[invoice["user_id"]].id,
"user_email": users[invoice["user_id"]].email,
})
print invoice
key = lambda inv: (0 - (inv["user_id"] == self.user.id), inv["id"])
invoices_annotated.sort(key=key)
template = "Invoice %(id)d - user: %(user_email)s (%(user_id)d) - $%(value)d"
return [
(invoice.id, "Invoice %(id)d - $%(value)d" % invoice.__dict__)
for invoice in invoices
(invoice["id"], template % invoice)
for invoice in invoices_annotated
]
invoice = forms.ChoiceField(
required=True,
)
verify = forms.BooleanField(
required=True,
help_text="Have you verified that this is the correct invoice?",
)
class CancellationFeeForm(forms.Form):
@ -394,3 +412,39 @@ def staff_products_formset_factory(user):
''' Creates a formset of StaffProductsForm for the given user. '''
form_type = staff_products_form_factory(user)
return forms.formset_factory(form_type)
class InvoiceNagForm(forms.Form):
invoice = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=commerce.Invoice.objects.all(),
)
from_email = forms.CharField()
subject = forms.CharField()
body = forms.CharField(
widget=forms.Textarea,
)
def __init__(self, *a, **k):
category = k.pop('category', None) or []
product = k.pop('product', None) or []
category = [int(i) for i in category]
product = [int(i) for i in product]
super(InvoiceNagForm, self).__init__(*a, **k)
qs = commerce.Invoice.objects.filter(
status=commerce.Invoice.STATUS_UNPAID,
).filter(
Q(lineitem__product__category__in=category) |
Q(lineitem__product__in=product)
)
# Uniqify
qs = commerce.Invoice.objects.filter(
id__in=qs,
)
self.fields['invoice'].queryset = qs
self.fields['invoice'].initial = [i.id for i in qs]

View file

@ -152,7 +152,9 @@ class Invoice(models.Model):
]
def __str__(self):
return "Invoice #%d" % self.id
return "Invoice #%d (to: %s, due: %s, value: %s)" % (
self.id, self.user.email, self.due_time, self.value
)
def clean(self):
if self.cart is not None and self.cart_revision is None:

View file

@ -213,8 +213,21 @@ def report_view(title, form_type=None):
class ReportView(object):
''' View objects that can render report data into HTML or CSV. '''
def __init__(self, inner_view, title, form_type):
'''
Arguments:
inner_view: Callable that returns either a Report or a sequence of
Report objects.
title: The title that appears at the top of all of the reports.
form_type: A Form class that can be used to query the report.
'''
# Consolidate form_type so it has content type and section
self.inner_view = inner_view
self.title = title
@ -226,6 +239,8 @@ class ReportView(object):
def get_form(self, request):
''' Creates an instance of self.form_type using request.GET '''
# Create a form instance
if self.form_type is not None:
form = self.form_type(request.GET)
@ -239,6 +254,10 @@ class ReportView(object):
@classmethod
def wrap_reports(cls, reports, content_type):
''' Wraps the reports in a _ReportTemplateWrapper for the given
content_type -- this allows data to be returned as HTML links, for
instance. '''
reports = [
_ReportTemplateWrapper(content_type, report)
for report in reports
@ -247,6 +266,16 @@ class ReportView(object):
return reports
def render(self, data):
''' Renders the reports based on data.content_type's value.
Arguments:
data (ReportViewRequestData): The report data. data.content_type
is used to determine how the reports are rendered.
Returns:
HTTPResponse: The rendered version of the report.
'''
renderers = {
"text/csv": self._render_as_csv,
"text/html": self._render_as_html,
@ -280,8 +309,20 @@ class ReportView(object):
class ReportViewRequestData(object):
'''
Attributes:
form (Form): form based on request
reports ([Report, ...]): The reports rendered from the request
Arguments:
report_view (ReportView): The ReportView to call back to.
request (HTTPRequest): A django HTTP request
'''
def __init__(self, report_view, request, *a, **k):
self.report_view = report_view
self.request = request
@ -293,6 +334,9 @@ class ReportViewRequestData(object):
self.section = request.GET.get("section")
self.section = int(self.section) if self.section else None
if self.content_type is None:
self.content_type = "text/html"
# Reports come from calling the inner view
reports = report_view.inner_view(request, self.form, *a, **k)

View file

@ -408,11 +408,13 @@ def attendee(request, form, user_id=None):
''' Returns a list of all manifested attendees if no attendee is specified,
else displays the attendee manifest. '''
if user_id is None and form.cleaned_data["user"] is not None:
user_id = form.cleaned_data["user"]
if user_id is None:
return attendee_list(request)
if form.cleaned_data["user"] is not None:
user_id = form.cleaned_data["user"]
print user_id
attendee = people.Attendee.objects.get(user__id=user_id)
name = attendee.attendeeprofilebase.attendee_name()
@ -589,6 +591,16 @@ def attendee_data(request, form, user_id=None):
"cart", "cart__user", "product", "product__category",
).order_by("cart__status")
# Add invoice nag link
links = []
links.append((
reverse(views.nag_unpaid, args=[]) + "?" + request.META["QUERY_STRING"],
"Send invoice reminders",
))
if items.count() > 0:
output.append(Links("Actions", links))
# Make sure we select all of the related fields
related_fields = set(
field for field in fields

View file

@ -13,6 +13,7 @@ from .views import (
invoice,
invoice_access,
manual_payment,
nag_unpaid,
product_category,
refund,
review,
@ -34,6 +35,7 @@ public = [
refund, name="refund"),
url(r"^invoice_access/([A-Z0-9]+)$", invoice_access,
name="invoice_access"),
url(r"^nag_unpaid$", nag_unpaid, name="nag_unpaid"),
url(r"^profile$", edit_profile, name="attendee_edit"),
url(r"^register$", guided_registration, name="guided_registration"),
url(r"^review$", review, name="review"),

View file

@ -26,9 +26,11 @@ from django.contrib.auth.models import User
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.core.mail import send_mass_mail
from django.http import Http404
from django.shortcuts import redirect
from django.shortcuts import render
from django.template import Context, Template
_GuidedRegistrationSection = namedtuple(
@ -916,3 +918,39 @@ def extend_reservation(request, user_id, days=7):
cart.extend_reservation(datetime.timedelta(days=days))
return redirect(request.META["HTTP_REFERER"])
@user_passes_test(_staff_only)
def nag_unpaid(request):
''' Allows staff to nag users with unpaid invoices. '''
category = request.GET.getlist("category", [])
product = request.GET.getlist("product", [])
form = forms.InvoiceNagForm(
request.POST or None,
category=category,
product=product,
)
if form.is_valid():
emails = []
for invoice in form.cleaned_data["invoice"]:
# datatuple = (subject, message, from_email, recipient_list)
from_email = form.cleaned_data["from_email"]
subject = form.cleaned_data["subject"]
body = Template(form.cleaned_data["body"]).render(
Context({
"invoice" : invoice,
})
)
recipient_list = [invoice.user.email]
emails.append((subject, body, from_email, recipient_list))
send_mass_mail(emails)
messages.info(request, "The e-mails have been sent.")
data = {
"form": form,
}
return render(request, "registrasion/nag_unpaid.html", data)