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):
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)

View file

@ -1,3 +1,4 @@
from registrasion.controllers.product import ProductController
from registrasion.models import commerce
from registrasion.models import inventory
@ -347,3 +348,33 @@ class VoucherForm(forms.Form):
help_text="If you have a voucher code, enter it here",
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)
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,

View file

@ -13,12 +13,15 @@ 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"^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"),

View file

@ -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
@ -18,6 +19,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
@ -504,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
@ -512,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
@ -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":
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)
@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)