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:
Step 1: Let the user choose the desired type.
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.