| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- from django.shortcuts import get_object_or_404, redirect
- from django.views import generic
- from django.core.urlresolvers import reverse, reverse_lazy
- from django.core.exceptions import ObjectDoesNotExist
- from django import http
- from django.contrib import messages
- from django.utils.translation import ugettext_lazy as _
- from django.contrib.auth import logout as auth_logout, login as auth_login
- from django.contrib.sites.models import get_current_site
- from django.conf import settings
-
- from oscar.core.utils import safe_referrer
- from oscar.views.generic import PostActionMixin
- from oscar.apps.customer.utils import get_password_reset_url
- from oscar.core.loading import (
- get_class, get_profile_class, get_classes, get_model)
- from oscar.core.compat import get_user_model
- from . import signals
-
- PageTitleMixin, RegisterUserMixin = get_classes(
- 'customer.mixins', ['PageTitleMixin', 'RegisterUserMixin'])
- Dispatcher = get_class('customer.utils', 'Dispatcher')
- EmailAuthenticationForm, EmailUserCreationForm, OrderSearchForm = get_classes(
- 'customer.forms', ['EmailAuthenticationForm', 'EmailUserCreationForm',
- 'OrderSearchForm'])
- PasswordChangeForm = get_class('customer.forms', 'PasswordChangeForm')
- ProfileForm, ConfirmPasswordForm = get_classes(
- 'customer.forms', ['ProfileForm', 'ConfirmPasswordForm'])
- UserAddressForm = get_class('address.forms', 'UserAddressForm')
- Order = get_model('order', 'Order')
- Line = get_model('basket', 'Line')
- Basket = get_model('basket', 'Basket')
- UserAddress = get_model('address', 'UserAddress')
- Email = get_model('customer', 'Email')
- ProductAlert = get_model('customer', 'ProductAlert')
- CommunicationEventType = get_model('customer', 'CommunicationEventType')
-
- User = get_user_model()
-
-
- # =======
- # Account
- # =======
-
-
- class AccountSummaryView(generic.RedirectView):
- """
- View that exists for legacy reasons and customisability. It commonly gets
- called when the user clicks on "Account" in the navbar, and can be
- overridden to determine to what sub-page the user is directed without
- having to change a lot of templates.
- """
- url = reverse_lazy(settings.OSCAR_ACCOUNTS_REDIRECT_URL)
-
-
- class AccountRegistrationView(RegisterUserMixin, generic.FormView):
- form_class = EmailUserCreationForm
- template_name = 'customer/registration.html'
- redirect_field_name = 'next'
-
- def get(self, request, *args, **kwargs):
- if request.user.is_authenticated():
- return redirect(settings.LOGIN_REDIRECT_URL)
- return super(AccountRegistrationView, self).get(
- request, *args, **kwargs)
-
- def get_logged_in_redirect(self):
- return reverse('customer:summary')
-
- def get_form_kwargs(self):
- kwargs = super(AccountRegistrationView, self).get_form_kwargs()
- kwargs['initial'] = {
- 'email': self.request.GET.get('email', ''),
- 'redirect_url': self.request.GET.get(self.redirect_field_name, '')
- }
- kwargs['host'] = self.request.get_host()
- return kwargs
-
- def get_context_data(self, *args, **kwargs):
- ctx = super(AccountRegistrationView, self).get_context_data(
- *args, **kwargs)
- ctx['cancel_url'] = safe_referrer(self.request.META, '')
- return ctx
-
- def form_valid(self, form):
- self.register_user(form)
- return redirect(form.cleaned_data['redirect_url'])
-
-
- class AccountAuthView(RegisterUserMixin, generic.TemplateView):
- """
- This is actually a slightly odd double form view that allows a customer to
- either login or register.
- """
- template_name = 'customer/login_registration.html'
- login_prefix, registration_prefix = 'login', 'registration'
- login_form_class = EmailAuthenticationForm
- registration_form_class = EmailUserCreationForm
- redirect_field_name = 'next'
-
- def get(self, request, *args, **kwargs):
- if request.user.is_authenticated():
- return redirect(settings.LOGIN_REDIRECT_URL)
- return super(AccountAuthView, self).get(
- request, *args, **kwargs)
-
- def get_context_data(self, *args, **kwargs):
- ctx = super(AccountAuthView, self).get_context_data(*args, **kwargs)
- if 'login_form' not in kwargs:
- ctx['login_form'] = self.get_login_form()
- if 'registration_form' not in kwargs:
- ctx['registration_form'] = self.get_registration_form()
- return ctx
-
- def post(self, request, *args, **kwargs):
- # Use the name of the submit button to determine which form to validate
- if u'login_submit' in request.POST:
- return self.validate_login_form()
- elif u'registration_submit' in request.POST:
- return self.validate_registration_form()
- return http.HttpResponseBadRequest()
-
- # LOGIN
-
- def get_login_form(self, bind_data=False):
- return self.login_form_class(
- **self.get_login_form_kwargs(bind_data))
-
- def get_login_form_kwargs(self, bind_data=False):
- kwargs = {}
- kwargs['host'] = self.request.get_host()
- kwargs['prefix'] = self.login_prefix
- kwargs['initial'] = {
- 'redirect_url': self.request.GET.get(self.redirect_field_name, ''),
- }
- if bind_data and self.request.method in ('POST', 'PUT'):
- kwargs.update({
- 'data': self.request.POST,
- 'files': self.request.FILES,
- })
- return kwargs
-
- def validate_login_form(self):
- form = self.get_login_form(bind_data=True)
- if form.is_valid():
- user = form.get_user()
-
- # Grab a reference to the session ID before logging in
- old_session_key = self.request.session.session_key
-
- auth_login(self.request, form.get_user())
-
- # Raise signal robustly (we don't want exceptions to crash the
- # request handling). We use a custom signal as we want to track the
- # session key before calling login (which cycles the session ID).
- signals.user_logged_in.send_robust(
- sender=self, request=self.request, user=user,
- old_session_key=old_session_key)
-
- msg = self.get_login_success_message(form)
- messages.success(self.request, msg)
-
- return redirect(self.get_login_success_url(form))
-
- ctx = self.get_context_data(login_form=form)
- return self.render_to_response(ctx)
-
- def get_login_success_message(self, form):
- return _("Welcome back")
-
- def get_login_success_url(self, form):
- redirect_url = form.cleaned_data['redirect_url']
- if redirect_url:
- return redirect_url
-
- # Redirect staff members to dashboard as that's the most likely place
- # they'll want to visit if they're logging in.
- if self.request.user.is_staff:
- return reverse('dashboard:index')
-
- return settings.LOGIN_REDIRECT_URL
-
- # REGISTRATION
-
- def get_registration_form(self, bind_data=False):
- return self.registration_form_class(
- **self.get_registration_form_kwargs(bind_data))
-
- def get_registration_form_kwargs(self, bind_data=False):
- kwargs = {}
- kwargs['host'] = self.request.get_host()
- kwargs['prefix'] = self.registration_prefix
- kwargs['initial'] = {
- 'redirect_url': self.request.GET.get(self.redirect_field_name, ''),
- }
- if bind_data and self.request.method in ('POST', 'PUT'):
- kwargs.update({
- 'data': self.request.POST,
- 'files': self.request.FILES,
- })
- return kwargs
-
- def validate_registration_form(self):
- form = self.get_registration_form(bind_data=True)
- if form.is_valid():
- self.register_user(form)
-
- msg = self.get_registration_success_message(form)
- messages.success(self.request, msg)
-
- return redirect(self.get_registration_success_url(form))
-
- ctx = self.get_context_data(registration_form=form)
- return self.render_to_response(ctx)
-
- def get_registration_success_message(self, form):
- return _("Thanks for registering!")
-
- def get_registration_success_url(self, form):
- return settings.LOGIN_REDIRECT_URL
-
-
- class LogoutView(generic.RedirectView):
- url = settings.OSCAR_HOMEPAGE
- permanent = False
-
- def get(self, request, *args, **kwargs):
- auth_logout(request)
- response = super(LogoutView, self).get(request, *args, **kwargs)
-
- for cookie in settings.OSCAR_COOKIES_DELETE_ON_LOGOUT:
- response.delete_cookie(cookie)
-
- return response
-
-
- # =============
- # Profile
- # =============
-
-
- class ProfileView(PageTitleMixin, generic.TemplateView):
- template_name = 'customer/profile/profile.html'
- page_title = _('Profile')
- active_tab = 'profile'
-
- def get_context_data(self, **kwargs):
- ctx = super(ProfileView, self).get_context_data(**kwargs)
- ctx['profile_fields'] = self.get_profile_fields(self.request.user)
- return ctx
-
- def get_profile_fields(self, user):
- field_data = []
-
- # Check for custom user model
- for field_name in User._meta.additional_fields:
- field_data.append(
- self.get_model_field_data(user, field_name))
-
- # Check for profile class
- profile_class = get_profile_class()
- if profile_class:
- try:
- profile = profile_class.objects.get(user=user)
- except ObjectDoesNotExist:
- profile = profile_class(user=user)
-
- field_names = [f.name for f in profile._meta.local_fields]
- for field_name in field_names:
- if field_name in ('user', 'id'):
- continue
- field_data.append(
- self.get_model_field_data(profile, field_name))
-
- return field_data
-
- def get_model_field_data(self, model_class, field_name):
- """
- Extract the verbose name and value for a model's field value
- """
- field = model_class._meta.get_field(field_name)
- if field.choices:
- value = getattr(model_class, 'get_%s_display' % field_name)()
- else:
- value = getattr(model_class, field_name)
- return {
- 'name': getattr(field, 'verbose_name'),
- 'value': value,
- }
-
-
- class ProfileUpdateView(PageTitleMixin, generic.FormView):
- form_class = ProfileForm
- template_name = 'customer/profile/profile_form.html'
- communication_type_code = 'EMAIL_CHANGED'
- page_title = _('Edit Profile')
- active_tab = 'profile'
- success_url = reverse_lazy('customer:profile-view')
-
- def get_form_kwargs(self):
- kwargs = super(ProfileUpdateView, self).get_form_kwargs()
- kwargs['user'] = self.request.user
- return kwargs
-
- def form_valid(self, form):
- # Grab current user instance before we save form. We may need this to
- # send a warning email if the email address is changed.
- try:
- old_user = User.objects.get(id=self.request.user.id)
- except User.DoesNotExist:
- old_user = None
-
- form.save()
-
- # We have to look up the email address from the form's
- # cleaned data because the object created by form.save() can
- # either be a user or profile instance depending whether a profile
- # class has been specified by the AUTH_PROFILE_MODULE setting.
- new_email = form.cleaned_data['email']
- if old_user and new_email != old_user.email:
- # Email address has changed - send a confirmation email to the old
- # address including a password reset link in case this is a
- # suspicious change.
- ctx = {
- 'user': self.request.user,
- 'site': get_current_site(self.request),
- 'reset_url': get_password_reset_url(old_user),
- 'new_email': new_email,
- }
- msgs = CommunicationEventType.objects.get_and_render(
- code=self.communication_type_code, context=ctx)
- Dispatcher().dispatch_user_messages(old_user, msgs)
-
- messages.success(self.request, _("Profile updated"))
- return redirect(self.get_success_url())
-
-
- class ProfileDeleteView(PageTitleMixin, generic.FormView):
- form_class = ConfirmPasswordForm
- template_name = 'customer/profile/profile_delete.html'
- page_title = _('Delete profile')
- active_tab = 'profile'
- success_url = settings.OSCAR_HOMEPAGE
-
- def get_form_kwargs(self):
- kwargs = super(ProfileDeleteView, self).get_form_kwargs()
- kwargs['user'] = self.request.user
- return kwargs
-
- def form_valid(self, form):
- self.request.user.delete()
- messages.success(
- self.request,
- _("Your profile has now been deleted. Thanks for using the site."))
- return redirect(self.get_success_url())
-
-
- class ChangePasswordView(PageTitleMixin, generic.FormView):
- form_class = PasswordChangeForm
- template_name = 'customer/profile/change_password_form.html'
- communication_type_code = 'PASSWORD_CHANGED'
- page_title = _('Change Password')
- active_tab = 'profile'
- success_url = reverse_lazy('customer:profile-view')
-
- def get_form_kwargs(self):
- kwargs = super(ChangePasswordView, self).get_form_kwargs()
- kwargs['user'] = self.request.user
- return kwargs
-
- def form_valid(self, form):
- form.save()
- messages.success(self.request, _("Password updated"))
-
- ctx = {
- 'user': self.request.user,
- 'site': get_current_site(self.request),
- 'reset_url': get_password_reset_url(self.request.user),
- }
- msgs = CommunicationEventType.objects.get_and_render(
- code=self.communication_type_code, context=ctx)
- Dispatcher().dispatch_user_messages(self.request.user, msgs)
-
- return redirect(self.get_success_url())
-
-
- # =============
- # Email history
- # =============
-
- class EmailHistoryView(PageTitleMixin, generic.ListView):
- context_object_name = "emails"
- template_name = 'customer/email/email_list.html'
- paginate_by = 20
- page_title = _('Email History')
- active_tab = 'emails'
-
- def get_queryset(self):
- return Email._default_manager.filter(user=self.request.user)
-
-
- class EmailDetailView(PageTitleMixin, generic.DetailView):
- """Customer email"""
- template_name = "customer/email/email_detail.html"
- context_object_name = 'email'
- active_tab = 'emails'
-
- def get_object(self, queryset=None):
- return get_object_or_404(Email, user=self.request.user,
- id=self.kwargs['email_id'])
-
- def get_page_title(self):
- """Append email subject to page title"""
- return u'%s: %s' % (_('Email'), self.object.subject)
-
-
- # =============
- # Order history
- # =============
-
- class OrderHistoryView(PageTitleMixin, generic.ListView):
- """
- Customer order history
- """
- context_object_name = "orders"
- template_name = 'customer/order/order_list.html'
- paginate_by = 20
- model = Order
- form_class = OrderSearchForm
- page_title = _('Order History')
- active_tab = 'orders'
-
- def get(self, request, *args, **kwargs):
- if 'date_from' in request.GET:
- self.form = self.form_class(self.request.GET)
- if not self.form.is_valid():
- self.object_list = self.get_queryset()
- ctx = self.get_context_data(object_list=self.object_list)
- return self.render_to_response(ctx)
- data = self.form.cleaned_data
-
- # If the user has just entered an order number, try and look it up
- # and redirect immediately to the order detail page.
- if data['order_number'] and not (data['date_to'] or
- data['date_from']):
- try:
- order = Order.objects.get(
- number=data['order_number'], user=self.request.user)
- except Order.DoesNotExist:
- pass
- else:
- return redirect(
- 'customer:order', order_number=order.number)
- else:
- self.form = self.form_class()
- return super(OrderHistoryView, self).get(request, *args, **kwargs)
-
- def get_queryset(self):
- qs = self.model._default_manager.filter(user=self.request.user)
- if self.form.is_bound and self.form.is_valid():
- qs = qs.filter(**self.form.get_filters())
- return qs
-
- def get_context_data(self, *args, **kwargs):
- ctx = super(OrderHistoryView, self).get_context_data(*args, **kwargs)
- ctx['form'] = self.form
- return ctx
-
-
- class OrderDetailView(PageTitleMixin, PostActionMixin, generic.DetailView):
- model = Order
- active_tab = 'orders'
-
- def get_template_names(self):
- return ["customer/order/order_detail.html"]
-
- def get_page_title(self):
- """
- Order number as page title
- """
- return u'%s #%s' % (_('Order'), self.object.number)
-
- def get_object(self, queryset=None):
- return get_object_or_404(self.model, user=self.request.user,
- number=self.kwargs['order_number'])
-
- def do_reorder(self, order): # noqa (too complex (10))
- """
- 'Re-order' a previous order.
-
- This puts the contents of the previous order into your basket
- """
- # Collect lines to be added to the basket and any warnings for lines
- # that are no longer available.
- basket = self.request.basket
- lines_to_add = []
- warnings = []
- for line in order.lines.all():
- is_available, reason = line.is_available_to_reorder(
- basket, self.request.strategy)
- if is_available:
- lines_to_add.append(line)
- else:
- warnings.append(reason)
-
- # Check whether the number of items in the basket won't exceed the
- # maximum.
- total_quantity = sum([line.quantity for line in lines_to_add])
- is_quantity_allowed, reason = basket.is_quantity_allowed(
- total_quantity)
- if not is_quantity_allowed:
- messages.warning(self.request, reason)
- self.response = redirect('customer:order-list')
- return
-
- # Add any warnings
- for warning in warnings:
- messages.warning(self.request, warning)
-
- for line in lines_to_add:
- options = []
- for attribute in line.attributes.all():
- if attribute.option:
- options.append({
- 'option': attribute.option,
- 'value': attribute.value})
- basket.add_product(line.product, line.quantity, options)
-
- if len(lines_to_add) > 0:
- self.response = redirect('basket:summary')
- messages.info(
- self.request,
- _("All available lines from order %(number)s "
- "have been added to your basket") % {'number': order.number})
- else:
- self.response = redirect('customer:order-list')
- messages.warning(
- self.request,
- _("It is not possible to re-order order %(number)s "
- "as none of its lines are available to purchase") %
- {'number': order.number})
-
-
- class OrderLineView(PostActionMixin, generic.DetailView):
- """Customer order line"""
-
- def get_object(self, queryset=None):
- order = get_object_or_404(Order, user=self.request.user,
- number=self.kwargs['order_number'])
- return order.lines.get(id=self.kwargs['line_id'])
-
- def do_reorder(self, line):
- self.response = redirect(
- 'customer:order', int(self.kwargs['order_number']))
- basket = self.request.basket
-
- line_available_to_reorder, reason = line.is_available_to_reorder(
- basket, self.request.strategy)
-
- if not line_available_to_reorder:
- messages.warning(self.request, reason)
- return
-
- # We need to pass response to the get_or_create... method
- # as a new basket might need to be created
- self.response = redirect('basket:summary')
-
- # Convert line attributes into basket options
- options = []
- for attribute in line.attributes.all():
- if attribute.option:
- options.append({'option': attribute.option,
- 'value': attribute.value})
- basket.add_product(line.product, line.quantity, options)
-
- if line.quantity > 1:
- msg = _("%(qty)d copies of '%(product)s' have been added to your"
- " basket") % {
- 'qty': line.quantity, 'product': line.product}
- else:
- msg = _("'%s' has been added to your basket") % line.product
-
- messages.info(self.request, msg)
-
-
- class AnonymousOrderDetailView(generic.DetailView):
- model = Order
- template_name = "customer/anon_order.html"
-
- def get_object(self, queryset=None):
- # Check URL hash matches that for order to prevent spoof attacks
- order = get_object_or_404(self.model, user=None,
- number=self.kwargs['order_number'])
- if self.kwargs['hash'] != order.verification_hash():
- raise http.Http404()
- return order
-
-
- # ------------
- # Address book
- # ------------
-
- class AddressListView(PageTitleMixin, generic.ListView):
- """Customer address book"""
- context_object_name = "addresses"
- template_name = 'customer/address/address_list.html'
- paginate_by = 40
- active_tab = 'addresses'
- page_title = _('Address Book')
-
- def get_queryset(self):
- """Return customer's addresses"""
- return UserAddress._default_manager.filter(user=self.request.user)
-
-
- class AddressCreateView(PageTitleMixin, generic.CreateView):
- form_class = UserAddressForm
- model = UserAddress
- template_name = 'customer/address/address_form.html'
- active_tab = 'addresses'
- page_title = _('Add a new address')
- success_url = reverse_lazy('customer:address-list')
-
- def get_form_kwargs(self):
- kwargs = super(AddressCreateView, self).get_form_kwargs()
- kwargs['user'] = self.request.user
- return kwargs
-
- def get_context_data(self, **kwargs):
- ctx = super(AddressCreateView, self).get_context_data(**kwargs)
- ctx['title'] = _('Add a new address')
- return ctx
-
- def get_success_url(self):
- messages.success(self.request,
- _("Address '%s' created") % self.object.summary)
- return super(AddressCreateView, self).get_success_url()
-
-
- class AddressUpdateView(PageTitleMixin, generic.UpdateView):
- form_class = UserAddressForm
- model = UserAddress
- template_name = 'customer/address/address_form.html'
- active_tab = 'addresses'
- page_title = _('Edit address')
- success_url = reverse_lazy('customer:address-list')
-
- def get_form_kwargs(self):
- kwargs = super(AddressUpdateView, self).get_form_kwargs()
- kwargs['user'] = self.request.user
- return kwargs
-
- def get_context_data(self, **kwargs):
- ctx = super(AddressUpdateView, self).get_context_data(**kwargs)
- ctx['title'] = _('Edit address')
- return ctx
-
- def get_queryset(self):
- return self.request.user.addresses.all()
-
- def get_success_url(self):
- messages.success(self.request,
- _("Address '%s' updated") % self.object.summary)
- return super(AddressUpdateView, self).get_success_url()
-
-
- class AddressDeleteView(PageTitleMixin, generic.DeleteView):
- model = UserAddress
- template_name = "customer/address/address_delete.html"
- page_title = _('Delete address?')
- active_tab = 'addresses'
- context_object_name = 'address'
- success_url = reverse_lazy('customer:address-list')
-
- def get_queryset(self):
- return UserAddress._default_manager.filter(user=self.request.user)
-
- def get_success_url(self):
- messages.success(self.request,
- _("Address '%s' deleted") % self.object.summary)
- return super(AddressDeleteView, self).get_success_url()
-
-
- class AddressChangeStatusView(generic.RedirectView):
- """
- Sets an address as default_for_(billing|shipping)
- """
- url = reverse_lazy('customer:address-list')
- permanent = False
-
- def get(self, request, pk=None, action=None, *args, **kwargs):
- address = get_object_or_404(UserAddress, user=self.request.user,
- pk=pk)
- # We don't want the user to set an address as the default shipping
- # address, though they should be able to set it as their billing
- # address.
- if address.country.is_shipping_country:
- setattr(address, 'is_%s' % action, True)
- elif action == 'default_for_billing':
- setattr(address, 'is_default_for_billing', True)
- else:
- messages.error(request, _('We do not ship to this country'))
- address.save()
- return super(AddressChangeStatusView, self).get(
- request, *args, **kwargs)
|