18

I've got a Form. I want to include a hidden field that returns a model. I'll set it's value in the view; I just need it to be posted along to the next page.

What field am I supposed to use in the form class?

Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
mpen
  • 272,448
  • 266
  • 850
  • 1,236

2 Answers2

29

A hidden field that returns a model? So a model instance ID?

The forms.HiddenInput widget should do the trick, whether on a FK field or CharField you put a model instance ID in.

class MyForm(forms.Form):
    hidden_2 = forms.CharField(widget=forms.HiddenInput())
    hidden_css = forms.CharField(widget=forms.MostWidgets(attrs={'style': 'display:none;'}))

I suppose the fastest way to get this working is

class MyForm(forms.Form):
    model_instance = forms.ModelChoiceField(queryset=MyModel.objects.all(), widget=forms.HiddenInput())

form = MyForm({'model_instance': '1'})
form.cleaned_data['model_instance']

But I don't like the idea of supplying MyModel.objects.all() if you're going to specify one item anyways.

It seems like to avoid that behavior, you'd have to override the form __init__ with a smaller QuerySet.

I think I prefer the old fashioned way:

class MyForm(forms.Form):
    model_instance = forms.CharField(widget=forms.HiddenInput())

    def clean_model_instance(self):
        data = self.cleaned_data['model_instance']
        if not data:
            raise forms.ValidationError()
        try:
            instance = MyModel.objects.get(id=data)
        except MyModel.DoesNotExist:
            raise forms.ValidationError()
        return instance
laffuste
  • 16,287
  • 8
  • 84
  • 91
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • Don't think `ForeignKey` is a valid form field... it's a model field. I'd rather it return a model instance rather than an integer (when I retrieve it later with `form.cleaned_data['field']`) – mpen Jan 14 '11 at 01:28
  • forms.MostWidgets? meaning most of available widgets? – fanny Nov 02 '17 at 13:48
  • @fanny yeah, you should be able to simply hide the widget by setting the style attribute, but `HiddenInput()` makes more sense. – Yuji 'Tomita' Tomita Nov 03 '17 at 20:44
2

The approach in Yuji's answer uses a clean_model_instance method on the form which is fine if you're only ever doing this once in your code base. If you do it more often, then you might benefit from implementing a custom model field.

This is the code I have:

from django import forms

class ModelField(forms.Field):

    Model = None

    def prepare_value(self, value):
        """Inject entities' id value into the form's html data"""
        if isinstance(value, self.Model):
            return value.id
        return value

    def to_python(self, value):
        """More or less stolen from ModelChoiceField.to_python"""

        if value in self.empty_values:
            return None

        try:
            value = self.Model.objects.get(id=value)
        except (ValueError, self.Model.DoesNotExist):
            raise forms.ValidationError('%s does not exist'
                                         % self.Model.__class__.__name__.capitalize())

        return value

If you use that as a base class and then specialise it with your own models then it becomes a useful based. For example:

# In app/fields.py
from .models import CustomModel

class CustomModelField(ModelField):

    Model = CustomModel

Then you can pair that with whatever widget you need at the time:

# in app/forms.py
class MyForm(forms.Form):

    hidden_custom_model_field = CustomModelField(widget=forms.HiddenInput())

    other_widget_custom_model_field = CustomModelField(widget=MyCustomWidget())
MichaelJones
  • 1,336
  • 2
  • 12
  • 22