Skip to content

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.DjAdminFormsetCreateMixin djadmin_formset.djadmin_hooks
  • djadmin.plugins.permissions.mixins.PermissionMixin djadmin.plugins.permissions.djadmin_hooks
  • djadmin.plugins.core.mixins.DjAdminViewMixin djadmin.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

  1. djadmin.factories.base.GenericAddView
  2. djadmin_formset.mixins.DjAdminFormsetCreateMixin djadmin_formset.djadmin_hooks
  3. djadmin_formset.mixins.DjAdminFormsetBaseMixin
  4. djadmin.plugins.permissions.mixins.PermissionMixin djadmin.plugins.permissions.djadmin_hooks
  5. django.contrib.auth.mixins.UserPassesTestMixin
  6. django.contrib.auth.mixins.AccessMixin
  7. djadmin.plugins.core.mixins.DjAdminViewMixin djadmin.plugins.core.djadmin_hooks
  8. formset.views.EditCollectionView djadmin_formset.djadmin_hooks
  9. formset.views.IncompleteSelectResponseMixin
  10. formset.upload.FileUploadMixin
  11. formset.views.FormCollectionViewMixin
  12. formset.views.FormsetResponseMixin
  13. django.views.generic.detail.SingleObjectMixin
  14. django.views.generic.base.ContextMixin
  15. django.views.generic.base.TemplateResponseMixin
  16. 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 -