# flake8: noqa: N805
"""
CRUDLFA+ introduces an MVC-ish pattern, as the Router class is meant to sit
between a Model class and its set of View. Your views will have to inherit from
Route to work in Router.views. This structural decision made for you by
CRUDLFA+ was not exactly designed: it's an open source rewrite of a module that
was ordered in a proprietary project.
"""
import re
from django import http
from django.urls import path, reverse, reverse_lazy
from django.utils.module_loading import import_string
from .factory import Factory, FactoryMetaclass
from .utils import guess_urlfield
[docs]class Route(Factory, metaclass=RouteMetaclass):
"""The mixin for Views that will make it compatible with Router.
.. py:data:: authenticate
False by default, it makes the default has_perm() implementation
require Django permission.
.. py:data:: urlargs
Args that should be passed to reverse() along with Route.urlfullname.
.. py:data:: url
Absolute url to the view, relying on Route.urlfullname and
Route.urlargs.
You will be able to check if a user has access to a view with a given
object for example as such::
crudlfap.site[YourModel]['detail'].clone(
request=request,
object=obj,
).has_perm()
If you want to open a View to all, set authenticate=False, examples::
class YourDetailView(DetailView):
authenticate = False
class YourRouter(Router):
views = [
YourDetailView,
ListView.clone(authenticate=False), # example with clone
]
Without authenticate=False, the default has_perm() implementation requires the
request user to have the permission corresponding to the
permission_fullcode attribute.
To create the permission with permission_fullcode, you can browse in your
CRUDLFA+ site and navigate to URL list view, for each URL you have link in
the menu called "authorized" that lets you select which groups have this
permission: it will auto-create the permission in the database if
necessary.
"""
[docs] @classmethod
def reverse(cls, *args, **kwargs):
"""Reverse a url to this view with the given args."""
return reverse_lazy(cls.urlfullname, args=args, kwargs=kwargs)
[docs] def get_urlargs(self):
"""
Return args for reversing this view url from self.
See ``self.reverse()`` for detail.
"""
return []
[docs] def get_url(self):
"""
Return the URL for this view given its current state.
Given that the ``reverse()`` method is a class method, this should
allow things like::
url = YourView(object=your_object).url
"""
return self.reverse(*self.urlargs)
[docs] def get_permission_shortcode(self):
"""Return the middle part for the view permission.
Returns the urlname by default.
"""
return self.urlname
[docs] def get_permission_codename(self):
"""Return the codename attribute for the view Permission."""
if not self.model:
return self.permission_shortcode
return f'{self.permission_shortcode}_{self.model._meta.model_name}'
[docs] def get_permission_fullcode(self):
"""
Return a string with the app name, permission_shortcode and model name.
"""
return f'{self.app_name}.{self.permission_codename}'
def get_authenticate(self):
return True
[docs] def has_perm(self):
"""Checks for user permission."""
if not self.authenticate:
return True
return self.request.user.has_perm(self.permission_fullcode, self)
def get_registry(self):
if self.router:
return self.router.registry
from .site import site
return site
def get_login_url(self):
if self.registry:
return reverse('{}:login'.format(self.registry.app_name))
return reverse('login')
def get_allowed_groups(self):
if not self.router:
return []
return getattr(self.router, 'allowed_groups', [])
[docs] def dispatch(self, request, *args, **kwargs):
"""This will run has_perm prior to super().dispatch()."""
if not self.has_perm():
if not request.user.is_authenticated:
return http.HttpResponseRedirect(
self.login_url + '?next=' + request.path_info
)
else:
return http.HttpResponseForbidden()
return super().dispatch(request, *args, **kwargs)
@classmethod
def factory(cls, view, **attributes):
if isinstance(view, str):
view = import_string(view)
return type(view.__name__, (view, cls), attributes)