From 897915f1217dedb96bdefe378b9d0012a9f2574d Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 14:22:32 +1000 Subject: [PATCH 1/7] Adds the amend_registration view, which currently can display all of the products that the user has added to their current cart, and not much else. --- registrasion/forms.py | 15 +++++++++++++++ registrasion/urls.py | 2 ++ registrasion/views.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/registrasion/forms.py b/registrasion/forms.py index d6e7878e..229f7bc5 100644 --- a/registrasion/forms.py +++ b/registrasion/forms.py @@ -347,3 +347,18 @@ class VoucherForm(forms.Form): help_text="If you have a voucher code, enter it here", required=False, ) + + +class StaffProductsForm(forms.Form): + ''' Form for allowing staff to add an item to a user's cart. ''' + + product = forms.ModelChoiceField( + widget=forms.Select, + queryset=inventory.Product.objects.all(), + ) + + quantity = forms.IntegerField( + min_value=0, + ) + +StaffProductsFormSet = forms.formset_factory(StaffProductsForm) diff --git a/registrasion/urls.py b/registrasion/urls.py index ae6ac8a1..dab49b00 100644 --- a/registrasion/urls.py +++ b/registrasion/urls.py @@ -13,10 +13,12 @@ from .views import ( invoice_access, edit_profile, guided_registration, + amend_registration, ) public = [ + url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"), url(r"^category/([0-9]+)$", product_category, name="product_category"), url(r"^checkout$", checkout, name="checkout"), url(r"^credit_note/([0-9]+)$", credit_note, name="credit_note"), diff --git a/registrasion/views.py b/registrasion/views.py index 95e97793..f4368be8 100644 --- a/registrasion/views.py +++ b/registrasion/views.py @@ -18,6 +18,7 @@ from collections import namedtuple from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import user_passes_test +from django.contrib.auth.models import User from django.contrib import messages from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError @@ -790,3 +791,30 @@ def credit_note(request, note_id, access_code=None): } return render(request, "registrasion/credit_note.html", data) + + +@user_passes_test(_staff_only) +def amend_registration(request, user_id): + ''' Allows staff to amend a user's current registration cart, and etc etc. + ''' + + user = User.objects.get(id=int(user_id)) + current_cart = CartController.for_user(user) + + items = commerce.ProductItem.objects.filter( + cart=current_cart.cart, + ).select_related("product") + + initial = [{"product": i.product, "quantity": i.quantity} for i in items] + + form = forms.StaffProductsFormSet( + request.POST or None, + initial=initial, + prefix="products", + ) + + data = { + "form": form, + } + + return render(request, "registrasion/amend_registration.html", data) From 83b8b62d7457bd1261c03efb78ab31f618cda9a1 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 14:24:58 +1000 Subject: [PATCH 2/7] Attendee view now uses user_id, like the rest of the app --- registrasion/reporting/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/registrasion/reporting/views.py b/registrasion/reporting/views.py index eb512454..6a507135 100644 --- a/registrasion/reporting/views.py +++ b/registrasion/reporting/views.py @@ -236,17 +236,17 @@ def credit_notes(request, form): @report_view("Attendee", form_type=forms.UserIdForm) -def attendee(request, form, attendee_id=None): +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 attendee_id is None and not form.has_changed(): + if user_id is None and not form.has_changed(): return attendee_list(request) if form.cleaned_data["user"] is not None: - attendee_id = form.cleaned_data["user"] + user_id = form.cleaned_data["user"] - attendee = people.Attendee.objects.get(id=attendee_id) + attendee = people.Attendee.objects.get(user__id=user_id) reports = [] @@ -349,7 +349,7 @@ def attendee_list(request): for attendee in attendees: data.append([ - attendee.id, + attendee.user.id, attendee.attendeeprofilebase.attendee_name(), attendee.user.email, attendee.has_registered > 0, From 84c40a1e1f28d630f3aa0c65de8203e8a75ddb17 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 15:08:25 +1000 Subject: [PATCH 3/7] Refactors ItemController, add items_released --- registrasion/controllers/item.py | 42 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/registrasion/controllers/item.py b/registrasion/controllers/item.py index a36604e5..f2d0a2ae 100644 --- a/registrasion/controllers/item.py +++ b/registrasion/controllers/item.py @@ -30,7 +30,7 @@ class ItemController(object): def __init__(self, user): self.user = user - def items_purchased(self, category=None): + def _items(self, cart_status, category=None): ''' Aggregates the items that this user has purchased. Arguments: @@ -45,7 +45,7 @@ class ItemController(object): in_cart = ( Q(productitem__cart__user=self.user) & - Q(productitem__cart__status=commerce.Cart.STATUS_PAID) + Q(productitem__cart__status=cart_status) ) quantities_in_cart = When( @@ -72,6 +72,20 @@ class ItemController(object): out.append(ProductAndQuantity(prod, prod.quantity)) return out + def items_purchased(self, category=None): + ''' Aggregates the items that this user has purchased. + + Arguments: + category (Optional[models.inventory.Category]): the category + of items to restrict to. + + Returns: + [ProductAndQuantity, ...]: A list of product-quantity pairs, + aggregating like products from across multiple invoices. + + ''' + return self._items(commerce.Cart.STATUS_PAID) + def items_pending(self): ''' Gets all of the items that the user has reserved, but has not yet paid for. @@ -82,14 +96,16 @@ class ItemController(object): ''' - all_items = commerce.ProductItem.objects.filter( - cart__user=self.user, - cart__status=commerce.Cart.STATUS_ACTIVE, - ).select_related( - "product", - "product__category", - ).order_by( - "product__category__order", - "product__order", - ) - return all_items + return self._items(commerce.Cart.STATUS_ACTIVE) + + def items_released(self): + ''' Gets all of the items that the user previously paid for, but has + since refunded. + + Returns: + [ProductAndQuantity, ...]: A list of product-quantity pairs for the + items that the user has not yet paid for. + + ''' + + return self._items(commerce.Cart.STATUS_RELEASED) From c2065dd4b90a20e430c98ad6221a6d4021e3c714 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 15:08:44 +1000 Subject: [PATCH 4/7] =?UTF-8?q?The=20form=20can=20now=20amend=20a=20user?= =?UTF-8?q?=E2=80=99s=20registration.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- registrasion/views.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/registrasion/views.py b/registrasion/views.py index f4368be8..58e2891c 100644 --- a/registrasion/views.py +++ b/registrasion/views.py @@ -10,6 +10,7 @@ from registrasion.controllers.cart import CartController from registrasion.controllers.credit_note import CreditNoteController from registrasion.controllers.discount import DiscountController from registrasion.controllers.invoice import InvoiceController +from registrasion.controllers.item import ItemController from registrasion.controllers.product import ProductController from registrasion.exceptions import CartValidationError @@ -804,17 +805,40 @@ def amend_registration(request, user_id): items = commerce.ProductItem.objects.filter( cart=current_cart.cart, ).select_related("product") - initial = [{"product": i.product, "quantity": i.quantity} for i in items] - form = forms.StaffProductsFormSet( + formset = forms.StaffProductsFormSet( request.POST or None, initial=initial, prefix="products", ) + if request.POST and formset.is_valid(): + print formset._errors + + pq = [ + (f.cleaned_data["product"], f.cleaned_data["quantity"]) + for f in formset + if "product" in f.cleaned_data and + f.cleaned_data["product"] is not None + ] + + try: + current_cart.set_quantities(pq) + return redirect(amend_registration, user_id) + except ValidationError as ve: + for ve_field in ve.error_list: + product, message = ve_field.message + for form in formset: + if form.cleaned_data["product"] == product: + form.add_error("quantity", message) + + ic = ItemController(user) data = { - "form": form, + "user": user, + "paid": ic.items_purchased(), + "cancelled": ic.items_released(), + "form": formset, } return render(request, "registrasion/amend_registration.html", data) From 1152e185d136ca50a448086907c8814e5e4ad64b Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 15:16:46 +1000 Subject: [PATCH 5/7] Staff can now check out an invoice for a user --- registrasion/urls.py | 1 + registrasion/views.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/registrasion/urls.py b/registrasion/urls.py index dab49b00..ac7faace 100644 --- a/registrasion/urls.py +++ b/registrasion/urls.py @@ -21,6 +21,7 @@ public = [ url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"), url(r"^category/([0-9]+)$", product_category, name="product_category"), url(r"^checkout$", checkout, name="checkout"), + url(r"^checkout/([0-9]+)$", checkout, name="checkout"), url(r"^credit_note/([0-9]+)$", credit_note, name="credit_note"), url(r"^invoice/([0-9]+)$", invoice, name="invoice"), url(r"^invoice/([0-9]+)/([A-Z0-9]+)$", invoice, name="invoice"), diff --git a/registrasion/views.py b/registrasion/views.py index 58e2891c..f457a629 100644 --- a/registrasion/views.py +++ b/registrasion/views.py @@ -506,7 +506,7 @@ def _handle_voucher(request, prefix): @login_required -def checkout(request): +def checkout(request, user_id=None): ''' Runs the checkout process for the current cart. If the query string contains ``fix_errors=true``, Registrasion will attempt @@ -514,6 +514,10 @@ def checkout(request): cancelling expired discounts and vouchers, and removing any unavailable products. + Arguments: + user_id (castable to int): + If the requesting user is staff, then the user ID can be used to + run checkout for another user. Returns: render or redirect: If the invoice is generated successfully, or there's already a @@ -527,7 +531,15 @@ def checkout(request): ''' - current_cart = CartController.for_user(request.user) + if user_id is not None: + if request.user.is_staff: + user = User.objects.get(id=int(user_id)) + else: + raise Http404() + else: + user = request.user + + current_cart = CartController.for_user(user) if "fix_errors" in request.GET and request.GET["fix_errors"] == "true": current_cart.fix_simple_errors() From b9ee438b891e9414d3391606858807a2da5fbf05 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 15:43:04 +1000 Subject: [PATCH 6/7] Registration amendments are now limited the products that the user is allowed to add. --- registrasion/forms.py | 36 ++++++++++++++++++++++++++---------- registrasion/views.py | 3 ++- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/registrasion/forms.py b/registrasion/forms.py index 229f7bc5..90bf1b48 100644 --- a/registrasion/forms.py +++ b/registrasion/forms.py @@ -1,3 +1,4 @@ +from registrasion.controllers.product import ProductController from registrasion.models import commerce from registrasion.models import inventory @@ -349,16 +350,31 @@ class VoucherForm(forms.Form): ) -class StaffProductsForm(forms.Form): - ''' Form for allowing staff to add an item to a user's cart. ''' +def staff_products_form_factory(user): + ''' Creates a StaffProductsForm that restricts the available products to + those that are available to a user. ''' - product = forms.ModelChoiceField( - widget=forms.Select, - queryset=inventory.Product.objects.all(), - ) + products = inventory.Product.objects.all() + products = ProductController.available_products(user, products=products) - quantity = forms.IntegerField( - min_value=0, - ) + product_ids = [product.id for product in products] + product_set = inventory.Product.objects.filter(id__in=product_ids) -StaffProductsFormSet = forms.formset_factory(StaffProductsForm) + class StaffProductsForm(forms.Form): + ''' Form for allowing staff to add an item to a user's cart. ''' + + product = forms.ModelChoiceField( + widget=forms.Select, + queryset=product_set, + ) + + quantity = forms.IntegerField( + min_value=0, + ) + + return StaffProductsForm + +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) diff --git a/registrasion/views.py b/registrasion/views.py index f457a629..a7cf2391 100644 --- a/registrasion/views.py +++ b/registrasion/views.py @@ -819,7 +819,8 @@ def amend_registration(request, user_id): ).select_related("product") initial = [{"product": i.product, "quantity": i.quantity} for i in items] - formset = forms.StaffProductsFormSet( + StaffProductsFormSet = forms.staff_products_formset_factory(user) + formset = StaffProductsFormSet( request.POST or None, initial=initial, prefix="products", From 5703221fbaf6d2969de179af3264d46137142a0b Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 3 Sep 2016 15:53:54 +1000 Subject: [PATCH 7/7] Adds voucher form to registration amendment --- registrasion/views.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/registrasion/views.py b/registrasion/views.py index a7cf2391..c457295f 100644 --- a/registrasion/views.py +++ b/registrasion/views.py @@ -826,8 +826,12 @@ def amend_registration(request, user_id): prefix="products", ) + voucher_form = forms.VoucherForm( + request.POST or None, + prefix="voucher", + ) + if request.POST and formset.is_valid(): - print formset._errors pq = [ (f.cleaned_data["product"], f.cleaned_data["quantity"]) @@ -846,12 +850,20 @@ def amend_registration(request, user_id): if form.cleaned_data["product"] == product: form.add_error("quantity", message) + if request.POST and voucher_form.is_valid(): + try: + current_cart.apply_voucher(voucher_form.cleaned_data["voucher"]) + return redirect(amend_registration, user_id) + except ValidationError as ve: + voucher_form.add_error(None, ve) + ic = ItemController(user) data = { "user": user, "paid": ic.items_purchased(), "cancelled": ic.items_released(), "form": formset, + "voucher_form": voucher_form, } return render(request, "registrasion/amend_registration.html", data)