# 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):
"""
Called to decide if user has the permission to execute this view.
This is what you should override to code view level permission logic,
unless you want to use a Django permission backend.
If unsure, you probably need to just define Router.has_perm.
Default behaviour:
- return True if the view ``authenticate`` attribute has been
overridden to False
- return self.router.has_perm(self) if the view has a router
- return self.has_perm_backend otherwise, to let a Django permission
backend decide
"""
if not self.authenticate:
return True
if self.router:
return self.router.has_perm(self)
return self.has_perm_backend()
[docs]
def has_perm_backend(self):
"""
Django Authentication backend has_perm() call.
This is called by Route.has_perm by default, from Router.has_perm is
the view has a router object, and useful only if you implement your own
permission backend, or add crudlfap_auth.backends.ViewBackend to
settings.AUTHENTICATION_BACKENDS, or implement your own View permission
backend.
If unsure, you probably need to just define Router.has_perm.
"""
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_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(
reverse('login') + '?next=' + request.path_info
)
else:
return http.HttpResponseForbidden()
HTTP_ACCEPT = request.META.get('HTTP_ACCEPT', '')
json_handler = (
request.content_type == 'application/json'
or HTTP_ACCEPT.startswith('application/json')
)
if json_handler and request.method.lower() in self.http_method_names:
handler = f'json_{self.request.method.lower()}'
if hasattr(self, handler):
result = getattr(self, handler)(request, *args, **kwargs)
if isinstance(result, dict):
return http.JsonResponse(result)
return result
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)
[docs]
@classmethod
def abstract(cls, **kwargs):
"""Return an instance of this view."""
return cls(**kwargs)
def get_swagger_path_definition(self):
result = dict()
for method in self.http_method_names:
method_def = getattr(self, f'swagger_{method}', None)
if not method_def:
continue
result[method] = method_def
return result