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