Source code for shuup.front.middleware

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import logout
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.http import HttpResponse
from django.template import loader
from django.utils import timezone, translation
from django.utils.translation import ugettext_lazy as _
from functools import lru_cache

from shuup.core.middleware import ExceptionMiddleware
from shuup.core.models import AnonymousContact, Contact, get_company_contact, get_person_contact
from shuup.core.shop_provider import get_shop
from shuup.core.utils.users import should_force_anonymous_contact, should_force_person_contact
from shuup.front.basket import get_basket
from shuup.front.utils.user import is_admin_user
from shuup.utils.django_compat import MiddlewareMixin, get_middleware_classes

__all__ = ["ProblemMiddleware", "ShuupFrontMiddleware"]

ProblemMiddleware = ExceptionMiddleware  # This class is only an alias for ExceptionMiddleware.


[docs]class ShuupFrontMiddleware(MiddlewareMixin): """ Handle Shuup specific tasks for each request and response. * Set request attributes that rest of the Shuup front-end rely on. * Set Django's timezone according to personal preferences (i.e. request.person.timezone). .. TODO:: Fallback to shop timezone? * Make sure that basket is saved before response is returned to the browser. Attributes set for requests: ``request.shop`` : :class:`shuup.core.models.Shop` Currently active Shop. ``request.person`` : :class:`shuup.core.models.Contact` :class:`~shuup.core.models.PersonContact` of currently logged in user or :class:`~shuup.core.models.AnonymousContact` if there is no logged-in user. ``request.customer`` : :class:`shuup.core.models.Contact` Customer contact used when creating Orders. This can be same as ``request.person``, but for example in B2B shops this is usually a :class:`~shuup.core.models.CompanyContact` whereas ``request.person`` is a :class:`~shuup.core.models.PersonContact`. ``request.basket`` : :class:`shuup.front.basket.objects.BaseBasket` Shopping basket in use. """
[docs] def process_request(self, request): if settings.DEBUG and ( request.path.startswith(settings.MEDIA_URL) or request.path.startswith(settings.STATIC_URL) ): return None self._set_shop(request) self._set_person(request) self._set_customer(request) self._set_basket(request) self._set_timezone(request) self._set_price_display_options(request)
def _set_shop(self, request): """ Set the shop here again, even if the ShuupCore already did it. As we use this middleware alone in `refresh_on_user_change`, we should ensure the request shop. """ request.shop = get_shop(request) def _set_person(self, request): if should_force_anonymous_contact(request.user): request.person = AnonymousContact() else: request.person = get_person_contact(request.user) if not request.person.is_active: messages.add_message(request, messages.INFO, _("Logged out since this account is inactive.")) logout(request) # Usually logout is connected to the `refresh_on_logout` # method via a signal and that already sets request.person # to anonymous. But set it explicitly too, just to be sure. request.person = get_person_contact(None) def _set_customer(self, request): if not request.person or should_force_person_contact(request.user): company = None else: company = get_company_contact(request.user) request.customer = company or request.person request.is_company_member = bool(company) request.customer_groups = (company or request.person).groups.all() def _set_basket(self, request): request.basket = get_basket(request) def _set_timezone(self, request): if request.person.timezone: timezone.activate(request.person.timezone) else: timezone.activate(settings.TIME_ZONE) request.TIME_ZONE = timezone.get_current_timezone_name() def _set_price_display_options(self, request): customer = request.customer assert isinstance(customer, Contact) customer.get_price_display_options(shop=request.shop).set_for_request(request)
[docs] def process_response(self, request, response): if hasattr(request, "basket") and request.basket.dirty: request.basket.save() return response
@classmethod
[docs] def refresh_on_user_change(cls, request, user=None, **kwargs): # In some cases, there is no `user` attribute in # the request, which would cause the middleware to fail. # If that's the case, let's assign the freshly changed user # now. if not hasattr(request, "user"): request.user = user cls().process_request(request)
@classmethod
[docs] def refresh_on_logout(cls, request, **kwargs): # The `user_logged_out` signal is fired _before_ `request.user` is set to `AnonymousUser()`, # so we'll have to do switcharoos and hijinks before invoking `refresh_on_user_change`. # The `try: finally:` is there to ensure other signal consumers still get an unchanged (well, # aside from `.person` etc. of course) `request` to toy with. # Oh, and let's also add shenanigans to switcharoos and hijinks. Shenanigans. current_user = getattr(request, "user", None) try: request.user = AnonymousUser() cls.refresh_on_user_change(request) finally: request.user = current_user
@lru_cache() def _get_front_urlpatterns_callbacks(self): from shuup.front.urls import urlpatterns return [urlpattern.callback for urlpattern in urlpatterns]
[docs] def process_view(self, request, view_func, *view_args, **view_kwargs): maintenance_response = self._get_maintenance_response(request, view_func) if maintenance_response: return maintenance_response # only force settings language when in Front urls if view_func in self._get_front_urlpatterns_callbacks(): self._set_language(request)
def _set_language(self, request): """ Set the language according to the shop preferences. If the current language is not in the available ones, change it to the first available. """ from shuup.front.utils.translation import get_language_choices current_language = translation.get_language() available_languages = [code for (code, name, local_name) in get_language_choices(request.shop)] if current_language not in available_languages: if available_languages: translation.activate(available_languages[0]) else: # fallback to LANGUAGE_CODE translation.activate(settings.LANGUAGE_CODE) request.LANGUAGE_CODE = translation.get_language() def _get_maintenance_response(self, request, view_func): # Allow media and static accesses in debug mode if settings.DEBUG and ( request.path.startswith(settings.MEDIA_URL) or request.path.startswith(settings.STATIC_URL) ): return None if getattr(view_func, "maintenance_mode_exempt", False): return None if "login" in view_func.__name__: return None resolver_match = getattr(request, "resolver_match", None) if resolver_match and resolver_match.app_name == "shuup_admin": return None if request.shop.maintenance_mode and not is_admin_user(request): return HttpResponse(loader.render_to_string("shuup/front/maintenance.jinja", request=request), status=503)
if ( "django.contrib.auth" in settings.INSTALLED_APPS and "shuup.front.middleware.ShuupFrontMiddleware" in get_middleware_classes() ): user_logged_in.connect(ShuupFrontMiddleware.refresh_on_user_change, dispatch_uid="shuup_front_refresh_on_login") user_logged_out.connect(ShuupFrontMiddleware.refresh_on_logout, dispatch_uid="shuup_front_refresh_on_logout")