Merge branch 'chrisjrn/amend_products'

Closes #33
This commit is contained in:
Christopher Neugebauer 2016-09-03 15:56:08 +10:00
commit 75d96ce1c1
5 changed files with 147 additions and 20 deletions

View file

@ -30,7 +30,7 @@ class ItemController(object):
def __init__(self, user): def __init__(self, user):
self.user = 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. ''' Aggregates the items that this user has purchased.
Arguments: Arguments:
@ -45,7 +45,7 @@ class ItemController(object):
in_cart = ( in_cart = (
Q(productitem__cart__user=self.user) & Q(productitem__cart__user=self.user) &
Q(productitem__cart__status=commerce.Cart.STATUS_PAID) Q(productitem__cart__status=cart_status)
) )
quantities_in_cart = When( quantities_in_cart = When(
@ -72,6 +72,20 @@ class ItemController(object):
out.append(ProductAndQuantity(prod, prod.quantity)) out.append(ProductAndQuantity(prod, prod.quantity))
return out 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): def items_pending(self):
''' Gets all of the items that the user has reserved, but has not yet ''' Gets all of the items that the user has reserved, but has not yet
paid for. paid for.
@ -82,14 +96,16 @@ class ItemController(object):
''' '''
all_items = commerce.ProductItem.objects.filter( return self._items(commerce.Cart.STATUS_ACTIVE)
cart__user=self.user,
cart__status=commerce.Cart.STATUS_ACTIVE, def items_released(self):
).select_related( ''' Gets all of the items that the user previously paid for, but has
"product", since refunded.
"product__category",
).order_by( Returns:
"product__category__order", [ProductAndQuantity, ...]: A list of product-quantity pairs for the
"product__order", items that the user has not yet paid for.
)
return all_items '''
return self._items(commerce.Cart.STATUS_RELEASED)

View file

@ -1,3 +1,4 @@
from registrasion.controllers.product import ProductController
from registrasion.models import commerce from registrasion.models import commerce
from registrasion.models import inventory from registrasion.models import inventory
@ -347,3 +348,33 @@ class VoucherForm(forms.Form):
help_text="If you have a voucher code, enter it here", help_text="If you have a voucher code, enter it here",
required=False, required=False,
) )
def staff_products_form_factory(user):
''' Creates a StaffProductsForm that restricts the available products to
those that are available to a user. '''
products = inventory.Product.objects.all()
products = ProductController.available_products(user, products=products)
product_ids = [product.id for product in products]
product_set = inventory.Product.objects.filter(id__in=product_ids)
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)

View file

@ -236,17 +236,17 @@ def credit_notes(request, form):
@report_view("Attendee", form_type=forms.UserIdForm) @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, ''' Returns a list of all manifested attendees if no attendee is specified,
else displays the attendee manifest. ''' 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) return attendee_list(request)
if form.cleaned_data["user"] is not None: 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 = [] reports = []
@ -349,7 +349,7 @@ def attendee_list(request):
for attendee in attendees: for attendee in attendees:
data.append([ data.append([
attendee.id, attendee.user.id,
attendee.attendeeprofilebase.attendee_name(), attendee.attendeeprofilebase.attendee_name(),
attendee.user.email, attendee.user.email,
attendee.has_registered > 0, attendee.has_registered > 0,

View file

@ -13,12 +13,15 @@ from .views import (
invoice_access, invoice_access,
edit_profile, edit_profile,
guided_registration, guided_registration,
amend_registration,
) )
public = [ public = [
url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"),
url(r"^category/([0-9]+)$", product_category, name="product_category"), url(r"^category/([0-9]+)$", product_category, name="product_category"),
url(r"^checkout$", checkout, name="checkout"), 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"^credit_note/([0-9]+)$", credit_note, name="credit_note"),
url(r"^invoice/([0-9]+)$", invoice, name="invoice"), url(r"^invoice/([0-9]+)$", invoice, name="invoice"),
url(r"^invoice/([0-9]+)/([A-Z0-9]+)$", invoice, name="invoice"), url(r"^invoice/([0-9]+)/([A-Z0-9]+)$", invoice, name="invoice"),

View file

@ -10,6 +10,7 @@ from registrasion.controllers.cart import CartController
from registrasion.controllers.credit_note import CreditNoteController from registrasion.controllers.credit_note import CreditNoteController
from registrasion.controllers.discount import DiscountController from registrasion.controllers.discount import DiscountController
from registrasion.controllers.invoice import InvoiceController from registrasion.controllers.invoice import InvoiceController
from registrasion.controllers.item import ItemController
from registrasion.controllers.product import ProductController from registrasion.controllers.product import ProductController
from registrasion.exceptions import CartValidationError from registrasion.exceptions import CartValidationError
@ -18,6 +19,7 @@ from collections import namedtuple
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
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
@ -504,7 +506,7 @@ def _handle_voucher(request, prefix):
@login_required @login_required
def checkout(request): def checkout(request, user_id=None):
''' Runs the checkout process for the current cart. ''' Runs the checkout process for the current cart.
If the query string contains ``fix_errors=true``, Registrasion will attempt If the query string contains ``fix_errors=true``, Registrasion will attempt
@ -512,6 +514,10 @@ def checkout(request):
cancelling expired discounts and vouchers, and removing any unavailable cancelling expired discounts and vouchers, and removing any unavailable
products. 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: Returns:
render or redirect: render or redirect:
If the invoice is generated successfully, or there's already a If the invoice is generated successfully, or there's already a
@ -525,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": if "fix_errors" in request.GET and request.GET["fix_errors"] == "true":
current_cart.fix_simple_errors() current_cart.fix_simple_errors()
@ -790,3 +804,66 @@ def credit_note(request, note_id, access_code=None):
} }
return render(request, "registrasion/credit_note.html", data) 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]
StaffProductsFormSet = forms.staff_products_formset_factory(user)
formset = StaffProductsFormSet(
request.POST or None,
initial=initial,
prefix="products",
)
voucher_form = forms.VoucherForm(
request.POST or None,
prefix="voucher",
)
if request.POST and formset.is_valid():
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)
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)