'Allow empty foreign key selection in admin forms

I created a model(AnalysisFieldTemplate) with a foreign key to AnalysisFieldRule. What i want is to have the possibility to leave the field display_analysis_field_rule blank and save my admin form.

class AnalysisFieldRule(models.Model):
    action_kind: str = models.CharField(
        choices=ActionKind.choices,
        default=ActionKind.DISPLAY,
        max_length=text_choices_max_length(ActionKind),
        verbose_name=_("action kind"),
    )
    formula: str = formula_field()
    name: str = models.CharField(max_length=MAX_NAME_LENGTH, verbose_name=_("name"))

    def __str__(self):
        return f"{self.name}: {ActionKind(self.action_kind).label}"

    class Meta:
        constraints = [
            UniqueConstraint(fields=("action_kind", "name"), name="%(app_label)s_%(class)s_is_unique"),
        ]
        ordering = ["name", "action_kind"]
        verbose_name = _("analysis field rule")
        verbose_name_plural = _("analysis field rules")

class AnalysisFieldTemplate(models.Model):
    display_analysis_field_rule: AnalysisFieldRule = models.ForeignKey(
        AnalysisFieldRule,
        blank=True,
        limit_choices_to={"action_kind": ActionKind.DISPLAY},
        null=True,
        on_delete=models.PROTECT,
        related_name="display_analysis_field_templates",
        verbose_name=_("display rule"),
    ) 

Now here is the problem. If i try to save my admin form without choosing one a value for display_analysis_field_rule it will result in an Validationerror. It seems that the standard empty value for a foreign key "------" is not a valid choice.

enter image description here

@admin.register(AnalysisFormTemplate)
class AnalysisFieldTemplateAdmin(admin.ModelAdmin):
    fieldsets = (
        (
            None,
            {
                "fields": (
                    "name",
                    "name_for_formula",
                    "ordering",
                    "required",
                    "kind",
                    "display_analysis_field_rule",
                    "highlight_analysis_field_rule",
                )
            },
        ),
        (
            _("Text options"),
            {"fields": ("max_length",)},
        ),
        (
            _("Integer options"),
            {"fields": ("min_integer_value", "max_integer_value")},
        ),
        (_("Amount of money options"), {"fields": ("min_amount_of_money", "max_amount_of_money")}),
    )

I debugged a little deeper and found that the "to_python" compares the choosen value with pythons standard "empty_values" but of course it contains not the "------" and it will handle it as a normal id which results in an Validation error.

My question is how can i make it possible to save my form without choosing a value for my foreign key? Do i have to override the "to_python" function? What would be a best practice here?

I appreciate all the help :)



Solution 1:[1]

The solution which worked for me was to create a custom form and override the empy_values for each of my foreing keys with its empty_label.

from django.core.validators import EMPTY_VALUES  # NOQA

def empty_values_list_for_foreign_key(empty_label:str):
    empty_values_list = list(EMPTY_VALUES)
    empty_values_list.append(empty_label)
    return empty_values_list


class AnalysisFieldTemplateAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["display_analysis_field_rule"].empty_values = empty_values_list_for_foreign_key(self.fields["display_analysis_field_rule"].empty_label)
        self.fields["highlight_analysis_field_rule"].empty_values = empty_values_list_for_foreign_key(self.fields["highlight_analysis_field_rule"].empty_label)


@admin.register(AnalysisFieldTemplate)
class AnalysisFieldTemplateAdmin(admin.ModelAdmin):
    form = AnalysisFieldTemplateAdminForm

Solution 2:[2]

You need to create custom modelform for your use case and make the particular field with required=False

class MyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['display_analysis_field_rule'].required = False
        self.fields['display_analysis_field_rule'].empty_label = None

    class Meta:
         model = MyModel  # Put your model name here
         

@admin.register(AnalysisFormTemplate)
class AnalysisFieldTemplateAdmin(admin.ModelAdmin):
    form = MyForm
# .... your stuff

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David Marogy
Solution 2