Router and router mixins¶
Contents
A router is an implementation of the abstract class Entity, that uses an entity store to allow routing other entities. In the code, this is represented by subclassing django_crucrudile.entities.store.EntityStore and django_crucrudile.entities.Entity, and providing a generator in patterns(), yielding URL patterns made from the entity store. Providing django_crucrudile.entities.Entity.patterns() makes router classes concrete implementations of the Entity abstract class, which allows them to be used in entity stores.
This module contains three implementations of routers, a simple one, and two implementations adapted to Django models :
- Router : implements the abstract class django_crucrudile.entities.Entity, and subclassing django_crucrudile.entities.store.EntityStore to implement Router.patterns()
- model.ModelRouter : subclasses Router, instantiate with a model as argument, adapted to pass that model as argument to registered entity classes
- model.generic.GenericModelRouter : that subclasses model.ModelRouter along with a set of default django_crucrudile.routes.ModelViewRoute for the five default Django generic views.
Base router¶
- class django_crucrudile.routers.Router(namespace=None, url_part=None, redirect=None, add_redirect=None, add_redirect_silent=None, get_redirect_silent=None, generic=None, **kwargs)[source]¶
Bases: django_crucrudile.entities.store.EntityStore, django_crucrudile.entities.Entity
RoutedEntity that yields an URL group containing URL patterns from the entities in the entity store (django_crucrudile.entities.store.EntityStore). The URL group can be set have an URL part and na namespace.
Also handles URL redirections : allows setting an Entity as “index”, which means that it will become the default routed entity for the parent entity (implementation details in get_redirect_pattern()).
- redirect_max_depth = 100¶
Attribute redirect_max_depth: Max depth when following redirect attributes
- __init__(namespace=None, url_part=None, redirect=None, add_redirect=None, add_redirect_silent=None, get_redirect_silent=None, generic=None, **kwargs)[source]¶
Initialize Router base attributes from given arguments
Parameters: - namespace – Optional. See namespace
- url_part – Optional. See url_part
- redirect – Optional. See redirect
- add_redirect – Optional. See add_redirect
- add_redirect_silent – Optional. See add_redirect_silent
- get_redirect_silent – Optional. See get_redirect_silent
- generic – Optional. See generic
- namespace = None¶
Attribute namespace: If defined, group this router’s patterns in an URL namespace
- url_part = None¶
Attribute url_part: If defined, add to router URL (use when as regex when building URL group)
- redirect = None¶
Attribute redirect: If defined, Router will add a redirect view to the returned patterns. To get the redirect target, get_redirect_pattern() will follow redirect attributes in the stored entities. The attribute’s value is altered by the register(), if index is True in its arguments or if the registered entity django_crucrudile.entities.Entity.index attribute is set to True.
- add_redirect = None¶
Attribute add_redirect: Add redirect pattern when calling patterns(). If None (default), will be guessed using redirect (Add redirect only if there is one defined)
- add_redirect_silent = False¶
Attribute add_redirect_silent: Fail silently when the patterns reader is asked to add the redirect patterns and the redirect attribute is not set (on self). Defaults to False, because in the default configuration, add_redirect is guessed using redirect, using bool. Set to True if you’re using add_redirect explicitly and want the redirect pattern to be optional.
- get_redirect_silent = False¶
Attribute get_redirect_silent: Fail silently when following redirect attributes to find the redirect URL name (if no URL name is found).
- generic = False¶
Attribute generic: If True, get_register_map() will return a model.generic.GenericModelRouter (with preconfigured Django videws) for the Model type.
- get_register_map()[source]¶
Add two base register mappings (to the mappings returned by the super implementation)
- django.db.models.Model subclasses are passed to a model.ModelRouter (or model.generic.GenericModelRouter) if generic is set to True)
- django.views.generic.detail.SingleObjectMixin or django.views.generic.list.MultipleObjectMixin subclasses are to passed to a django_crucrudile.routes.ModelViewRoute
- django.views.generic.View subclasses are passed to a View
returns: Register mappings rtype: dict Warning
When overriding, remember that the first matching mapping (in the order they are added, as the superclass returns a collections.OrderedDict) will be used.
Because of this, to override mappings, methods should add the super mappings to another OrderedDict, containing the overrides.
- register(entity, index=False, map_kwargs=None)[source]¶
Register routed entity, using django_crucrudile.entities.store.EntityStore.register()
Set as index when index or entity.index is True.
Parameters: - entity (django_crucrudile.entities.Entity) – Entity to register
- index (bool) – Register as index (set redirect to entity
- map_kwargs (dict) – Optional. Keyword arguments to pass to mapping value if entity gets transformed.
>>> from mock import Mock >>> router = Router()
>>> entity = Mock() >>> entity.index = False >>> >>> router.register(entity) is not None True >>> router.redirect is None True
>>> entity = Mock() >>> entity.index = False >>> >>> router.register(entity, index=True) is not None True >>> router.redirect is entity True
>>> entity = Mock() >>> entity.index = True >>> >>> router.register(entity) is not None True >>> router.redirect is entity True
- get_redirect_pattern(namespaces=None, silent=None, redirect_max_depth=None)[source]¶
Compile the URL name to this router’s redirect path (found by following Router.redirect), and that return a lazy django.views.generic.RedirectView that redirects to this URL name
Parameters: - namespaces (list of str) – Optional. The list of namespaces will be used to get the current namespaces when building the redirect URL name. If not given an empty list will be used.
- silent (bool) – Optional. See Router.get_redirect_silent
- redirect_max_depth (int) – Optional. See Router.redirect_max_depth
Raises: - OverflowError – If the depth-first search in the graph made from redirect attributes reaches the depth in redirect_max_depth (to intercept graph cycles)
- ValueError – If no redirect found when following redirect attributes, and silent mode is not enabled.
>>> from mock import Mock >>> entity = Mock() >>> entity.redirect.redirect = 'redirect_target' >>> >>> router = Router() >>> router.register(entity) is not None True >>> >>> pattern = router.get_redirect_pattern() >>> >>> type(pattern).__name__ 'RegexURLPattern' >>> pattern.callback.__name__ 'RedirectView' >>> pattern._target_url_name 'redirect_target'
>>> from mock import Mock >>> entity = Mock() >>> entity.redirect.redirect = 'redirect_target' >>> >>> router = Router() >>> router.register(entity) is not None True >>> >>> pattern = router.get_redirect_pattern( ... namespaces=['ns1', 'ns2'] ... ) >>> type(pattern).__name__ 'RegexURLPattern' >>> pattern.callback.__name__ 'RedirectView' >>> pattern._target_url_name 'ns1:ns2:redirect_target'
>>> entity = Mock() >>> entity.redirect.redirect = entity >>> >>> router = Router() >>> router.register(entity) is not None True >>> >>> router.get_redirect_pattern() ... Traceback (most recent call last): ... OverflowError: Depth-first search reached its maximum (100) depth, without returning a leaf item (string).Maybe the redirect graph has a cycle ?
>>> entity = Mock() >>> entity.__str__ = lambda x: 'mock redirect' >>> entity.redirect = None >>> >>> router = Router() >>> router.register(entity) is not None True >>> >>> router.get_redirect_pattern() ... Traceback (most recent call last): ... ValueError: Failed following redirect attribute (mock redirect) (last redirect found : mock redirect, redirect value: None)) in Router
- patterns(namespaces=None, add_redirect=None, add_redirect_silent=None)[source]¶
Read _store and yield a pattern of an URL group (with url part and namespace) containing entities’s patterns (obtained from the entity store), also yield redirect patterns where defined.
Parameters: - namespaces (list of str) – We need patterns() to pass namespaces recursively, because it may be needed to make redirect URL patterns
- add_redirect (bool) – Override Router.add_redirect
- add_redirect_silent – Override Router.add_redirect_silent
>>> from mock import Mock >>> router = Router() >>> pattern = Mock()
>>> entity_1 = Mock() >>> entity_1.index = False >>> entity_1.patterns = lambda *args: ['MockPattern1'] >>> >>> router.register(entity_1) is not None True >>> >>> list(router.patterns()) [<RegexURLResolver <str list> (None:None) ^>] >>> next(router.patterns()).url_patterns ['MockPattern1']
>>> entity_2 = Mock() >>> entity_2.index = True >>> entity_2.redirect = 'redirect_target' >>> entity_2.patterns = lambda *args: ['MockPattern2'] >>> >>> router.register(entity_2) is not None True >>> >>> list(router.patterns()) ... [<RegexURLResolver <RegexURLPattern list> (None:None) ^>] >>> next(router.patterns()).url_patterns ... [<RegexURLPattern redirect_target-redirect ^$>, 'MockPattern1', 'MockPattern2']
>>> router.redirect = None >>> >>> list(router.patterns(add_redirect=True)) ... Traceback (most recent call last): ... ValueError: No redirect attribute set (and ``add_redirect_silent`` is ``False``).
Router mixins¶
Model router mixin¶
- class django_crucrudile.routers.mixins.model.ModelMixin(model=None, url_part=None, **kwargs)[source]¶
Bases: builtins.object
ModelRouter with no views. Give model kwarg where needed, ask it in __init__(), and map SingleObjectMixin and MultipleObjectMixin to django_crucrudile.routes.ModelViewRoute in register functions.
- __init__(model=None, url_part=None, **kwargs)[source]¶
Read model (from args or class-level value (model), fail if none found.
Parameters: model (django.db.Models) – see model Raises ValueError: if model not passed an argument and not defined on class
- model = None¶
Attribute model: Model used when building router URL name and URL part, and passed to registered routes. Must be defined at class-level or passed to __init__().
- model_url_part[source]¶
Return the model name to be used when building the URL part
Returns: URL part from the Django model name (model._meta.model_name) Return type: str >>> from mock import Mock >>> >>> model = Mock() >>> model._meta.model_name = 'testmodel' >>> route = ModelMixin(model=model) >>> >>> route.model_url_part 'testmodel'
- get_register_map_kwargs()[source]¶
Add model as kwarg when applying register map
Returns: Keyword arguments to pass to mapping value, when applying register map (from get_register_map()) in register() Return type: dict See also
For doctests that use this member, see django_crucrudile.routers.model.ModelRouter
- get_base_store_kwargs()[source]¶
Add model so that the route classes in the base store will get the model as a kwarg when being instantiated
Returns: Keyword arguments to pass to mapping value, when applying class register map (from get_register_class_map()) in register_class() Return type: dict See also
For doctests that use this member, see django_crucrudile.routers.model.ModelRouter
- classmethod get_register_class_map()[source]¶
Add django_crucrudile.routes.ModelViewRoute.make_for_view() mapping for SingleObjectMixin and MultipleObjectMixin.
This mapping allows registering Django generic views in the base store, so that entities made using django_crucrudile.routes.ModelViewRoute (and instantiated with model) get registered in the entity store when ModelMixin gets instantiated (in django_crucrudile.entities.store.EntityStore.register_base_store()).
django_crucrudile.routes.ModelViewRoute.make_for_view() creates a new django_crucrudile.routes.ModelViewRoute class, and uses its argument as the django_crucrudile.routes.ViewRoute.view_class attribute.
Returns: Base store mappings Return type: dict Warning
When overriding, remember that the first matching mapping (in the order they are added, as the superclass returns a collections.OrderedDict) will be used.
Because of this, to override mappings, methods should add the super mappings to another OrderedDict, containing the overrides.
See also
For doctests that use this member, see django_crucrudile.routers.model.ModelRouter
Model router¶
- class django_crucrudile.routers.model.ModelRouter(model=None, url_part=None, **kwargs)[source]¶
Bases: django_crucrudile.routers.mixins.model.ModelMixin, django_crucrudile.routers.Router
Model router, implements django_crucrudile.routers.mixins.model.ModelMixin`with :class:`django_crucrudile.routers.Router, to provide a Model that passes the model when instantiating entities.
>>> from django.views.generic.detail import SingleObjectMixin >>> from mock import Mock >>> >>> class GenericView(SingleObjectMixin): ... pass >>> class NotGenericView: ... pass
>>> model = Mock() >>> view = Mock() >>> >>> model._meta.model_name = 'modelname' >>> >>> router = ModelRouter(model=model) >>> >>> router.model_url_part 'modelname'
>>> router.register(GenericView) is not None True
Generic model router¶
- class django_crucrudile.routers.model.generic.GenericModelRouter(model=None, url_part=None, **kwargs)[source]¶
Bases: django_crucrudile.routers.model.ModelRouter
Generic model router, subclasses django_crucrudile.routers.model.ModelRouter and use 5 Django generic views for each registered model.
Provides specific django_crucrudile.routes.ModelViewRoute classes, created for the following Django generic views :
- django.views.generic.ListView
- django.views.generic.DetailView
- django.views.generic.CreateView
- django.views.generic.UpdateView
- django.views.generic.DeleteView
These classes are registered in the base store, using django_crucrudile.entities.store.EntityStore.register_class() or the django_crucrudile.entities.store.provides() decorator. They will be instantiated (with the model as argument) when the router is itself instantiated, using django_crucrudile.entities.store.EntityStore.register_base_store().
We use our own class mapping, to use our own route, because we want to add the arguments specification to the route automatically.
>>> # these two lines are required to subclass Django model in doctests >>> import tests.unit >>> __name__ = "tests.doctests" >>> from django.db.models import Model >>> from django_crucrudile.routers import Router, ModelRouter >>> >>> class TestModel(Model): ... pass
>>> router = Router(generic=True) >>> >>> router.register(TestModel) is not None True
>>> print(router.get_str_tree()) ... - Router @ ^ - GenericModelRouter testmodel @ ^testmodel/ - testmodel-list-redirect @ ^$ RedirectView - testmodel-delete @ ^delete/(?P<pk>\d+)$ DeleteView - testmodel-delete @ ^delete/(?P<slug>[\w-]+)$ DeleteView - testmodel-update @ ^update/(?P<pk>\d+)$ UpdateView - testmodel-update @ ^update/(?P<slug>[\w-]+)$ UpdateView - testmodel-create @ ^create$ CreateView - testmodel-detail @ ^detail/(?P<pk>\d+)$ DetailView - testmodel-detail @ ^detail/(?P<slug>[\w-]+)$ DetailView - testmodel-list @ ^list$ ListView
- classmethod get_register_class_map()[source]¶
Override super implementation to set the mapping for Django generic views to django_crucrudile.routes.GenericModelViewRoute.
For doctests that use this member, see django_crucrudile.routers.model.generic.GenericModelRouter