Source code for superdjango.views.viewsets

# Imports

from django.urls import path, re_path
from django.core.exceptions import ImproperlyConfigured
from superdjango.exceptions import IMustBeMissingSomething, NoViewForYou
from superdjango.patterns import PATTERNS
from .crud import CreateView, DeleteView, DetailView, ListView, UpdateView

# Exports

__all__ = (
    "ModelViewSet",
    "ViewSet",
)

# BaseView Sets


[docs]class ViewSet(object): """A generic viewset, the main purpose of which is to collect views together to consistently generate their URLs.""" views = None
[docs] def __init__(self, app_name=None, namespace=None): """Initialize a generic view set. :param app_name: The app name. :type app_name: str :param namespace: The namespace in which the app will operate. :type namespace: str """ self.app_name = app_name self.namespace = namespace
[docs] @classmethod def get_pattern_name(cls, view_class): """Get the name of a view. :param view_class: The class-based view. :type view_class: class :rtype: str :raise: ImproperlyConfigured .. note:: The view may override the default behavior by implementing a ``pattern_name`` property or the ``get_pattern_name()`` method. This method must return a ``str``. """ try: return view_class.get_pattern_name() except AttributeError: pass try: return view_class.pattern_name except AttributeError: pass e = "'%s' must define 'pattern_name' attribute or implement the 'get_pattern_name()' method." raise ImproperlyConfigured(e % view_class.__class__.__name__)
[docs] @classmethod def get_pattern_value(cls, view_class): """Get the value (path) of a view. :param view_class: The class-based view. :type view_class: class :rtype: str :raise: ImproperlyConfigured """ try: return view_class.get_pattern_value() except AttributeError: pass try: return view_class.pattern_value except AttributeError: pass e = "'%s' must define 'pattern_value' attribute or implement the 'get_pattern_value()' method." raise ImproperlyConfigured(e % view_class.__class__.__name__)
[docs] @classmethod def get_pattern_regex(cls, view_class): """Get the regular expression of a view. :param view_class: The class-based view. :type view_class: class :rtype: str | None .. note:: This method does *not* raise an improperly configured, allowing the other methods to fall back to ``get_pattern_value()``. """ try: return view_class.get_pattern_regex() except AttributeError: pass try: return view_class.pattern_regex except AttributeError: pass return None
[docs] def get_url(self, view_class, prefix=None): """Get the url of a view. :param view_class: The class-based view. :type view_class: class :param prefix: A prefix added to the regex. Note that the URL will fail to resolve if the ``^`` appears in the regex for the view. :rtype: URLPattern .. note:: The view may override the default behavior by implementing a ``get_url()`` method. This method must return an instance of ``URLPattern`` using ``re_path()`` or ``path()``. """ # This allows the view to completely override the URL generation process. try: return view_class.get_url() except AttributeError: pass name = self.get_pattern_name(view_class) # Handle regular expressions. regex = self.get_pattern_regex(view_class) if regex is not None: if prefix is not None: regex = prefix + regex return re_path(regex, view_class.as_view(), name=name) # Handle paths. value = self.get_pattern_value(view_class) if prefix is not None: value = prefix + value return path(value, view_class.as_view(), name=name)
[docs] def get_urls(self, prefix=None): """Generate the url objects. :param prefix: A prefix added to the URL. Note that the URL will fail to resolve if ``^`` appears in the regex for the view. :type prefix: str :rtype: list """ views = self.get_views() urls = list() for view_class in views: urls.append(self.get_url(view_class, prefix=prefix)) return urls
[docs] def get_views(self): """Get the views included in the view set. :rtype: list :raise: ImproperlyConfigured """ if self.views is not None: return self.views e = "'%s' must either define 'views' or override 'get_views()'" raise ImproperlyConfigured(e % self.__class__.__name__)
[docs]class ModelViewSet(ViewSet): """Extends :py:class:`ViewSet` to add support for models. **BaseView Name** Unless the ``get_pattern_name()`` method is defined on the view, the name will be automatically determined based on the name of the model plus a suffix for the purpose of the view class: - ``_create`` - ``_delete`` - ``_detail`` - ``_list`` - ``_update`` The name of the view (used to reverse the URL) is established in the following manner: 1. Use the ``pattern_name`` property if it has been set on the view class. 2. Use the result of ``get_pattern_name()`` if the method is defined on the view class. 3. If the view is an extension of any of the model views, the name will be automatically set based on the name of the model plus the purpose of the view class: ``_create``, ``_delete``, ``_detail``, ``_list``, and ``_update``. 4. If none of the other methods have produced a result, the name of the view class will be used. This is rarely ideal, but does provide a default and prevents throwing an error. **Pattern Value** Unless the ``get_pattern_value()`` method is defined, the base pattern will be automatically determined based on the purpose of the view class: - ``create`` - ``delete`` - ``detail`` - ``update`` List views are assumed to be the index of the app's URLs. Views that require an identifier will use the ``lookup_field`` on the view class. **Example** .. code-block:: py # views.py from superdjango.views.models import CreateView, DeleteView, DetailView, ListView, UpdateView from superdjango.views.viewsets import ModelViewSet from .models import Task class CreateTask(CreateView): model = Task # ... class DeleteTask(DeleteView): model = Task # ... class ListTasks(ListView) model = Task # ... class TaskDetail(DetailView): model = Task # ... class UpdateTask(UpdateView): model = Task # ... class TaskViewSet(ViewSet): views = [ CreateTask, DeleteTask, ListTasks, TaskDetail, UpdateTask, ] .. code-block:: py # urls.py from views import TaskViewSet urlpatterns = TaskViewSet().get_urls() """
[docs] @classmethod def get_pattern_name(cls, view_class): """Get the name of a view based on the implemented model view. :param view_class: The class-based view. :type view_class: class :rtype: str :raise: NoViewForYou """ # This allows the view class to override the automatic behavior below. This is helpful when multiple model views # are defined in the same view set. try: return ViewSet.get_pattern_name(view_class) except ImproperlyConfigured: pass # The model attribute must be defined. try: model = view_class.get_model() except (AttributeError, IMustBeMissingSomething) as e: raise NoViewForYou(str(e)) # App name and model name are both required for a unique view name. # noinspection PyProtectedMember app_label = model._meta.app_label # noinspection PyProtectedMember model_name = model._meta.model_name # Get the action/verb used in the name. verb = cls.get_pattern_verb(view_class) if verb is None: message = "A verb for the %s view could not be determined; add a class method get_pattern_verb() or " \ "class attribute or pattern_verb to help identify this view." raise NoViewForYou(message % view_class.__name__) # We have a winner. return "%s_%s_%s" % (app_label, model_name, verb)
[docs] @classmethod def get_pattern_value(cls, view_class): """Get the path of the model view based on the CBV it extends. :param view_class: The class-based view. :type view_class: class :rtype: str :raise: NoViewForYou """ # This allows the view class to override the automatic behavior below. try: return ViewSet.get_pattern_value(view_class) except ImproperlyConfigured: pass # The model attribute must be defined. try: model = view_class.get_model() except (AttributeError, IMustBeMissingSomething) as e: raise NoViewForYou(str(e)) # noinspection PyProtectedMember model_name = model._meta.model_name # Get the action/verb that the view represents. verb = cls.get_pattern_verb(view_class) if verb is None: message = "A verb for the %s view could not be determined; add a class method get_pattern_verb() or " \ "class attribute or pattern_verb to help identify this view." raise NoViewForYou(message % view_class.__name__) # Add and list verbs are not included in the URL and do not utilize a pattern. if verb == "create": return "%s/create/" % model_name elif verb == "list": return "%s/" % model_name else: try: pattern = PATTERNS[view_class.lookup_field] return "%s/%s/%s/" % (model_name, verb, pattern) except KeyError: message = "A pattern for the %s verb could not be determined because the lookup_field of %s view is " \ "either undefined or is not a recognized pattern identifier." raise NoViewForYou(message % (verb, view_class.__name__))
[docs] @classmethod def get_pattern_verb(cls, view_class): """Get the action (verb) represented by the view class. :rtype: str | None """ # This allows the view class to override the automatic behavior below. This is helpful for custom model views # that define their own verb. try: return view_class.get_pattern_verb() except AttributeError: pass try: return view_class.pattern_verb except AttributeError: pass if issubclass(view_class, CreateView): return "create" elif issubclass(view_class, DeleteView): return "delete" elif issubclass(view_class, DetailView): return "detail" elif issubclass(view_class, ListView): return "list" elif issubclass(view_class, UpdateView): return "update" else: return None