Merge branch 'chrisjrn/20161207'
This commit is contained in:
commit
f4e11ee53f
6 changed files with 161 additions and 9 deletions
|
@ -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]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue