Source code for document_catalogue.plugins

"""
Dependency Injection Micro-Framework using Plugins

    A Plugin provides some custom behaviour to specific Concrete classes in the class hierarchy
    The Base class of the hierarchy generally applies the plugins by adding PluginManager as a mixin
    Plugins can then be injected into any concrete class in its hierarchy.

"""

from abc import ABCMeta
from itertools import chain

from django.db.models.functions import Lower

#########################
#  Abstract Base Classes - plugin architecture
#########################


[docs]class PluginManager: """ A simple, generic plugin manager. Provides a clean dependency injection mechanism when used as a Mixin: BaseClass(PluginManager) BaseClass implements behaviours for plugins (defined by whatever interface suited to app) ConcreteSubclass(BaseClass) can then RegisterPlugins to inject additional behaviours """ plugins = [] @classmethod def add_plugins(cls, plugins): cls.plugins = list(chain(cls.plugins, plugins))
[docs] @classmethod def apply_plugins(cls, f): """Apply f to each plugin. f must be a callable that takes a single plugin parameter""" for plugin in cls.plugins: try: f(plugin) except AttributeError: pass
[docs]class RegisterPlugins: """ A Decorator for registering a set of plugins to a PluginManager E.g.: @RegisterPlugins(Plugin1(some, parameters), Plugin2()) """ def __init__(self, *plugins): self.plugins = plugins def __call__(self, decorated_class): decorated_class.add_plugins(self.plugins) return decorated_class
######################### # Concrete Implementations - document_catalogue plugins #########################
[docs]class ViewPluginManager(PluginManager): """Encapsulates logic specific to applying AbstractViewPlugin plugins. Intended as View mixin"""
[docs] @classmethod def plugins_extend_qs(cls, request, qs): """Apply each plugin to the given queryset, in sequence, return resulting queryset""" for plugin in cls.plugins: try: qs = plugin.extend_qs(request, qs) except AttributeError: pass return qs
@classmethod def plugins_get_context(cls, request): context = {} cls.apply_plugins(lambda plugin: context.update(plugin.get_context(request))) return context
[docs]class AbstractViewPlugin(metaclass=ABCMeta): """Defines the API for a plugin that injects behaviour into a View class"""
[docs] def apply(self, request): """Apply the plugin to the given request just prior to dispatching it""" pass
[docs] def extend_qs(self, request, qs): """Extend, modify, or constrain the base document queryset and return it""" return qs
[docs] def get_context(self, request): """Return a dictionary to be added to the View's context""" return {}
[docs]class OrderedViewPlugin(AbstractViewPlugin): """Applies ordering to view's queryset based on URL query argument found in request.GET""" ORDERING_CHOICES = ( ("default", "Default"), ("date", "Recently Updated"), ("title", "Title"), ) ORDERING_KEYS = tuple(k for k, v in ORDERING_CHOICES) ORDERING_EXPRESSION = ( { # valid ordering expressions maps query param value to ordering clause "date": "-update_date", "title": Lower("title").asc(), } ) def __init__(self, query_param="dc_ordering"): """Name of the query parameter used to specify ordering""" super().__init__() self.query_param = query_param def get_ordering_key(self, request): key = request.GET.get(self.query_param, None) return key if key in self.ORDERING_KEYS else None
[docs] def get_ordering(self, request): """Return the order_by experession for the queryset""" return self.ORDERING_EXPRESSION.get(self.get_ordering_key(request), None)
[docs] def extend_qs(self, request, qs): ordering = self.get_ordering(request) if ordering: qs = qs.order_by(ordering) return qs
[docs] def get_context(self, request): return { self.query_param: self.get_ordering_key(request), "{ordering}_choices".format( ordering=self.query_param ): self.ORDERING_CHOICES, }
[docs]class SessionOrderedViewPlugin(OrderedViewPlugin): """Applies ordering to view's queryset based on ordering passed in URL query arg and stored in session""" def get_ordering_key(self, request): key = request.session.get(self.query_param, None) return key if key in self.ORDERING_KEYS else None
[docs] def apply(self, request): """Apply the plugin to the given request""" ordering_key = super().get_ordering_key(request) if ordering_key: request.session[self.query_param] = ordering_key