Skip to content
Back to top
View this page
Edit this page

# Class Based Views

While django-polymorphic provides full admin integration, you might want to build front-end views that allow users to create polymorphic objects. Since a single URL cannot easily handle different form fields for different models, the best approach is a two-step process:

1. Step 1: Let the user choose the desired type.

2. Step 2: Display the form for that specific type.

Tip

The code for this example can be found here.

This example uses model labels (e.g., `app.ModelName`) to identify the selected type. Assume we have the following models:

     1from django.db import models
     2from polymorphic.models import PolymorphicModel
     3
     4
     5class Project(PolymorphicModel):
     6    topic = models.CharField(max_length=30)
     7
     8
     9class ArtProject(Project):
    10    artist = models.CharField(max_length=30)
    11
    12
    13class ResearchProject(Project):
    14    supervisor = models.CharField(max_length=30)

## Step 1: Selecting the Type

Create a form that allows users select the desired model type. You can use a simple choice field for this.

     1from django.apps import apps
     2from django.shortcuts import redirect
     3from django.urls import reverse
     4from django.views.generic import FormView, CreateView
     5from .models import Project, ArtProject, ResearchProject
     6
     7from django import forms
     8from django.utils.translation import gettext_lazy as _
     9
    10
    11class ProjectTypeChoiceForm(forms.Form):
    12    model_type = forms.ChoiceField(
    13        label=_("Project Type"),
    14        widget=forms.RadioSelect(attrs={"class": "radiolist"}),
    15    )
    16
    17
    18class ProjectTypeSelectView(FormView):
    19    form_class = ProjectTypeChoiceForm
    20    template_name = "project_type_select.html"
    21
    22    def get_form_kwargs(self):
    23        kwargs = super().get_form_kwargs()
    24        # Build choices using model labels: [(model_label, verbose_name), ...]
    25        choices = [
    26            (model._meta.label, model._meta.verbose_name)
    27            for model in [ArtProject, ResearchProject]
    28        ]
    29        kwargs["initial"] = {"model_type": choices[0][0] if choices else None}
    30        return kwargs
    31
    32    def get_form(self, form_class=None):
    33        form = super().get_form(form_class)
    34        # Populate the choices for the form using model labels
    35        choices = [
    36            (model._meta.label, model._meta.verbose_name)
    37            for model in [ArtProject, ResearchProject]
    38        ]
    39        form.fields["model_type"].choices = choices
    40        return form
    41
    42    def form_valid(self, form):
    43        model_label = form.cleaned_data["model_type"]
    44        return redirect(f"{reverse('project-create')}?model={model_label}")
    45

Your template `project_type_select.html`, might look like this:

    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Next</button>
    </form>

## Step 2: Displaying the Form

The creation view needs to dynamically select the correct form class based on the chosen model label.

     1class ProjectCreateView(CreateView):
     2    model = Project
     3    template_name = "project_form.html"
     4
     5    def get_success_url(self):
     6        return reverse("project-select")
     7
     8    def get_form_class(self):
     9        # Get the requested model label from query parameter
    10        model_label = self.request.GET.get("model")
    11        if not model_label:
    12            # Fallback or redirect to selection view
    13            return super().get_form_class()
    14
    15        # Get the model class using the app registry
    16        model_class = apps.get_model(model_label)
    17
    18        # Create a form for this model
    19        # You can also use a factory or a dict mapping if you have custom forms
    20        class SpecificForm(forms.ModelForm):
    21            class Meta:
    22                model = model_class
    23                fields = "__all__"  # Or specify fields
    24
    25        return SpecificForm
    26
    27    def get_context_data(self, **kwargs):
    28        context = super().get_context_data(**kwargs)
    29        # Pass the model label to the template so it can be preserved
    30        # in the form action
    31        context["model_label"] = self.request.GET.get("model")
    32        return context

In your template `project_form.html`, make sure to preserve the `model` parameter:

    <form method="post" action=".?model={{ model_label }}">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Save</button>
    </form>

And our urls might look like this:

    1from django.urls import path
    2from .views import ProjectTypeSelectView, ProjectCreateView
    3
    4urlpatterns = [
    5    path("select/", ProjectTypeSelectView.as_view(), name="project-select"),
    6    path("create/", ProjectCreateView.as_view(), name="project-create"),
    7]

## Using `extra_views`

If you are using django-extra-views, django-polymorphic provides mixins to help with formsets. See `polymorphic.contrib.extra_views` for more details.