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…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer