GenericAddView¶
Generated view class for AddAction.
GenericAddView
¶
djadmin.factories.base.GenericAddView
AddAction View class for site.None generated by ViewFactory.
Base class: formset.views.EditCollectionView Mixins: DjAdminFormsetCreateMixin, PermissionMixin, DjAdminViewMixin Active plugins: djadmin.plugins.contrib_auth, examples.webshop, djadmin_formset, djadmin_classy_doc, djadmin_filters, djadmin.plugins.permissions, djadmin.plugins.core, djadmin.plugins.theme
View Information ¶
- Action
AddAction- ModelAdmin
djadmin.options.ModelAdmin
View Construction ¶
Base Class
formset.views.EditCollectionView
djadmin_formset.djadmin_hooks
Mixins
-
djadmin_formset.mixins.DjAdminFormsetCreateMixindjadmin_formset.djadmin_hooks -
djadmin.plugins.permissions.mixins.PermissionMixindjadmin.plugins.permissions.djadmin_hooks -
djadmin.plugins.core.mixins.DjAdminViewMixindjadmin.plugins.core.djadmin_hooks
Bound Methods from Action
-
get_template_names()from AddAction -
get_form_features()from AddAction -
validate_features()from AddAction -
get_success_url()from AddAction -
get_layout()from AddAction -
get_form_class()from AddAction -
get_fields()from AddAction
Method Resolution Order ¶
-
djadmin.factories.base.GenericAddView -
djadmin_formset.mixins.DjAdminFormsetCreateMixindjadmin_formset.djadmin_hooks -
djadmin_formset.mixins.DjAdminFormsetBaseMixin -
djadmin.plugins.permissions.mixins.PermissionMixindjadmin.plugins.permissions.djadmin_hooks -
django.contrib.auth.mixins.UserPassesTestMixin -
django.contrib.auth.mixins.AccessMixin -
djadmin.plugins.core.mixins.DjAdminViewMixindjadmin.plugins.core.djadmin_hooks -
formset.views.EditCollectionViewdjadmin_formset.djadmin_hooks -
formset.views.IncompleteSelectResponseMixin -
formset.upload.FileUploadMixin -
formset.views.FormCollectionViewMixin -
formset.views.FormsetResponseMixin -
django.views.generic.detail.SingleObjectMixin -
django.views.generic.base.ContextMixin -
django.views.generic.base.TemplateResponseMixin -
django.views.generic.base.View
Attributes ¶
| Attribute | Value | Defined in |
|---|---|---|
action |
AddAction() |
djadmin.factories.base.GenericAddView
|
admin_site |
AdminSite(name='docs_temp') |
djadmin.factories.base.GenericAddView
|
collection_class |
None |
formset.views.FormCollectionViewMixin
|
collection_kwargs |
None |
formset.views.FormCollectionViewMixin
|
content_type |
None |
django.views.generic.base.TemplateResponseMixin
|
context_object_name |
None |
django.views.generic.detail.SingleObjectMixin
|
create_form_class |
None |
djadmin.factories.base.GenericAddView
|
create_layout |
None |
djadmin.factories.base.GenericAddView
|
extra_context |
None |
django.views.generic.base.ContextMixin
|
form_class |
None |
djadmin.factories.base.GenericAddView
|
http_method_names |
['get', 'post', 'put', 'patch', 'delete', 'head', 'option... |
django.views.generic.base.View
|
initial |
{} |
formset.views.FormCollectionViewMixin
|
layout |
None |
djadmin.factories.base.GenericAddView
|
login_url |
None |
django.contrib.auth.mixins.AccessMixin
|
model |
None |
django.views.generic.detail.SingleObjectMixin
|
model_admin |
ModelAdmin() |
djadmin.factories.base.GenericAddView
|
permission_denied_message |
|
django.contrib.auth.mixins.AccessMixin
|
pk_url_kwarg |
pk |
django.views.generic.detail.SingleObjectMixin
|
query_pk_and_slug |
False |
django.views.generic.detail.SingleObjectMixin
|
queryset |
None |
django.views.generic.detail.SingleObjectMixin
|
raise_exception |
False |
django.contrib.auth.mixins.AccessMixin
|
redirect_field_name |
next |
django.contrib.auth.mixins.AccessMixin
|
response_class |
django.template.response.TemplateResponse |
django.views.generic.base.TemplateResponseMixin
|
slug_field |
slug |
django.views.generic.detail.SingleObjectMixin
|
slug_url_kwarg |
slug |
django.views.generic.detail.SingleObjectMixin
|
success_url |
None |
formset.views.FormCollectionViewMixin
|
template_engine |
None |
django.views.generic.base.TemplateResponseMixin
|
template_name |
None |
django.views.generic.base.TemplateResponseMixin
|
view_is_async |
False |
django.views.generic.base.View
|
Methods ¶
__init__(self, **kwargs)
Defined in:
<class 'django.views.generic.base.View'>
Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.
Source code
in base.py
line 54
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
as_view(**initkwargs)
@classmethod
Defined in:
<class 'django.views.generic.base.View'>
Main entry point for a request-response process.
Source code
in base.py
line 81
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
"The method name %s is not accepted as a keyword argument "
"to %s()." % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError(
"%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key)
)
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, "request"):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# __name__ and __qualname__ are intentionally left unchanged as
# view_class should be used to robustly determine the name of the view
# instead.
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__annotations__ = cls.dispatch.__annotations__
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
# the dispatch method.
view.__dict__.update(cls.dispatch.__dict__)
# Mark the callback if the view class is async.
if cls.view_is_async:
markcoroutinefunction(view)
return view
build_form(self, form=None)
Defined in:
<class 'djadmin.plugins.core.mixins.DjAdminViewMixin'>
Build form class from layout or fields. Override in plugins to customize.
This method is called by get_form_class() when no explicit form_class is provided. The default implementation uses FormBuilder to create forms from layouts or fields.
Returns: ModelForm class ready for instantiation
Source code
in mixins.py
line 246
def build_form(self, form=None):
"""
Build form class from layout or fields. Override in plugins to customize.
This method is called by get_form_class() when no explicit form_class is provided.
The default implementation uses FormBuilder to create forms from layouts or fields.
Returns:
ModelForm class ready for instantiation
"""
layout = self.get_layout()
if layout:
# Build from layout using FormBuilder
print('using FormBuilder.from_layout')
return FormBuilder.from_layout(layout, self.model, form_class=None)
print('using FormBuilder.create_form')
# No layout - build from fields
fields = getattr(self, 'get_fields', lambda: '__all__')()
return FormBuilder.create_form(self.model, fields=fields)
Source code
in mixins.py
line 45
def build_form(self, form_class=None):
"""
Build FormCollection from layout or fields.
This overrides DjAdminViewMixin.build_form() to create FormCollections
instead of regular ModelForms. This is the key integration point for
djadmin-formset plugin.
Returns:
FormCollection class (not ModelForm)
"""
print('build_form called')
layout = self.get_layout()
if layout:
# Build FormCollection from layout
# Note: We know form_class is None since build_form() only called when no explicit form
return FormFactory.from_layout(
layout=layout,
model=self.model,
base_form=form_class,
renderer=getattr(layout, 'renderer', None) or DjAdminFormRenderer,
)
# No layout - build regular form and wrap in collection
if form_class is None:
fields = getattr(self, 'get_fields', lambda: '__all__')()
form_class = FormBuilder.create_form(self.model, fields=fields)
if not issubclass(form_class, FormCollection):
form_class = FormFactory.wrap_form_in_collection(
form_class=form_class,
model=self.model,
renderer=DjAdminFormRenderer,
)
return form_class
delete(self, request, **kwargs)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Method DELETE is used for partial deletion.
Source code
in views.py
line 219
def delete(self, request, **kwargs):
"""
Method `DELETE` is used for partial deletion.
"""
if set(['path', 'pk']).issubset(request.GET):
return self._delete_partial()
return HttpResponseForbidden("Method DELETE not supported in this context")
dispatch(self, request, *args, **kwargs)
Defined in:
<class 'django.views.generic.base.View'>
Source code
in base.py
line 134
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
Source code
in mixins.py
line 131
def dispatch(self, request, *args, **kwargs):
user_test_result = self.get_test_func()()
if not user_test_result:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
filtered_actions
Defined in:
<class 'djadmin.plugins.core.mixins.DjAdminViewMixin'>
Filtered action lists by user permissions, cached on view instance.
For object-based views (UpdateView, DetailView, DeleteView), filters actions based on the specific object. For list views, filters at model level.
Results are cached on the view instance, which is per-request since Django creates a new view instance for each request. Combined with request-level caching in ModelAdmin.filter_actions(), this ensures actions are filtered only once per request.
Performance Impact: Without caching: Actions filtered multiple times per request With caching: Actions filtered once per request (view instance)
Returns: dict: Dictionary with filtered action lists: - filtered_general_actions - filtered_bulk_actions - filtered_record_actions
form_collection_invalid(self, form_collection)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 303
def form_collection_invalid(self, form_collection):
return JsonResponse(form_collection._errors, status=422, safe=False)
form_collection_valid(self, form_collection)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 300
def form_collection_valid(self, form_collection):
return JsonResponse({'success_url': self.get_success_url()})
Source code
in views.py
line 333
def form_collection_valid(self, form_collection):
with transaction.atomic():
form_collection.construct_instance(self.object)
# integrity errors may occur during construction, hence revalidate collection
if form_collection.is_valid():
return super().form_collection_valid(form_collection)
else:
return self.form_collection_invalid(form_collection)
Source code
in mixins.py
line 216
def form_collection_valid(self, form_collection):
"""
Handle successful validation by flattening and delegating to django-formset.
Steps:
1. Flatten Fieldsets/Rows into single forms per instance
2. Manually construct instance from flattened forms
3. Let django-formset handle Collections (has_many=True)
4. Save and return success response
Works for both CREATE and UPDATE by preprocessing the FormCollection structure
before construction. Flattening eliminates the IntegrityError (CREATE) and field
name conflict (UPDATE) bugs.
Args:
form_collection: The validated FormCollection
Returns:
JsonResponse with success_url or revalidation response
"""
from django.db import transaction
from django.http import JsonResponse
from formset.collection import FormCollection
with transaction.atomic():
# Flatten Fieldsets/Rows in-place
self._flatten_fieldsets_in_place(form_collection, self.object)
# After flattening, all Fieldsets/Rows should have a single 'main' form
# with all fields merged. Manually apply cleaned_data to the instance.
if 'main' in form_collection.valid_holders:
merged_form = form_collection.valid_holders['main']
# Separate M2M fields from regular fields
m2m_data = {}
for field_name, value in merged_form.cleaned_data.items():
try:
field = self.object._meta.get_field(field_name)
if field.many_to_many:
# Save M2M fields for after instance is saved
m2m_data[field_name] = value
else:
# Regular field - set directly
setattr(self.object, field_name, value)
except Exception:
# Field might not exist on model, skip it
pass
# Save instance first (required before setting M2M fields)
self.object.save()
# Now set M2M fields
for field_name, value in m2m_data.items():
getattr(self.object, field_name).set(value)
# Handle Collections separately (they weren't flattened)
for _name, holder in form_collection.valid_holders.items():
if isinstance(holder, FormCollection) and getattr(holder, 'has_many', False):
# CRITICAL: Pass parent instance so django-formset can set the FK!
# Django-formset uses this to do: child_instance.fk_field = parent_instance
# Our custom retrieve_instance() ensures each form.instance is the CHILD,
# so construct_instance() applies data to child, then sets FK.
holder.construct_instance(self.object) # Pass parent for FK linking
# Revalidate in case of integrity errors
if form_collection.is_valid():
return JsonResponse({'success_url': self.get_success_url()})
else:
return self.form_collection_invalid(form_collection)
get(self, request, *args, **kwargs)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 199
def get(self, request, *args, **kwargs):
if request.accepts('application/json') and set(['path', 'pk']).issubset(request.GET):
# invoked by `DjangoFormset.prefillPartial()`
return self._fetch_partial_data()
# instantiate blank versions of the forms in the collection
return self.render_to_response(self.get_context_data())
Source code
in views.py
line 30
def get(self, request, **kwargs):
if request.accepts('application/json') and 'field' in request.GET:
return self._fetch_options(request)
return super().get(request, **kwargs)
Source code
in views.py
line 318
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
get_collection_class(self)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 290
def get_collection_class(self):
return self.collection_class
Source code
in mixins.py
line 114
def get_collection_class(self):
"""
Get the FormCollection class for this view.
Follows priority hierarchy:
1. form_collection_class (if explicitly provided)
2. layout (if provided via get_layout()):
- With form_class: Use form_class as base_form in factory
- Without form_class: Auto-generate from layout
3. form_class (if provided via get_form_class(), no layout): Wrap in FormCollection
4. Return None (fallback to standard Django forms)
Called by FormCollectionViewMixin.get_form_collection().
Returns:
type: A FormCollection subclass, or None
"""
# Priority 1: Check for explicit form_collection_class
form_collection_class = self._get_action_specific_form_collection_class()
if form_collection_class:
return form_collection_class
# Priority 2: we s
form_class = self.get_form_class()
print(form_class.__mro__)
if not issubclass(form_class, FormMixin | FormCollection):
return self.build_form(form_class)
return form_class
get_collection_kwargs(self)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 271
def get_collection_kwargs(self):
kwargs = {
'initial': self.get_initial(),
'partial': self.request.method == 'PATCH',
}
if self.collection_kwargs:
kwargs.update(**self.collection_kwargs)
return kwargs
get_context_data(self, **kwargs)
Defined in:
<class 'django.views.generic.base.ContextMixin'>
Source code
in base.py
line 30
def get_context_data(self, **kwargs):
kwargs.setdefault("view", self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
Source code
in detail.py
line 92
def get_context_data(self, **kwargs):
"""Insert the single object into the context dict."""
context = {}
if self.object:
context["object"] = self.object
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
context.update(kwargs)
return super().get_context_data(**context)
Source code
in views.py
line 262
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_collection'] = self.get_form_collection()
return context
Source code
in mixins.py
line 126
def get_context_data(self, **kwargs):
"""
Add standard admin context to all views.
When bound to the view, self is the view instance which has:
- self.action: Action instance
- self.model: Model class
- self.model_admin: ModelAdmin instance
- self.admin_site: AdminSite instance
- self.request: Current request
"""
from djadmin.plugins import pm
# Call super to get base context from Django CBV
context = super().get_context_data(**kwargs)
# Add request to context (needed for inclusion tags)
context['request'] = self.request
# Add standard admin context
context['model'] = self.model
context['opts'] = self.model._meta if self.model else None
context['action'] = self.action
context['model_admin'] = self.model_admin
context['admin_site'] = self.admin_site
# Add version information
context['djadmin_version'] = djadmin.__version__
context['django_version'] = django.get_version()
# Add site-wide URLs
try:
context['index_url'] = reverse(f'{self.admin_site.name}:index')
except Exception:
context['index_url'] = '/'
# Add site configuration
context['site_header'] = self.admin_site.site_header
context['site_title'] = self.admin_site.site_title
context['index_title'] = self.admin_site.index_title
# Add list URL (using admin_site namespace)
# Skip for site-level actions (model=None)
if self.model is not None:
list_url_name = f'{self.model_admin.list_url_name}'
context['list_url'] = self.admin_site.reverse(list_url_name)
else:
context['list_url'] = context.get('index_url', '/')
# Add namespaced URL names for actions
namespace = self.admin_site.name
context['action_namespace'] = namespace
# Add breadcrumbs
context['breadcrumb_list'] = self._get_breadcrumbs()
# Add assets from plugins
assets = self._get_assets_from_plugins()
context['assets'] = assets
# Add sidebar widgets for template rendering
sidebar_widgets = self._get_sidebar_widgets_for_template()
context['sidebar_widgets'] = sidebar_widgets
# Add column header icons for list views
column_header_icons = self._get_column_header_icons()
context['column_header_icons'] = column_header_icons
# Add filtered actions to context (permission-checked)
# This ensures all views (ListView, UpdateView, CreateView, etc.) get filtered actions
if self.model is not None and self.model_admin is not None:
# Add unfiltered action lists (for template tag use in ListView)
context['general_actions'] = self.model_admin.general_actions
context['bulk_actions'] = self.model_admin.bulk_actions
context['record_actions'] = self.model_admin.record_actions
# Add filtered action lists (for use in templates)
context.update(self.filtered_actions)
# Allow plugins to add more context
plugin_contexts = pm.hook.djadmin_add_context_data(
context=context,
request=self.request,
view=self,
)
for plugin_context in plugin_contexts:
if plugin_context:
context.update(plugin_context)
if settings.DEBUG:
# List loaded plugins for debugging
# djp.PluginManager wraps pluggy, access the underlying manager
try:
# Get plugins from the underlying pluggy manager
if hasattr(pm, '_pm'):
# djp stores the pluggy manager as _pm
plugins = pm._pm.get_plugins()
elif hasattr(pm, 'get_plugins'):
plugins = pm.get_plugins()
else:
# Fallback: just note that we couldn't list plugins
plugins = ['(plugin listing not available)']
context['debug_loaded_plugins'] = [str(plugin) for plugin in plugins]
except Exception as e:
context['debug_loaded_plugins'] = [f'Error listing plugins: {e}']
# Debug: Show view's MRO (Method Resolution Order) to see which mixins are applied
context['debug_view_mro'] = [cls.__name__ for cls in self.__class__.__mro__[:10]]
# Debug: Show form class being used
if hasattr(self, 'form_class'):
context['debug_form_class'] = str(self.form_class)
# Debug: Show action class hierarchy
context['debug_action_mro'] = [cls.__name__ for cls in self.action.__class__.__mro__[:10]]
return context
get_context_object_name(self, obj)
Defined in:
<class 'django.views.generic.detail.SingleObjectMixin'>
Get the name to use for the object.
Source code
in detail.py
line 83
def get_context_object_name(self, obj):
"""Get the name to use for the object."""
if self.context_object_name:
return self.context_object_name
elif isinstance(obj, models.Model):
return obj._meta.model_name
else:
return None
get_extra_data(self)
Defined in:
<class 'formset.views.FormsetResponseMixin'>
When submitting a form, one can additionally add extra parameters via the button's submit() action.
Use this method to access that extra data.
Source code
in views.py
line 105
def get_extra_data(self):
"""
When submitting a form, one can additionally add extra parameters via the button's ``submit()`` action.
Use this method to access that extra data.
"""
if self._request_body:
return self._request_body.get('extra_data', {})
get_field(self, field_path)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 267
def get_field(self, field_path):
collection_class = self.get_collection_class()
return collection_class().get_field(field_path)
get_form_class(self)
bound from AddAction
Defined in:
<class 'djadmin.factories.base.GenericAddView'>
Get form class - returns explicit form_class or delegates to build_form().
Source code
in actions.py
line 25
def get_form_class(self):
"""Get form class - returns explicit form_class or delegates to build_form()."""
# Validate that required features are available
self.validate_features()
print('get_form_class called')
# Check for explicit form_class (action-specific or general)
name = self.action._action_map.get(self.action.action_name, self.action.action_name)
if form_class := getattr(self, f'{name}_form_class', None) or self.form_class:
return form_class
# No explicit form - delegate to build_form() (defined on view)
return self.build_form()
get_form_collection(self)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 280
def get_form_collection(self):
collection_class = self.get_collection_class()
kwargs = self.get_collection_kwargs()
if self.request.method in ('PATCH', 'POST', 'PUT') and self.request.content_type == 'application/json':
body = json.loads(self.request.body)
kwargs.update(data=body.get('formset_data'))
if callable(getattr(self, 'get_object', None)):
kwargs.update(instance=self.get_object())
return collection_class(**kwargs)
get_form_features(self)
bound from AddAction
Defined in:
<class 'djadmin.factories.base.GenericAddView'>
Get features required by this form.
Checks the ModelAdmin's layout for required features and allows plugins to add more via the djadmin_get_form_features hook.
Uses action-specific layout if available: - CreateViewActionMixin → create_layout or layout - UpdateViewActionMixin → update_layout or layout
Returns: Set of feature names (strings) like 'collections', 'conditional_fields', etc.
Source code
in actions.py
line 34
def get_form_features(self):
"""
Get features required by this form.
Checks the ModelAdmin's layout for required features and allows
plugins to add more via the djadmin_get_form_features hook.
Uses action-specific layout if available:
- CreateViewActionMixin → create_layout or layout
- UpdateViewActionMixin → update_layout or layout
Returns:
Set of feature names (strings) like 'collections', 'conditional_fields', etc.
"""
from djadmin.plugins import pm
features = set()
# Get action-specific layout (inline to avoid MRO issues with view instances)
layout = None
if isinstance(self, CreateViewActionMixin):
# Create action: use create_layout or fallback to layout
layout = getattr(self.model_admin, 'create_layout', None) or getattr(self.model_admin, 'layout', None)
elif isinstance(self, UpdateViewActionMixin):
# Update action: use update_layout or fallback to layout
layout = getattr(self.model_admin, 'update_layout', None) or getattr(self.model_admin, 'layout', None)
else:
# Other actions: use generic layout
layout = getattr(self.model_admin, 'layout', None)
if layout:
features.update(layout.get_features())
# Let plugins add more features
for plugin_features in pm.hook.djadmin_get_form_features(
action=self,
model_admin=self.model_admin,
):
if plugin_features:
features.update(plugin_features)
return features
get_initial(self)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Return the initial data to use for collections of forms on this view.
Source code
in views.py
line 293
def get_initial(self):
"""Return the initial data to use for collections of forms on this view."""
return self.initial.copy()
Source code
in views.py
line 326
def get_initial(self):
initial = super().get_initial()
if isinstance(initial, dict) and self.object:
collection_class = self.get_collection_class()
initial.update(collection_class().model_to_dict(self.object))
return initial
get_layout(self)
bound from AddAction
Defined in:
<class 'djadmin.factories.base.GenericAddView'>
Source code
in actions.py
line 21
def get_layout(self):
name = self.action._action_map.get(self.action.action_name, self.action.action_name)
return getattr(self, f'{name}_layout', None) or self.layout
get_login_url(self)
Defined in:
<class 'django.contrib.auth.mixins.AccessMixin'>
Override this method to override the login_url attribute.
Source code
in mixins.py
line 21
def get_login_url(self):
"""
Override this method to override the login_url attribute.
"""
login_url = self.login_url or settings.LOGIN_URL
if not login_url:
raise ImproperlyConfigured(
f"{self.__class__.__name__} is missing the login_url attribute. Define "
f"{self.__class__.__name__}.login_url, settings.LOGIN_URL, or override "
f"{self.__class__.__name__}.get_login_url()."
)
return str(login_url)
get_object(self, queryset=None)
Defined in:
<class 'django.views.generic.detail.SingleObjectMixin'>
Return the object the view is displaying.
Require self.queryset and a pk or slug argument in the URLconf.
Subclasses can override this to return any object.
Source code
in detail.py
line 21
def get_object(self, queryset=None):
"""
Return the object the view is displaying.
Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
Subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError(
"Generic detail view %s must be called with either an object "
"pk or a slug in the URLconf." % self.__class__.__name__
)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(
_("No %(verbose_name)s found matching the query")
% {"verbose_name": queryset.model._meta.verbose_name}
)
return obj
Source code
in mixins.py
line 39
def get_object(self, queryset=None):
"""Override to check object permissions after fetching."""
obj = super().get_object(queryset)
# Get permission class from action or model_admin
permission_class = self._get_permission_class()
if permission_class is not None:
# Check object permission
if not permission_class.has_object_permission(self, obj):
raise PermissionDenied(f'You do not have permission to access this {obj._meta.verbose_name}.')
return obj
Source code
in mixins.py
line 298
def get_object(self, queryset=None):
"""
Return None for CREATE actions.
FormCollectionViewMixin.get_form_collection() checks if get_object() is callable
and calls it if so. For CREATE actions, there is no existing object, so we
return None instead of raising an error.
"""
return None
get_permission_denied_message(self)
Defined in:
<class 'django.contrib.auth.mixins.AccessMixin'>
Override this method to override the permission_denied_message attribute.
Source code
in mixins.py
line 34
def get_permission_denied_message(self):
"""
Override this method to override the permission_denied_message attribute.
"""
return self.permission_denied_message
get_queryset(self)
Defined in:
<class 'django.views.generic.detail.SingleObjectMixin'>
Return the QuerySet that will be used to look up the object.
This method is called by the default implementation of get_object() and may not be called if get_object() is overridden.
Source code
in detail.py
line 61
def get_queryset(self):
"""
Return the `QuerySet` that will be used to look up the object.
This method is called by the default implementation of get_object() and
may not be called if get_object() is overridden.
"""
if self.queryset is None:
if self.model:
return self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
)
return self.queryset.all()
get_redirect_field_name(self)
Defined in:
<class 'django.contrib.auth.mixins.AccessMixin'>
Override this method to override the redirect_field_name attribute.
Source code
in mixins.py
line 40
def get_redirect_field_name(self):
"""
Override this method to override the redirect_field_name attribute.
"""
return self.redirect_field_name
get_slug_field(self)
Defined in:
<class 'django.views.generic.detail.SingleObjectMixin'>
Get the name of a slug field to be used to look up by slug.
Source code
in detail.py
line 79
def get_slug_field(self):
"""Get the name of a slug field to be used to look up by slug."""
return self.slug_field
get_success_url(self)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in actions.py
line 650
def get_success_url(self):
"""
Redirect to list view after creation.
When bound to the view, self is the view instance which has:
- self.object: The created object
- self.request: The current request
"""
opts = self.model._meta
return self.admin_site.reverse(f'{opts.app_label}_{opts.model_name}_list')
Source code
in base.py
line 650
def get_success_url(self):
"""
Redirect to list view after creation.
When bound to the view, self is the view instance which has:
- self.object: The created object
- self.request: The current request
"""
opts = self.model._meta
return self.admin_site.reverse(f'{opts.app_label}_{opts.model_name}_list')
get_template_names(self)
Defined in:
<class 'django.views.generic.base.TemplateResponseMixin'>
Return a list of template names to be used for the request. Must return a list. May not be called if render_to_response() is overridden.
Source code
in actions.py
line 175
def get_template_names(self):
"""
Get template names as a list (Django CBV expects plural method).
This wraps get_template_name() and ensures the result is a list.
Returns:
List of template path strings
"""
template_names = []
if model_name := getattr(getattr(self.model, '_meta', None), 'model_name', None):
template_names.append(f'djadmin/{self.model._meta.app_label}/{model_name}_{self.action.action_name}.html')
template_names.append(f'djadmin/actions/{self.action.action_name}.html')
return template_names
Source code
in base.py
line 175
def get_template_names(self):
"""
Get template names as a list (Django CBV expects plural method).
This wraps get_template_name() and ensures the result is a list.
Returns:
List of template path strings
"""
template_names = []
if model_name := getattr(getattr(self.model, '_meta', None), 'model_name', None):
template_names.append(f'djadmin/{self.model._meta.app_label}/{model_name}_{self.action.action_name}.html')
template_names.append(f'djadmin/actions/{self.action.action_name}.html')
return template_names
get_test_func(self)
Defined in:
<class 'django.contrib.auth.mixins.UserPassesTestMixin'>
Override this method to use a different test_func method.
Source code
in mixins.py
line 125
def get_test_func(self):
"""
Override this method to use a different test_func method.
"""
return self.test_func
handle_no_permission(self)
Defined in:
<class 'django.contrib.auth.mixins.AccessMixin'>
Source code
in mixins.py
line 46
def handle_no_permission(self):
if self.raise_exception or self.request.user.is_authenticated:
raise PermissionDenied(self.get_permission_denied_message())
path = self.request.build_absolute_uri()
resolved_login_url = resolve_url(self.get_login_url())
# If the login url is the same scheme and net location then use the
# path as the "next" url.
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
current_scheme, current_netloc = urlsplit(path)[:2]
if (not login_scheme or login_scheme == current_scheme) and (
not login_netloc or login_netloc == current_netloc
):
path = self.request.get_full_path()
return redirect_to_login(
path,
resolved_login_url,
self.get_redirect_field_name(),
)
http_method_not_allowed(self, request, *args, **kwargs)
Defined in:
<class 'django.views.generic.base.View'>
Source code
in base.py
line 146
def http_method_not_allowed(self, request, *args, **kwargs):
response = HttpResponseNotAllowed(self._allowed_methods())
log_response(
"Method Not Allowed (%s): %s",
request.method,
request.path,
response=response,
request=request,
)
if self.view_is_async:
async def func():
return response
return func()
else:
return response
options(self, request, *args, **kwargs)
Defined in:
<class 'django.views.generic.base.View'>
Handle responding to requests for the OPTIONS HTTP verb.
Source code
in base.py
line 165
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response.headers["Allow"] = ", ".join(self._allowed_methods())
response.headers["Content-Length"] = "0"
if self.view_is_async:
async def func():
return response
return func()
else:
return response
patch(self, request, **kwargs)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Method PATCH is used for partial submits.
Source code
in views.py
line 213
def patch(self, request, **kwargs):
"""
Method `PATCH` is used for partial submits.
"""
return self.post(request, **kwargs)
post(self, request, **kwargs)
Defined in:
<class 'formset.views.FormCollectionViewMixin'>
Source code
in views.py
line 206
def post(self, request, **kwargs):
form_collection = self.get_form_collection()
if form_collection.is_valid():
return self.form_collection_valid(form_collection)
else:
return self.form_collection_invalid(form_collection)
Source code
in upload.py
line 127
def post(self, request, **kwargs):
if request.content_type == 'multipart/form-data' and 'temp_file' in request.FILES and 'image_height' in request.POST:
try:
return JsonResponse(receive_uploaded_file(request.FILES['temp_file'], request.POST['image_height']))
except Exception as e:
return HttpResponseBadRequest(str(e))
return super().post(request, **kwargs)
Source code
in views.py
line 322
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
Source code
in mixins.py
line 308
def post(self, request, *args, **kwargs):
"""
Create a new instance and handle form submission.
We must bypass EditCollectionView.post() because it does:
self.object = self.get_object()
which would overwrite our newly created instance with None.
Instead, we call FormCollectionViewMixin.post() directly.
"""
from formset.views import FormCollectionViewMixin
# Create a new instance of the model
self.object = self.model_admin.model()
# Skip EditCollectionView.post() to avoid self.object being set to None
# Call FormCollectionViewMixin.post() directly
return FormCollectionViewMixin.post(self, request, *args, **kwargs)
render_to_response(self, context, **response_kwargs)
Defined in:
<class 'django.views.generic.base.TemplateResponseMixin'>
Return a response, using the response_class for this view, with a
template rendered with the given context.
Pass response_kwargs to the constructor of the response class.
Source code
in base.py
line 192
def render_to_response(self, context, **response_kwargs):
"""
Return a response, using the `response_class` for this view, with a
template rendered with the given context.
Pass response_kwargs to the constructor of the response class.
"""
response_kwargs.setdefault("content_type", self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs,
)
setup(self, request, *args, **kwargs)
Defined in:
<class 'django.views.generic.base.View'>
Initialize attributes shared by all view methods.
Source code
in base.py
line 126
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, "get") and not hasattr(self, "head"):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
test_func(self)
Defined in:
<class 'django.contrib.auth.mixins.UserPassesTestMixin'>
Source code
in mixins.py
line 118
def test_func(self):
raise NotImplementedError(
"{} is missing the implementation of the test_func() method.".format(
self.__class__.__name__
)
)
Source code
in mixins.py
line 16
def test_func(self):
"""
Permission check method for UserPassesTestMixin.
This method is called by UserPassesTestMixin to check if the user
has permission to access the view.
Returns:
bool: True if user has permission, False otherwise
"""
# Get permission from action or model_admin
perm = self._get_permission_class()
if perm is None:
# None means no permission enforcement (allow all access)
# This happens when:
# 1. Explicitly set to None on ModelAdmin or Action
# 2. No model_admin exists (site-level views like DashboardAction)
return True
# Execute permission with view (self is the view)
return perm(self)
validate_features(self)
bound from AddAction
Defined in:
<class 'djadmin.factories.base.GenericAddView'>
Validate that required features are available.
Called during form class generation. If required features are missing, raises ImproperlyConfigured with helpful error messages.
This uses the plugin feature advertising system to check if required features are provided by registered plugins.
Raises: ImproperlyConfigured: If required features are not available
Source code
in actions.py
line 77
def validate_features(self):
"""
Validate that required features are available.
Called during form class generation. If required features are missing,
raises ImproperlyConfigured with helpful error messages.
This uses the plugin feature advertising system to check if required
features are provided by registered plugins.
Raises:
ImproperlyConfigured: If required features are not available
"""
from djadmin.plugins import pm
features = self.get_form_features()
if not features:
return # No special features needed
# Get all provided features from plugins
all_provided_features = set()
results = pm.hook.djadmin_provides_features()
for feature_list in results:
if feature_list:
all_provided_features.update(feature_list)
# Check if features are satisfied
missing_features = features - all_provided_features
if missing_features:
# Build helpful error message
feature_messages = {
'collections': ('Inline editing (Collection components) requires ' 'the djadmin-formset plugin'),
'inlines': ('Inline editing (Collection components) requires ' 'the djadmin-formset plugin'),
'conditional_fields': ('Conditional fields (show_if/hide_if) require ' 'the djadmin-formset plugin'),
'computed_fields': ('Computed fields (calculate) require ' 'the djadmin-formset plugin'),
}
messages = []
for feature in missing_features:
if feature in feature_messages:
messages.append(feature_messages[feature])
else:
messages.append(f"Feature '{feature}' is not provided by any plugin")
# De-duplicate messages
messages = list(dict.fromkeys(messages))
raise ImproperlyConfigured(
f'Form for {self.model._meta.verbose_name} requires '
f'features that are not available:\n'
+ '\n'.join(f' • {msg}' for msg in messages)
+ '\n\nInstall with: pip install django-admin-deux[djadmin-formset]'
)
Fields ¶
| Field | Type | Related To |
|---|---|---|
__dict__ |
getset_descriptor |
- |
__weakref__ |
getset_descriptor |
- |