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 import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Q
class ApplyCreditNoteForm(forms.Form): class ApplyCreditNoteForm(forms.Form):
@ -14,22 +15,39 @@ class ApplyCreditNoteForm(forms.Form):
self.user = user self.user = user
super(ApplyCreditNoteForm, self).__init__(*a, **k) 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( invoices = commerce.Invoice.objects.filter(
status=commerce.Invoice.STATUS_UNPAID, 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 [ return [
(invoice.id, "Invoice %(id)d - $%(value)d" % invoice.__dict__) (invoice["id"], template % invoice)
for invoice in invoices for invoice in invoices_annotated
] ]
invoice = forms.ChoiceField( invoice = forms.ChoiceField(
required=True, required=True,
) )
verify = forms.BooleanField(
required=True,
help_text="Have you verified that this is the correct invoice?",
)
class CancellationFeeForm(forms.Form): class CancellationFeeForm(forms.Form):
@ -394,3 +412,39 @@ def staff_products_formset_factory(user):
''' Creates a formset of StaffProductsForm for the given user. ''' ''' Creates a formset of StaffProductsForm for the given user. '''
form_type = staff_products_form_factory(user) form_type = staff_products_form_factory(user)
return forms.formset_factory(form_type) 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): 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): def clean(self):
if self.cart is not None and self.cart_revision is None: 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): class ReportView(object):
''' View objects that can render report data into HTML or CSV. '''
def __init__(self, inner_view, title, form_type): 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 # Consolidate form_type so it has content type and section
self.inner_view = inner_view self.inner_view = inner_view
self.title = title self.title = title
@ -226,6 +239,8 @@ class ReportView(object):
def get_form(self, request): def get_form(self, request):
''' Creates an instance of self.form_type using request.GET '''
# Create a form instance # Create a form instance
if self.form_type is not None: if self.form_type is not None:
form = self.form_type(request.GET) form = self.form_type(request.GET)
@ -239,6 +254,10 @@ class ReportView(object):
@classmethod @classmethod
def wrap_reports(cls, reports, content_type): 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 = [ reports = [
_ReportTemplateWrapper(content_type, report) _ReportTemplateWrapper(content_type, report)
for report in reports for report in reports
@ -247,6 +266,16 @@ class ReportView(object):
return reports return reports
def render(self, data): 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 = { renderers = {
"text/csv": self._render_as_csv, "text/csv": self._render_as_csv,
"text/html": self._render_as_html, "text/html": self._render_as_html,
@ -280,8 +309,20 @@ class ReportView(object):
class ReportViewRequestData(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): def __init__(self, report_view, request, *a, **k):
self.report_view = report_view self.report_view = report_view
self.request = request self.request = request
@ -293,6 +334,9 @@ class ReportViewRequestData(object):
self.section = request.GET.get("section") self.section = request.GET.get("section")
self.section = int(self.section) if self.section else None 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 come from calling the inner view
reports = report_view.inner_view(request, self.form, *a, **k) 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, ''' Returns a list of all manifested attendees if no attendee is specified,
else displays the attendee manifest. ''' 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: if user_id is None:
return attendee_list(request) return attendee_list(request)
if form.cleaned_data["user"] is not None: print user_id
user_id = form.cleaned_data["user"]
attendee = people.Attendee.objects.get(user__id=user_id) attendee = people.Attendee.objects.get(user__id=user_id)
name = attendee.attendeeprofilebase.attendee_name() name = attendee.attendeeprofilebase.attendee_name()
@ -589,6 +591,16 @@ def attendee_data(request, form, user_id=None):
"cart", "cart__user", "product", "product__category", "cart", "cart__user", "product", "product__category",
).order_by("cart__status") ).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 # Make sure we select all of the related fields
related_fields = set( related_fields = set(
field for field in fields field for field in fields

View file

@ -13,6 +13,7 @@ from .views import (
invoice, invoice,
invoice_access, invoice_access,
manual_payment, manual_payment,
nag_unpaid,
product_category, product_category,
refund, refund,
review, review,
@ -34,6 +35,7 @@ public = [
refund, name="refund"), refund, name="refund"),
url(r"^invoice_access/([A-Z0-9]+)$", invoice_access, url(r"^invoice_access/([A-Z0-9]+)$", invoice_access,
name="invoice_access"), name="invoice_access"),
url(r"^nag_unpaid$", nag_unpaid, name="nag_unpaid"),
url(r"^profile$", edit_profile, name="attendee_edit"), url(r"^profile$", edit_profile, name="attendee_edit"),
url(r"^register$", guided_registration, name="guided_registration"), url(r"^register$", guided_registration, name="guided_registration"),
url(r"^review$", review, name="review"), 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.contrib import messages
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import send_mass_mail
from django.http import Http404 from django.http import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.shortcuts import render from django.shortcuts import render
from django.template import Context, Template
_GuidedRegistrationSection = namedtuple( _GuidedRegistrationSection = namedtuple(
@ -916,3 +918,39 @@ def extend_reservation(request, user_id, days=7):
cart.extend_reservation(datetime.timedelta(days=days)) cart.extend_reservation(datetime.timedelta(days=days))
return redirect(request.META["HTTP_REFERER"]) 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)