Source code for superdjango.patterns.library

# Imports

from django.core.exceptions import FieldDoesNotExist
from django.db import models
from django.urls import path, re_path, reverse, reverse_lazy, NoReverseMatch

# Exports

__all__ = (
    "Pattern",
    "ModelPattern",
)

# Classes


[docs]class Pattern(object): """A generic pattern for representing a view."""
[docs] def __init__(self, name, namespace=None): """Initialize the pattern. :param name: The pattern name. :type name: str :param namespace: The namespace in which the pattern exists. :type namespace: str """ self.name = name self.namespace = namespace
def __repr__(self): return "<%s %s:%s>" % (self.__class__.__name__, self.namespace, self.name)
[docs] def get_name(self, namespace=None): """Get the name of the view with the ``namespace`` if one has been provided. :param namespace: The namespace in which the view operates. :type namespace: str :rtype: str """ _namespace = None if namespace or self.namespace: _namespace = namespace or self.namespace if _namespace is not None: return "%s_%s" % (_namespace, self.name) return self.name
[docs] def reverse(self, *args, **kwargs): """Reverse the pattern.""" return reverse(self.get_name(), args=args, kwargs=kwargs)
[docs] def reverse_lazy(self, *args, **kwargs): """Lazy reverse the pattern.""" return reverse_lazy(self.get_name(), args=args, kwargs=kwargs)
[docs]class ModelPattern(object): """A helper class for encapsulating data for model views to derive the necessary components for automatic URL definition. Primarily used by the UI package to automatically generate URLs for the user interface. However, programmatic use is also possible: .. code-block:: python update = ModelPattern( Project, ProjectUpdate, lookup_field="uuid" ) """
[docs] def __init__(self, model, view, list_path="", lookup_field=None, lookup_key=None, name=None, namespace=None, prefix=None, regex=None, verb=None): """Initialize a view pattern. :param model: The model class. This is used only during instantiation. :type model: class :param view: The view class. :type view: class :param list_path: The part of the path that represents the model's list view. This defaults to an empty string, but may be given if something else will live at the "index" of the model's views. For example, ``list_path="index"`` or ``list_path="list"``. :type list_path: str :param lookup_field: The name of the field used to uniquely identify a record. This is required for any view that resolves to a specific record. Default: ``uuid``. :type lookup_field: str :param name: If given, this is used as the pattern name of the view instead of composing the name using pattern attributes. :type name: str :param namespace: The namespace in which the view operates. :type namespace: str :param prefix: The prefix of the URL. For example, the model's ``app_label``. :type prefix: str :param regex: A regular expression used to identify the view in URLs. This is optional. :type regex: str :param verb: The name (verb) of the view. For example: "create", "list", "detail", etc. :type verb: str .. important:: ``lookup_field`` should only be provided for views that actually require identifying a specific record. """ self.list_path = list_path self.lookup_field = lookup_field self.lookup_key = lookup_key or lookup_field self.namespace = namespace self.prefix = prefix self.view = view self.verb = verb # noinspection PyProtectedMember self._app_label = model._meta.app_label self._name = name self._regex = regex # noinspection PyProtectedMember self._model_name = model._meta.model_name
def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.get_name())
[docs] def get_name(self, namespace=None): """Get the name of the view. :param namespace: The namespace in which the view operates. :type namespace: str :rtype: str """ # if self._name is not None: # name = self._name # else: # name = self.get_verb() # The only certain way of providing unique pattern name is to include the app label and model name before the # name of the action. if self._name: pattern_name = "%s_%s" % (self._app_label, self._name) else: pattern_name = "%s_%s_%s" % (self._app_label, self._model_name, self.get_verb()) # Handle namespace. _namespace = None if namespace or self.namespace: _namespace = namespace or self.namespace if _namespace is not None: pattern_name = "%s_%s" % (_namespace, pattern_name) return pattern_name
[docs] def get_path(self): """Get the URL path for the view. :rtype: str """ name = self.get_name() if "_create" in name: value = "create/" elif "_list" in name: value = self.list_path elif self.lookup_field is not None: if self.lookup_field == "pk": converter = "<int:%s>" % self.lookup_key else: converter = "<%s:%s>" % (self.lookup_field, self.lookup_key) value = "%s/%s/" % (self.get_verb().replace("_", "/"), converter) else: value = self.get_verb().replace("_", "/") + "/" value = "%s/%s" % (self._model_name, value) if self.prefix is not None: value = "%s/%s" % (self.prefix, value) return value
[docs] def get_regex(self): """Get the regular expression for the view. :rtype: str | None """ if self._regex is None: return None regex = self._regex if self.prefix: regex = "%s/%s" % (self.prefix, regex) return regex
[docs] def get_url(self): """Get the URL object for the view. :rtype: RegexPattern | RoutePattern """ if self._regex is not None: return re_path(self.get_regex(), self.view, name=self.get_name()) return path(self.get_path(), self.view, name=self.get_name())
[docs] def get_verb(self): """Get the action (verb) that the view represents. :rtype: str """ if self.verb is not None: return self.verb return self.view.__name__.replace("_view", "")
@property def name(self): """Alias for ``get_name()``.""" return self.get_name() @property def path(self): """Alias for ``get_path()``.""" return self.get_path() @property def regex(self): """Alias for ``get_regex()``.""" return self.get_regex()
[docs] def reverse(self, namespace=None, record=None): """Use Django's ``reverse()`` to obtain the URL. :param namespace: The namespace in which the view operates. :type namespace: str :param record: The model instance. :rtype: str | None """ args = None if record is not None: args = [self._get_identifier(record)] try: return reverse(self.get_name(namespace=namespace), args=args) except NoReverseMatch: return None
# TODO: Determine whether reverse_lazy() is actually useful.
[docs] def reverse_lazy(self, namespace=None, record=None): """Use Django's ``reverse_lazy()`` to obtain the URL. :param namespace: The namespace in which the view operates. :type namespace: str :param record: The model instance. :rtype: str """ args = None if record is not None: args = [self._get_identifier(record)] return reverse_lazy(self.get_name(namespace=namespace), args=args)
# noinspection PyMethodMayBeStatic def _field_exists(self, field_name, record, unique=False): """Determine if the given field name is defined on the model. :param record: The model instance may be used to determine which field to use. :param field_name: The name of the field to check. :type field_name: str :param unique: Also test whether the field is unique. :type unique: bool :rtype: bool """ try: # noinspection PyProtectedMember field = record._meta.get_field(field_name) if unique: if isinstance(field, models.UUIDField): return True return field.unique return True except FieldDoesNotExist: return False def _get_identifier(self, record): """Get the value of the given record's lookup field. :param record: The model instance. :rtype: int | str """ lookup_field = self._get_lookup_field(record) return getattr(record, lookup_field) def _get_lookup_field(self, record): """Get (guess) the name of the field that uniquely identifies the record. :param record: The model instance may be used to determine which field to use. :rtype: str """ if self.lookup_field is not None: return self.lookup_field if self._field_exists("unique_id", record, unique=True): return "unique_id" if self._field_exists("uuid", record, unique=True): return "uuid" if self._field_exists("slug", record, unique=True): return "slug" return "pk"