Djelm widgets are opinionated but flexible Elm programs, designed in house, that extend the style and functionality of the native html elements you would usually get out of the box from Django primitives such as ModelChoiceField.
Note
For demonstration purposes, I will be referring to a hypothetical djelm app called elm_programs
django primitive: ModelChoiceField
Elm dependencies:
Note
All Elm dependecies are automatically installed for you
Add the ModelChoiceField widget to your djelm app
python manage.py djelm addwidget elm_programs ModelChoiceField
Compile the ModelChoiceField widget program
python manage.py djelm compile elm_programs
In your form, declare a custom template for the forms.ModelChoiceField instance
# forms.py
from django import forms
from models import Promotion
class PromotionForm(forms.ModelForm):
courses = forms.ModelChoiceField(
queryset=Course.objects.all(),
template_name="modelChoiceFieldWidget.html", # <--- custom template
)
class Meta:
model = Promotion
fields = ["courses"]
Load and add the ModelChoiceField tags to your custom template.
Note
Modify the following to suit your template layout
<!-- modelChoiceFieldWidget.html -->
{% extends "base.html" %}
{% load modelChoiceField_widget_tags %} <--- load
{% block head %}
{% include_ModelChoiceFieldWidget %} <--- include
{% endblock %}
{% block content %}
{{ field.label_tag }}
{% render_ModelChoiceFieldWidget %} <--- render
{% endblock %}
The ModelChoiceField widget is generic and can work with all of your django forms that implement a forms.ModelChoiceField instance.
However, there may be times where you want the widget to look or perform differently based on the model that was used in the queryset, perhaps something like the following.
Variants leverage Elm Custom Types so that you can, at a type level, know which model generated the values and also, at a data level, have access to the fields and values of that model to enhance the widget as required.
Let's consider the following models and forms
# models.py
from django.db import models
class Course(models.Model):
name = models.CharField(max_length=100)
instructor = models.CharField(max_length=100)
def __str__(self) -> str:
return self.name
class Student(models.Model):
first_name = models.CharField(max_length=100)
country = models.CharField(max_length=100)
def __str__(self) -> str:
return self.first_name
# forms.py
from django import forms
from models import Promotion, School, Course, Student
class PromotionForm(forms.ModelForm):
courses = forms.ModelChoiceField(
queryset=Course.objects.all(),
template_name="modelChoiceFieldWidget.html",
empty_label=None
)
class Meta:
model = Promotion
fields = ["courses"]
class SchoolForm(forms.ModelForm):
students = forms.ModelChoiceField(
queryset=Student.objects.all(),
template_name="modelChoiceFieldWidget.html",
)
class Meta:
model = School
fields = ["students"]
The widget program represents options with a generic type alias, with just enough information to faithfully render the widget for any model queryset:
type alias Options_ =
{ choice_label : String
, value : String
, selected : Bool
}
This type doesn't give us enough information to differentiate the model however i.e. are they Course
or Student
options?
Let's add the models as variants to the ModelChoiceField widget flag in elm_programs/flags/widgets/modelChoiceField.py
file:
from models import Course, Student
ModelChoiceFieldFlags = Flags(ModelChoiceFieldFlag(variants=[Course, Student]))
We can now generate the model for the widget:
python manage.py djelm generatemodel elm_programs Widgets.ModelChoiceField
Let's see what our types now look like
type Options_
= Course Options_Course__
| Student Options_Student__
type alias Options_Course__ =
{ choice_label : String
, value : String
, selected : Bool
, instance : { name : String, instructor : String }
}
type alias Options_Student__ =
{ choice_label : String
, value : String
, selected : Bool
, instance : { first_name : String, country : String }
}
This gives us pattern matching super powers that we can leverage to customise the widget program.
doSomethingWithOption : Options_ -> somethingAwesome
doSomethingWithOption option =
case option of
Course data ->
-- do something awesome with course option
Student data ->
-- do something awesome with student option
Important
Django by default includes an empty label option that is not part of a model instance. Using variants will cause djelm to trigger a pydantic error due to this.
Make sure you set empty_label
to None
on the ModelChoiceField form field to avoid this error.