| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999 |
- from django.conf.urls import url
- from django.contrib.auth.decorators import login_required
- from django.core.exceptions import ImproperlyConfigured, ValidationError
- from django.db.models.query import QuerySet
- from django.http import Http404
- from django.urls import reverse
- from django.shortcuts import get_object_or_404
- from django.views import generic
- import django_tables2
- import copy
- import threading
- #import logging
- #logger = logging.getLogger("console")
- LIST_VIEW_TYPE = 'list'
- CREATE_VIEW_TYPE = 'create'
- DETAIL_VIEW_TYPE = 'detail'
- UPDATE_VIEW_TYPE = 'update'
- DELETE_VIEW_TYPE = 'delete'
- # Used in the case where a viewset functions only as a nested list view, and
- # therefore doesn't require any URLs of its own.
- NOT_FOUND_VIEW_TYPE = 'not_found'
- INSTANCE_VIEW_TYPES = [DETAIL_VIEW_TYPE, UPDATE_VIEW_TYPE, DELETE_VIEW_TYPE]
- class SaveErrorHandlerMixin(object):
- def form_valid(self, form):
- try:
- return super(SaveErrorHandlerMixin, self).form_valid(form)
- except ValidationError as e:
- for field, errors in e:
- form.add_error(field, errors)
- return self.form_invalid(form)
- class CrudViewset(object):
- model = None
- queryset = None
- root_breadcrumbs = []
- master_viewset_class = None
- nested_list_view_index = None
- # Setting this to any of the instance view types will cause the master
- # viewset to use its instance_breadcrumb_view_type setting, so the only
- # other value that makes sense is LIST_VIEW_TYPE.
- master_viewset_breadcrumb_view_type = DETAIL_VIEW_TYPE
- # instance_breadcrumb_text.format(instance)
- instance_breadcrumb_text = u"{0}"
- instance_breadcrumb_view_type = DETAIL_VIEW_TYPE
- extra_instance_views = []
- lookup_field = 'pk'
- lookup_url_kwarg = 'pk'
- lookup_url_kwarg_pattern = r'\d+'
- lookup_regex = None
- view_url_kwarg_names = []
- active_nested_list_view_query_param = 't'
- view_decorator = staticmethod(login_required)
- exclude_views = []
- create_button_text = 'Create'
- create_button_class = 'btn-primary'
- edit_button_text = 'Edit'
- edit_button_class = 'btn-primary'
- delete_button_text = 'Delete'
- delete_button_class = 'btn-danger'
- back_button_text = 'Back'
- back_button_class = 'btn-default'
- base_template_shim = None
- list_view_template_name = None
- create_view_template_name = 'crud/create_view.html'
- detail_view_template_name = 'crud/detail_view.html'
- update_view_template_name = 'crud/update_view.html'
- delete_view_template_name = 'crud/delete_view.html'
- # list_view_title.format(model.verbose_name_plural) if model can be
- # determined. The value is set to None here in case model cannot be
- # determined, in which case a specific value must be provided.
- list_view_title = None
- # create_view_title.format(model.verbose_name) if model can be determined.
- # The value is set to None here in case model cannot be determined, in
- # which case a specific value must be provided.
- create_view_title = None
- # *_view_title.format(view.object)
- detail_view_title = u"{0}"
- update_view_title = u"Edit {0}"
- delete_view_title = u"Delete {0}"
- success_url = None
- create_view_success_url = None
- update_view_success_url = None
- delete_view_success_url = None
- filter_class = None
- form_class = None
- create_view_form_class = None
- update_view_form_class = None
- form_exclude = []
- create_view_form_exclude = None
- update_view_form_exclude = None
- list_view_base_class = generic.ListView
- create_view_base_class = generic.CreateView
- def __init__(self):
- self.master_viewset = None
- self.nested_list_views = []
- self.is_list_view_nested = (self.master_viewset_class is not None) and (self.nested_list_view_index is not None)
- self.list_view_query_param_prefix = self.get_list_view_query_param_prefix()
- # This will be set to an empty string later if no nested list views are
- # linked to this view. We need the value in the detail view before then,
- # however.
- self.nested_list_view_query_param_prefix = self.get_nested_list_view_query_param_prefix()
- # Remove the trailing '-' as django_filter adds it in again.
- self.filter_query_param_prefix = self.list_view_query_param_prefix[:-1]
- # Cache the get_filter_class result in an instance attribute, which will
- # hide the class attribute of the same name.
- self.filter_class = self.get_filter_class()
- self.base_view_mixin = None
- self.views = {}
- self.instance_view_types = set(INSTANCE_VIEW_TYPES)
- self.extra_instance_view_types = set()
- self.exclude_views = set(self.exclude_views)
- if self.is_list_view_nested:
- self.exclude_views.add(LIST_VIEW_TYPE)
- if self.model is None:
- queryset = self.get_queryset(None)
- if isinstance(queryset, QuerySet):
- self.model = queryset.model
- elif self.queryset is None:
- # It's the user's problem if they define 'model' and override
- # 'get_queryset' at the same time.
- self.queryset = self.model._default_manager.all()
- else:
- raise ImproperlyConfigured("%s contains definitions for both 'model' and 'queryset', but only one or the other is allowed" % self.__class__.__name__)
- # If model is None then list_view_title and create_view_title will be
- # left with their default values (which are None by default and will
- # cause the corresponding getters to raise an error).
- if self.model is not None:
- if self.list_view_title is None:
- self.list_view_title = u"{0}"
- self.list_view_title = self.list_view_title.format(self.model._meta.verbose_name_plural.title())
- if self.create_view_title is None:
- self.create_view_title = u"Create {0}"
- self.create_view_title = self.create_view_title.format(self.model._meta.verbose_name.title())
- self.generate_list_view()
- self.generate_create_view()
- self.generate_detail_view()
- self.generate_update_view()
- self.generate_delete_view()
- self.generate_extra_instance_views()
- self.is_view_enabled = {view_type: view is not None for view_type, view in self.views.items()}
- # For convenience.
- if not self.is_view_enabled[DETAIL_VIEW_TYPE] and (self.instance_breadcrumb_view_type == DETAIL_VIEW_TYPE):
- self.instance_breadcrumb_view_type = UPDATE_VIEW_TYPE
- @staticmethod
- def reverse_with_query_params(viewname, *args, **kwargs):
- query_params = kwargs.pop('query_params', None)
- url = reverse(viewname, *args, **kwargs)
- if query_params:
- url += '?' + query_params.urlencode()
- return url
- @staticmethod
- def get_parent_url_name(url_name, remove_count=1):
- return ':'.join(url_name.split(':')[:-remove_count])
- @staticmethod
- def derive_view_url_kwarg_names(master_viewset_class):
- return master_viewset_class.view_url_kwarg_names + [master_viewset_class.lookup_url_kwarg]
- @classmethod
- def depth(cls):
- return 0 if cls.master_viewset_class is None else cls.master_viewset_class.depth() + 1
- def get_master_object(self, view, instance):
- raise ImproperlyConfigured("%s requires an implementation of 'get_master_object'" % self.__class__.__name__)
- def get_list_view_query_param_prefix_core(self, relative_depth):
- # The prefix must include a trailing '-' to match the way django_filter
- # handles prefixes.
- prefix = 'd%d-' % (self.depth() + relative_depth)
- if self.is_list_view_nested and (relative_depth == 0):
- return '%s%d-' % (prefix, self.nested_list_view_index)
- else:
- return prefix
- def get_list_view_query_param_prefix(self):
- return self.get_list_view_query_param_prefix_core(0)
- def get_nested_list_view_query_param_prefix(self):
- return self.get_list_view_query_param_prefix_core(1)
- def remove_list_view_query_params_core(self, GET, prefix):
- if prefix != '':
- filtered = GET.copy()
- for key in filtered.keys():
- if key.startswith(prefix):
- filtered.pop(key, None)
- return filtered
- return GET
- def remove_list_view_query_params(self, GET):
- return self.remove_list_view_query_params_core(GET, self.list_view_query_param_prefix)
- def remove_nested_list_view_query_params(self, GET):
- return self.remove_list_view_query_params_core(GET, self.nested_list_view_query_param_prefix)
- def get_view_url_kwargs(self, view_type, view, instance=None):
- kwargs = {name: view.kwargs[name] for name in self.view_url_kwarg_names}
- if view_type in self.instance_view_types:
- if isinstance(instance, dict):
- kwargs[self.lookup_url_kwarg] = instance.get(self.lookup_field)
- else:
- kwargs[self.lookup_url_kwarg] = getattr(instance, self.lookup_field)
- return kwargs
- def get_view_url(self, view_type, view, GET, instance=None):
- if self.is_list_view_nested and (view_type == LIST_VIEW_TYPE):
- return self.master_viewset.get_view_url(self.master_viewset.instance_breadcrumb_view_type, view, GET, view.cached_objects[self.master_viewset])
- else:
- return self.reverse_with_query_params(self.url_name_prefix + view_type, kwargs=self.get_view_url_kwargs(view_type, view, instance), query_params=self.remove_nested_list_view_query_params(GET) if view_type == LIST_VIEW_TYPE else GET)
- def get_root_breadcrumbs(self, view):
- return self.root_breadcrumbs
- def get_list_view_breadcrumb(self, view, GET, instance=None):
- return {
- 'text': self.get_list_view_title(view),
- 'url': self.get_view_url(LIST_VIEW_TYPE, view, GET)
- }
- def get_instance_breadcrumb_text(self, view, instance):
- return self.instance_breadcrumb_text.format(instance)
- def get_breadcrumbs(self, view_type, view, GET):
- breadcrumbs = []
- if self.master_viewset is None:
- breadcrumbs += self.get_root_breadcrumbs(view)
- else:
- breadcrumbs += self.master_viewset.get_breadcrumbs(self.master_viewset_breadcrumb_view_type, view, GET if self.is_list_view_nested else self.remove_list_view_query_params(GET))
- instance = view.cached_objects[self]
- # The list view of nested viewsets is part of the detail view of the
- # master viewset, and the breadcrumb will already have been added above.
- if not self.is_list_view_nested and self.is_view_enabled[LIST_VIEW_TYPE]:
- breadcrumbs.append(self.get_list_view_breadcrumb(view, GET, instance))
- if view_type in self.instance_view_types:
- breadcrumbs += [{
- 'text': self.get_instance_breadcrumb_text(view, instance),
- 'url': self.get_view_url(self.instance_breadcrumb_view_type, view, GET, instance)
- }]
- return breadcrumbs
- def get_list_view_title(self, view):
- if self.list_view_title is None:
- raise ImproperlyConfigured("%s requires either a definition of 'list_view_title' or an implementation of 'get_list_view_title'" % self.__class__.__name__)
- return self.list_view_title
- def get_create_view_title(self, view):
- if self.create_view_title is None:
- raise ImproperlyConfigured("%s requires either a definition of 'create_view_title' or an implementation of 'get_create_view_title'" % self.__class__.__name__)
- return self.create_view_title
- def get_detail_view_title(self, view):
- return self.detail_view_title.format(view.object)
- def get_update_view_title(self, view):
- return self.update_view_title.format(view.object)
- def get_delete_view_title(self, view):
- return self.delete_view_title.format(view.object)
- """
- If get_queryset is overridden to return an iterable rather than a QuerySet
- you must provide an implementation of:
- get_object(self, view, queryset=None)
- in order for instance views to work correctly. You can also provide an
- implementation of:
- delete_object(self, view, request, *args, **kwargs)
-
- to implement custom deletion logic.
- """
- def get_queryset(self, view):
- if self.queryset is None:
- raise ImproperlyConfigured("%s requires either a definition of 'model' or 'queryset', or an implementation of 'get_queryset'" % self.__class__.__name__)
- return self.queryset
- def get_object(self, view, queryset=None):
- if queryset is None:
- queryset = self.get_queryset(view)
- queryset = queryset.filter(**{self.lookup_field: view.kwargs[self.lookup_url_kwarg]})
- return get_object_or_404(queryset)
- def get_create_view_object(self, view):
- return None
- def add_context_data(self, view, context):
- pass
- def add_list_view_context_data(self, view, GET, context):
- pass
- def create_view_form_valid(self, view, form):
- pass
- def update_view_form_valid(self, view, form):
- pass
- def get_filter_class(self):
- return self.filter_class
- def get_form_class(self):
- if self.form_class is None:
- raise ImproperlyConfigured("%s requires a definition of either 'form_class' or the appropriate combination of 'create_view_form_class' and 'update_view_form_class'" % self.__class__.__name__)
- return self.form_class
- def get_create_view_form_class(self):
- if self.create_view_form_class is None:
- return self.get_form_class()
- return self.create_view_form_class
- def get_update_view_form_class(self):
- if self.update_view_form_class is None:
- return self.get_form_class()
- return self.update_view_form_class
- def get_form_exclude(self, view):
- return self.form_exclude
- def get_create_view_form_exclude(self, view):
- if self.create_view_form_exclude is None:
- return self.get_form_exclude(view)
- return self.create_view_form_exclude
- def get_update_view_form_exclude(self, view):
- if self.update_view_form_exclude is None:
- return self.get_form_exclude(view)
- return self.update_view_form_exclude
- def get_create_view_form_initial(self, view):
- return {}
- def get_update_view_form_initial(self, view):
- return {}
- def get_create_button_text(self):
- return self.create_button_text
- def get_create_button_class(self):
- return self.create_button_class
- def get_edit_button_text(self):
- return self.edit_button_text
- def get_edit_button_class(self):
- return self.edit_button_class
- def get_delete_button_text(self):
- return self.delete_button_text
- def get_delete_button_class(self):
- return self.delete_button_class
- def get_back_button_text(self):
- return self.back_button_text
- def get_back_button_class(self):
- return self.back_button_class
- def get_relative_view_url(self, url_name_suffix, remove_url_name_components=0, kwargs=None, query_params=None):
- if remove_url_name_components > 0:
- # url_name_prefix has a trailing colon so we always need to remove
- # an extra component.
- url_name_prefix = self.get_parent_url_name(self.url_name_prefix, remove_url_name_components + 1) + ':'
- else:
- url_name_prefix = self.url_name_prefix
- return self.reverse_with_query_params(url_name_prefix + url_name_suffix, kwargs=kwargs, query_params=query_params)
- def get_success_url(self, view):
- if self.success_url is None:
- return self.get_view_url(LIST_VIEW_TYPE, view, view.request.GET)
- return self.success_url
- def get_create_view_success_url(self, view):
- if self.create_view_success_url is None:
- return self.get_success_url(view)
- return self.create_view_success_url
- def get_update_view_success_url(self, view):
- if self.update_view_success_url is None:
- return self.get_success_url(view)
- return self.update_view_success_url
- def get_delete_view_success_url(self, view):
- if self.delete_view_success_url is None:
- return self.get_success_url(view)
- return self.delete_view_success_url
- def get_base_view_mixin(self):
- if self.base_view_mixin is None:
- class BaseViewMixin(object):
- viewset = self
- view_type = None
- pk_url_kwarg = viewset.lookup_url_kwarg
- def get_queryset(self):
- return self.viewset.get_queryset(self)
- # Derived classes must inherit or define
- # def get_view_title(self)
- def get_cached_object(self, viewset=None):
- return self.cached_objects[self.viewset if viewset is None else viewset]
- def get_object(self, *args, **kwargs):
- return self.cached_objects[self.viewset]
- def populate_object_cache(self, instance):
- # Populate a cache of objects for all viewsets in the
- # master-detail chain so all lookup logic is in one place
- # and we don't have to worry about accidentally doing
- # the same lookup twice in other functions.
- viewset = self.viewset
- viewset_object = instance
- self.cached_objects = {viewset: viewset_object}
- while viewset.master_viewset is not None:
- if viewset_object is None:
- viewset_object = viewset.master_viewset.get_object(self)
- else:
- viewset_object = viewset.get_master_object(self, viewset_object)
- viewset = viewset.master_viewset
- self.cached_objects[viewset] = viewset_object
- def get_context_data(self, *args, **kwargs):
- context = super(BaseViewMixin, self).get_context_data(*args, **kwargs)
- viewset = self.viewset
- breadcrumbs = viewset.get_breadcrumbs(self.view_type, self, self.request.GET)
- final_breadcrumb = getattr(self, 'final_breadcrumb', None)
- if final_breadcrumb is not None:
- breadcrumbs.append(
- {
- 'text': final_breadcrumb,
- # Includes the query string.
- 'url': self.request.get_full_path()
- }
- )
- context['base_template_shim'] = viewset.base_template_shim
- context['breadcrumbs'] = breadcrumbs
- context['query_string'] = self.request.GET.urlencode()
- context['view_title'] = self.get_view_title()
- context['is_view_enabled'] = viewset.is_view_enabled
- context['actions'] = []
- if (self.view_type in viewset.extra_instance_view_types):
- back_url = viewset.get_view_url(self.viewset.instance_breadcrumb_view_type, self, self.request.GET, self.get_object())
- elif ((self.view_type != LIST_VIEW_TYPE) and (viewset.is_list_view_nested or viewset.is_view_enabled[LIST_VIEW_TYPE])):
- back_url = viewset.get_view_url(LIST_VIEW_TYPE, self, self.request.GET)
- else:
- back_url = None
- if back_url is not None:
- context['actions'].append(
- [
- {
- 'type': 'button',
- 'text': viewset.get_back_button_text(),
- 'url': back_url,
- 'button_class': viewset.get_back_button_class()
- }
- ]
- )
- viewset.add_context_data(self, context)
- return context
- self.base_view_mixin = BaseViewMixin
- return self.base_view_mixin
- def get_decorated_view(self, view_class):
- view = view_class.as_view()
- if self.view_decorator is not None:
- view = self.view_decorator(view)
- view.viewset = self
- return view
- def is_view_generated(self, view_type):
- if view_type in self.views:
- return True
- if view_type in self.exclude_views:
- self.views[view_type] = None
- return True
- return False
- def generate_list_view(self):
- if self.is_view_generated(LIST_VIEW_TYPE):
- return
- class ListView(self.get_base_view_mixin(), self.list_view_base_class):
- viewset = self
- view_type = LIST_VIEW_TYPE
- template_name = self.list_view_template_name
- filter = None
- def dispatch(self, request, *args, **kwargs):
- self.populate_object_cache(None)
- return super(ListView, self).dispatch(request, *args, **kwargs)
- def get_view_title(self):
- return self.viewset.get_list_view_title(self)
- def get_context_data(self, *args, **kwargs):
- context = super(ListView, self).get_context_data(*args, **kwargs)
- if self.filter is not None:
- context['filter'] = self.filter
- self.viewset.add_list_view_context_data(self, self.request.GET, context)
- return context
- if self.filter_class is not None:
- filter_class = self.filter_class
- prefix = self.filter_query_param_prefix
- # NOTE: This will only be called if base_list_view_class is a
- # ListView, not a TemplateView.
- def get_queryset(self):
- # Sanity check: verify that get_queryset() is called only once
- # per request.
- if self.filter is not None:
- raise RuntimeError("ListView.get_queryset() was called more than once")
- self.filter = filter_class(self.request.GET, super(ListView, self).get_queryset(), prefix=prefix)
- return self.filter.qs
- ListView.get_queryset = get_queryset
- self.views[LIST_VIEW_TYPE] = self.get_decorated_view(ListView)
- def generate_create_view(self):
- if self.is_view_generated(CREATE_VIEW_TYPE):
- return
- class CreateView(SaveErrorHandlerMixin, self.get_base_view_mixin(), self.create_view_base_class):
- viewset = self
- view_type = CREATE_VIEW_TYPE
- form_class = self.get_create_view_form_class()
- template_name = self.create_view_template_name
- final_breadcrumb = 'Create'
- def dispatch(self, request, *args, **kwargs):
- self.populate_object_cache(self.viewset.get_create_view_object(self))
- return super(CreateView, self).dispatch(request, *args, **kwargs)
- def get_view_title(self):
- return self.viewset.get_create_view_title(self)
- def get_context_data(self, *args, **kwargs):
- context = super(CreateView, self).get_context_data(*args, **kwargs)
- context['form_exclude'] = self.viewset.get_create_view_form_exclude(self)
- return context
- def form_valid(self, form):
- self.viewset.create_view_form_valid(self, form)
- return super(CreateView, self).form_valid(form)
- def get_initial(self):
- return self.viewset.get_create_view_form_initial(self)
- def get_form(self, form_class=None):
- form = super(CreateView, self).get_form(form_class)
- form.view = self
- customise = getattr(form, 'customise_for_view', None)
- if customise is not None:
- customise(self)
- return form
- def get_success_url(self):
- return self.viewset.get_create_view_success_url(self)
- self.views[CREATE_VIEW_TYPE] = self.get_decorated_view(CreateView)
- def generate_detail_view(self):
- if self.is_view_generated(DETAIL_VIEW_TYPE):
- return
- active_nested_list_view_query_param = self.nested_list_view_query_param_prefix + self.active_nested_list_view_query_param
- class DetailView(self.get_base_view_mixin(), generic.DetailView):
- viewset = self
- view_type = DETAIL_VIEW_TYPE
- template_name = self.detail_view_template_name
- def dispatch(self, request, *args, **kwargs):
- self.populate_object_cache(self.viewset.get_object(self))
- return super(DetailView, self).dispatch(request, *args, **kwargs)
- def get_view_title(self):
- return self.viewset.get_detail_view_title(self)
- def get_context_data(self, *args, **kwargs):
- context = super(DetailView, self).get_context_data(*args, **kwargs)
- viewset = self.viewset
- action_group = []
- if viewset.is_view_enabled[UPDATE_VIEW_TYPE]:
- edit_button_text = viewset.get_edit_button_text()
- if edit_button_text is not None:
- action_group.append(
- {
- 'type': 'button',
- 'id': 'id_edit_button',
- 'url': viewset.get_view_url(UPDATE_VIEW_TYPE, self, self.request.GET, self.get_object()),
- 'text': edit_button_text,
- 'button_class': viewset.get_edit_button_class()
- }
- )
- if viewset.is_view_enabled[DELETE_VIEW_TYPE]:
- delete_button_text = viewset.get_delete_button_text()
- if delete_button_text is not None:
- action_group.append(
- {
- 'type': 'button',
- 'id': 'id_delete_button',
- 'url': viewset.get_view_url(DELETE_VIEW_TYPE, self, self.request.GET, self.get_object()),
- 'text': delete_button_text,
- 'button_class': viewset.get_delete_button_class()
- }
- )
- if action_group:
- # HACK: Insert after the back button and before any other
- # action groups.
- context['actions'].insert(min(1, len(context['actions'])), action_group)
- context['nested_list_views'] = []
- if self.viewset.nested_list_views:
- active_index = self.request.GET.get(active_nested_list_view_query_param, '')
- have_active = False
- for nested_viewset in self.viewset.nested_list_views:
- index = str(nested_viewset.nested_list_view_index)
- active = index == active_index
- have_active = have_active or active
- GET = self.request.GET.copy()
- GET[active_nested_list_view_query_param] = index
- nested_context = {
- 'active': active,
- 'id': 't' + index,
- 'caption': nested_viewset.get_list_view_title(self),
- 'actions': []
- }
- nested_viewset.add_list_view_context_data(self, GET, nested_context)
- context['nested_list_views'].append(nested_context)
- if not have_active:
- context['nested_list_views'][0]['active'] = True
- return context
- self.views[DETAIL_VIEW_TYPE] = self.get_decorated_view(DetailView)
- def generate_update_view(self):
- if self.is_view_generated(UPDATE_VIEW_TYPE):
- return
- class UpdateView(SaveErrorHandlerMixin, self.get_base_view_mixin(), generic.UpdateView):
- viewset = self
- view_type = UPDATE_VIEW_TYPE
- form_class = self.get_update_view_form_class()
- template_name = self.update_view_template_name
- final_breadcrumb = None if self.instance_breadcrumb_view_type == UPDATE_VIEW_TYPE else 'Update'
- def dispatch(self, request, *args, **kwargs):
- self.populate_object_cache(self.viewset.get_object(self))
- return super(UpdateView, self).dispatch(request, *args, **kwargs)
- def get_view_title(self):
- return self.viewset.get_update_view_title(self)
- def get_context_data(self, *args, **kwargs):
- context = super(UpdateView, self).get_context_data(*args, **kwargs)
- viewset = self.viewset
- if viewset.is_view_enabled[DELETE_VIEW_TYPE]:
- delete_button_text = viewset.get_delete_button_text()
- if delete_button_text is not None:
- context['actions'].append(
- [
- {
- 'type': 'button',
- 'url': viewset.get_view_url(DELETE_VIEW_TYPE, self, self.request.GET, self.get_object()),
- 'text': delete_button_text,
- 'button_class': viewset.get_delete_button_class()
- }
- ]
- )
- context['form_exclude'] = self.viewset.get_update_view_form_exclude(self)
- return context
- def form_valid(self, form):
- self.viewset.update_view_form_valid(self, form)
- return super(UpdateView, self).form_valid(form)
- def get_initial(self):
- return self.viewset.get_update_view_form_initial(self)
- def get_form(self, form_class=None):
- form = super(UpdateView, self).get_form(form_class)
- form.view = self
- customise = getattr(form, 'customise_for_view', None)
- if customise is not None:
- customise(self)
- return form
- def get_success_url(self):
- return self.viewset.get_update_view_success_url(self)
- self.views[UPDATE_VIEW_TYPE] = self.get_decorated_view(UpdateView)
- def generate_delete_view(self):
- if self.is_view_generated(DELETE_VIEW_TYPE):
- return
- class DeleteView(self.get_base_view_mixin(), generic.DeleteView):
- viewset = self
- view_type = DELETE_VIEW_TYPE
- template_name = self.delete_view_template_name
- final_breadcrumb = 'Delete'
- def dispatch(self, request, *args, **kwargs):
- self.populate_object_cache(self.viewset.get_object(self))
- return super(DeleteView, self).dispatch(request, *args, **kwargs)
- def get_view_title(self):
- return self.viewset.get_delete_view_title(self)
- def get_success_url(self):
- return self.viewset.get_delete_view_success_url(self)
- if getattr(self, 'delete_object', None) is not None:
- def delete(self, request, *args, **kwargs):
- return self.viewset.delete_object(self, request, *args, **kwargs)
- DeleteView.delete = delete
- self.views[DELETE_VIEW_TYPE] = self.get_decorated_view(DeleteView)
- def generate_extra_instance_views(self):
- for instance_view in self.extra_instance_views:
- my_view_type = instance_view['view_type']
- if self.is_view_generated(my_view_type):
- continue
- class InstanceView(self.get_base_view_mixin(), instance_view['view_class']):
- viewset = self
- view_type = my_view_type
- def dispatch(self, request, *args, **kwargs):
- self.populate_object_cache(self.viewset.get_object(self))
- return super(InstanceView, self).dispatch(request, *args, **kwargs)
- def get_context_data(self, *args, **kwargs):
- context = super(InstanceView, self).get_context_data(*args, **kwargs)
- context['object'] = self.get_object()
- return context
- self.views[my_view_type] = self.get_decorated_view(InstanceView)
- self.instance_view_types.add(my_view_type)
- self.extra_instance_view_types.add(my_view_type)
- @classmethod
- def urls(cls):
- viewset = cls()
- lookup_regex = r'(?P<' + viewset.lookup_url_kwarg + '>' + viewset.lookup_url_kwarg_pattern + ')/' if viewset.lookup_regex is None else viewset.lookup_regex
- patterns = []
- def append_pattern(pattern, view_type):
- view = viewset.views[view_type]
- if view is not None:
- patterns.append(url(pattern, view, name=view_type))
- append_pattern(r'^$', LIST_VIEW_TYPE)
- append_pattern(r'^create/$', CREATE_VIEW_TYPE)
- append_pattern(lookup_regex + '$', DETAIL_VIEW_TYPE)
- append_pattern(lookup_regex + 'update/$', UPDATE_VIEW_TYPE)
- append_pattern(lookup_regex + 'delete/$', DELETE_VIEW_TYPE)
- for instance_view in viewset.extra_instance_views:
- view_type = instance_view['view_type']
- patterns.append(url(lookup_regex + instance_view['regex'], viewset.views[view_type], name=view_type))
- if not patterns:
- # If a viewset functions only as a nested list view then it will
- # not define any views of its own. We need at least one view URL
- # to be defined in order to discover the viewset via link_viewsets,
- # however, so we simply define a dummy one.
- def not_found(request, *args, **kwargs):
- raise Http404('Not found')
- not_found.viewset = viewset
- patterns.append(url(r'^$', not_found, name=NOT_FOUND_VIEW_TYPE))
- return patterns
- @staticmethod
- def link_viewsets(views):
- viewsets = {}
- for view_function, url_pattern, view_name in views:
- viewset = getattr(view_function, 'viewset', None)
- if viewset is not None:
- viewset_class = viewset.__class__
- if viewset_class in viewsets:
- # TODO: we can remove this restriction by passing a name/ID
- # to the CrudViewset.urls() function to use in the viewset
- # constructor, and then specifying that name/ID instead of
- # the class for master or nested viewsets.
- # OR we could just create derived viewset classes for each
- # case.
- if viewset != viewsets[viewset_class]:
- raise ImproperlyConfigured("%s.urls() is called multiple times (see TODO in code)" % viewset_class.__name__)
- else:
- viewset.url_name_prefix = CrudViewset.get_parent_url_name(view_name) + ':'
- viewsets[viewset_class] = viewset
- for viewset_class, viewset in viewsets.items():
- if viewset.master_viewset_class is not None:
- # Sanity check.
- if viewset.master_viewset is not None:
- raise RuntimeError("%s already has a master viewset" % viewset_class.__name__)
- viewset.master_viewset = viewsets.get(viewset.master_viewset_class, None)
- if viewset.master_viewset is None:
- raise ImproperlyConfigured("%s does not have an associated URL" % viewset.master_viewset_class.__name__)
- if viewset.nested_list_view_index is not None:
- for i, nested_viewset in enumerate(viewset.master_viewset.nested_list_views):
- if viewset.nested_list_view_index < nested_viewset.nested_list_view_index:
- viewset.master_viewset.nested_list_views.insert(i, viewset)
- break
- elif viewset.nested_list_view_index == nested_viewset.nested_list_view_index:
- raise ImproperlyConfigured("%s and %s have the same value for nested_list_view_index", viewset.__class__.__name__, nested_viewset.__class__.__name__)
- else:
- viewset.master_viewset.nested_list_views.append(viewset)
- for viewset in viewsets.values():
- # Prevents unnecessary processing in
- # remove_nested_list_view_query_params() if there are no nested list
- # views.
- if not viewset.nested_list_views:
- viewset.nested_list_view_query_param_prefix = ''
- class Tables2ListViewMixin(object):
- list_view_base_class = generic.TemplateView
- list_view_template_name = 'crud/tables2_list_view.html'
- detail_view_template_name = 'crud/tables2_detail_view.html'
- tables2_class = None
- tables2_context_variable_name = 'table'
- tables2_paginator_query_string_variable_name = 'paginator_query_string'
- tables2_template = 'django_tables2/bootstrap.html'
- tables2_attributes = {}
- tables2_meta_attributes = {}
- tables2_page_field = 'p'
- tables2_per_page_field = 'c'
- tables2_order_by_field = 's'
- tables2_clickable_rows = False
- tables2_clickable_row_class = 'clickable-row'
- tables2_clickable_row_view_type = DETAIL_VIEW_TYPE
- def __init__(self, *args, **kwargs):
- super(Tables2ListViewMixin, self).__init__(*args, **kwargs)
- # For convenience.
- if not self.is_view_enabled[DETAIL_VIEW_TYPE] and (self.tables2_clickable_row_view_type == DETAIL_VIEW_TYPE):
- self.tables2_clickable_row_view_type = UPDATE_VIEW_TYPE
- if self.tables2_class is None:
- meta = type('Meta', (object,), self.tables2_meta_attributes)
- attrs = copy.copy(self.tables2_attributes)
- attrs['Meta'] = meta
- self.tables2_class = type('Table', (django_tables2.Table,), attrs)
- try:
- my_attrs = copy.copy(self.tables2_class.Meta.attrs)
- except AttributeError:
- my_attrs = {}
- my_attrs['class'] = my_attrs.get('class', 'table table-bordered table-striped table-hover')
- class DerivedTable(self.tables2_class):
- class Meta(self.tables2_class.Meta):
- model = self.model
- template = self.tables2_template
- page_field = self.list_view_query_param_prefix + self.tables2_page_field
- per_page_field = self.list_view_query_param_prefix + self.tables2_per_page_field
- order_by_field = self.list_view_query_param_prefix + self.tables2_order_by_field
- attrs = my_attrs
- # This will create an instance variable containing the class and
- # leave the original class variable unchanged.
- self.tables2_class = DerivedTable
- def add_list_view_context_data(self, view, GET, context):
- super(Tables2ListViewMixin, self).add_list_view_context_data(view, GET, context)
- # Sanity check: get_queryset() will not have been called yet as we
- # are using a TemplateView instead of a ListView.
- if 'object_list' in context:
- raise RuntimeError("Context contains an 'object_list' key")
- if self.filter_class is None:
- queryset = self.get_queryset(view)
- else:
- context['filter'] = self.filter_class(GET, self.get_queryset(view), prefix=self.filter_query_param_prefix)
- queryset = context['filter'].qs
- table_kwargs = {}
- if self.tables2_clickable_rows:
- table_kwargs['row_attrs'] = {
- 'class': self.tables2_clickable_row_class,
- 'data-href': lambda record: self.get_view_url(self.tables2_clickable_row_view_type, view, GET, record)
- }
- table = self.tables2_class(queryset, **table_kwargs)
- django_tables2.RequestConfig(view.request).configure(table)
- table.viewset = self
- table.view = view
- table.GET = GET
- paginator_query_params = GET.copy()
- paginator_query_params.pop(table.page_field, None)
- context[self.tables2_context_variable_name] = table
- context[self.tables2_paginator_query_string_variable_name] = paginator_query_params.urlencode()
- if self.is_view_enabled[CREATE_VIEW_TYPE]:
- create_button_text = self.get_create_button_text()
- if create_button_text is not None:
- context['actions'].append(
- [
- {
- 'type': 'button',
- 'id': self.list_view_query_param_prefix + 'create_button',
- 'url': self.get_view_url(CREATE_VIEW_TYPE, view, GET),
- 'text': create_button_text,
- 'button_class': self.get_create_button_class()
- }
- ]
- )
|