# -*- coding: utf-8 -*-
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
CreateView,
DeleteView,
FormView,
ListView,
UpdateView,
View,
)
from oscar.core.loading import get_class, get_classes, get_model
from oscar.core.utils import redirect_to_referrer, safe_referrer
WishList = get_model("wishlists", "WishList")
Line = get_model("wishlists", "Line")
Product = get_model("catalogue", "Product")
WishListForm = get_class("wishlists.forms", "WishListForm")
LineFormset, WishListSharedEmailFormset = get_classes(
"wishlists.formsets", ("LineFormset", "WishListSharedEmailFormset")
)
PageTitleMixin = get_class("customer.mixins", "PageTitleMixin")
[docs]class WishListListView(PageTitleMixin, ListView):
context_object_name = active_tab = "wishlists"
template_name = "oscar/customer/wishlists/wishlists_list.html"
page_title = _("Wish Lists")
[docs] def get_queryset(self):
"""
Return a list of all the wishlists for the currently
authenticated user.
"""
return self.request.user.wishlists.all()
[docs]class WishListDetailView(PageTitleMixin, FormView):
"""
This view acts as a DetailView for a wish list and allows updating the
quantities of products.
It is implemented as FormView because it's easier to adapt a FormView to
display a product then adapt a DetailView to handle form validation.
"""
template_name = "oscar/customer/wishlists/wishlists_detail.html"
active_tab = "wishlists"
form_class = LineFormset
def dispatch(self, request, *args, **kwargs):
# pylint: disable=attribute-defined-outside-init
self.object = self.get_wishlist_or_404(kwargs["key"], request.user)
return super().dispatch(request, *args, **kwargs)
def get_wishlist_or_404(self, key, user):
wishlist = get_object_or_404(WishList, key=key)
if wishlist.is_allowed_to_edit(user):
return wishlist
else:
raise Http404
def get_page_title(self):
return self.object.name
[docs] def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["wishlist"] = self.object
other_wishlists = self.request.user.wishlists.exclude(pk=self.object.pk)
ctx["other_wishlists"] = other_wishlists
return ctx
[docs]class WishListCreateUpdateViewMixin(PageTitleMixin):
"""
The wishlist create and update view have the same approach on saving the wislist and shared email
forms. This mixin handles that in the post view, where it will call process_wishlist_forms
if both forms are valid. If one of the forms is not valid, the user will be redirected to the original
view with form errors.
"""
def process_wishlist_forms(self, wishlist_form, shared_emails_formset):
wishlist = wishlist_form.save()
for form in shared_emails_formset:
# Prevents saving empty or unchanged forms in the formset.
if not form.has_changed():
continue
# Don't commit to DB until we saved the wislist instance.
wishlist_shared_email = form.save(commit=False)
wishlist_shared_email.wishlist = wishlist
wishlist_shared_email.save()
if wishlist.shared_emails.exists() and wishlist.visibility != WishList.SHARED:
if wishlist.visibility == WishList.PRIVATE:
msg = _(
"The shared accounts won't be able to access your wishlist "
"because the visiblity is set to private."
)
messages.warning(self.request, msg)
elif wishlist.visibility == WishList.PUBLIC:
msg = _(
"You have added shared accounts to your wishlist but the visiblity "
"is public, this means everyone with a link has access to it."
)
messages.warning(self.request, msg)
return wishlist
[docs] def post(self, request, *args, **kwargs):
"""
This post method will handle both the create and update view post request.
"""
try:
self.object = self.get_object()
except AttributeError:
self.object = None
form = self.get_form()
shared_emails_formset = WishListSharedEmailFormset(
request.POST, instance=self.object
)
if form.is_valid() and shared_emails_formset.is_valid():
wishlist = self.process_wishlist_forms(form, shared_emails_formset)
return self.form_valid(wishlist)
context = self.get_context_data(
form=form, shared_emails_formset=shared_emails_formset
)
return self.render_to_response(context)
[docs]class WishListCreateView(WishListCreateUpdateViewMixin, CreateView):
"""
Create a new wishlist
If a product ID is passed as a kwargs, then this product will be added to
the wishlist.
"""
model = WishList
template_name = "oscar/customer/wishlists/wishlists_form.html"
active_tab = "wishlists"
page_title = _("Create a new wish list")
form_class = WishListForm
product = None
def dispatch(self, request, *args, **kwargs):
if "product_pk" in kwargs:
try:
self.product = Product.objects.get(pk=kwargs["product_pk"])
except ObjectDoesNotExist:
messages.error(request, _("The requested product no longer exists"))
return redirect("wishlists-create")
return super().dispatch(request, *args, **kwargs)
[docs] def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["product"] = self.product
# Invalid post response passes this to the context.
if "shared_emails_formset" not in kwargs:
ctx["shared_emails_formset"] = WishListSharedEmailFormset()
return ctx
[docs]class WishListCreateWithProductView(View):
"""
Create a wish list and immediately add a product to it
"""
def post(self, request, *args, **kwargs):
product = get_object_or_404(Product, pk=kwargs["product_pk"])
wishlists = request.user.wishlists.all()
if len(wishlists) == 0:
wishlist = request.user.wishlists.create()
else:
# This shouldn't really happen but we default to using the first
# wishlist for a user if one already exists when they make this
# request.
wishlist = wishlists[0]
wishlist.add(product)
messages.success(
request,
_("%(title)s has been added to your wishlist")
% {"title": product.get_title()},
)
return redirect_to_referrer(request, wishlist.get_absolute_url())
[docs]class WishListUpdateView(WishListCreateUpdateViewMixin, UpdateView):
model = WishList
template_name = "oscar/customer/wishlists/wishlists_form.html"
active_tab = "wishlists"
form_class = WishListForm
context_object_name = "wishlist"
def get_page_title(self):
return self.object.name
[docs] def get_object(self, queryset=None):
return get_object_or_404(
WishList, owner=self.request.user, key=self.kwargs["key"]
)
[docs] def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
# Invalid post response passes this to the context.
if "shared_emails_formset" not in kwargs:
ctx["shared_emails_formset"] = WishListSharedEmailFormset(
instance=self.object
)
return ctx
[docs] def get_success_url(self):
messages.success(
self.request, _("Your '%s' wishlist has been updated") % self.object.name
)
return reverse("customer:wishlists-list")
[docs]class WishListDeleteView(PageTitleMixin, DeleteView):
model = WishList
template_name = "oscar/customer/wishlists/wishlists_delete.html"
active_tab = "wishlists"
def get_page_title(self):
return _("Delete %s") % self.object.name
[docs] def get_object(self, queryset=None):
return get_object_or_404(
WishList, owner=self.request.user, key=self.kwargs["key"]
)
[docs] def get_success_url(self):
messages.success(
self.request, _("Your '%s' wish list has been deleted") % self.object.name
)
return reverse("customer:wishlists-list")
[docs]class WishListAddProduct(View):
"""
Adds a product to a wish list.
- If the user doesn't already have a wishlist then it will be created for
them.
- If the product is already in the wish list, its quantity is increased.
"""
# pylint: disable=attribute-defined-outside-init
def dispatch(self, request, *args, **kwargs):
self.product = get_object_or_404(Product, pk=kwargs["product_pk"])
self.wishlist = self.get_or_create_wishlist(request, *args, **kwargs)
return super().dispatch(request)
def get_or_create_wishlist(self, request, *args, **kwargs):
if "key" in kwargs:
wishlist = get_object_or_404(
WishList, key=kwargs["key"], owner=request.user
)
else:
wishlists = request.user.wishlists.all()[:1]
if not wishlists:
return request.user.wishlists.create()
wishlist = wishlists[0]
if not wishlist.is_allowed_to_edit(request.user):
raise PermissionDenied
return wishlist
def get(self, request, *args, **kwargs):
# This is nasty as we shouldn't be performing write operations on a GET
# request. It's only included as the UI of the product detail page
# allows a wishlist to be selected from a dropdown.
return self.add_product()
def post(self, request, *args, **kwargs):
return self.add_product()
def add_product(self):
self.wishlist.add(self.product)
msg = _("'%s' was added to your wish list.") % self.product.get_title()
messages.success(self.request, msg)
return redirect_to_referrer(self.request, self.product.get_absolute_url())
[docs]class LineMixin(object):
"""
Handles fetching both a wish list and a product
Views using this mixin must be passed two keyword arguments:
* key: The key of a wish list
* line_pk: The primary key of the wish list line
or
* product_pk: The primary key of the product
"""
def fetch_line(self, user, wishlist_key, line_pk=None, product_pk=None):
if line_pk is not None:
self.line = get_object_or_404(
Line,
pk=line_pk,
wishlist__owner=user,
wishlist__key=wishlist_key,
)
else:
try:
self.line = get_object_or_404(
Line,
product_id=product_pk,
wishlist__owner=user,
wishlist__key=wishlist_key,
)
except Line.MultipleObjectsReturned:
raise Http404
self.wishlist = self.line.wishlist
self.product = self.line.product
[docs]class WishListRemoveProduct(LineMixin, PageTitleMixin, DeleteView):
template_name = "oscar/customer/wishlists/wishlists_delete_product.html"
active_tab = "wishlists"
def get_page_title(self):
return _("Remove %s") % self.object.get_title()
[docs] def get_object(self, queryset=None):
self.fetch_line(
self.request.user,
self.kwargs["key"],
self.kwargs.get("line_pk"),
self.kwargs.get("product_pk"),
)
return self.line
[docs] def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["wishlist"] = self.wishlist
ctx["product"] = self.product
return ctx
[docs] def get_success_url(self):
msg = _("'%(title)s' was removed from your '%(name)s' wish list") % {
"title": self.line.get_title(),
"name": self.wishlist.name,
}
messages.success(self.request, msg)
# We post directly to this view on product pages; and should send the
# user back there if that was the case
referrer = safe_referrer(self.request, "")
if referrer and self.product and self.product.get_absolute_url() in referrer:
return referrer
else:
return reverse(
"customer:wishlists-detail", kwargs={"key": self.wishlist.key}
)
[docs]class WishListMoveProductToAnotherWishList(LineMixin, View):
def dispatch(self, request, *args, **kwargs):
self.fetch_line(request.user, kwargs["key"], line_pk=kwargs["line_pk"])
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
to_wishlist = get_object_or_404(
WishList, owner=request.user, key=kwargs["to_key"]
)
if to_wishlist.lines.filter(product=self.line.product).count() > 0:
msg = _("Wish list '%(name)s' already containing '%(title)s'") % {
"title": self.product.get_title(),
"name": to_wishlist.name,
}
messages.error(self.request, msg)
else:
self.line.wishlist = to_wishlist
self.line.save()
msg = _("'%(title)s' moved to '%(name)s' wishlist") % {
"title": self.product.get_title(),
"name": to_wishlist.name,
}
messages.success(self.request, msg)
default_url = reverse(
"customer:wishlists-detail", kwargs={"key": self.wishlist.key}
)
return redirect_to_referrer(self.request, default_url)