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 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]
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue