What's the cleanest way to add arbitrary data to ModelForm?

231 views Asked by At

Imagine we're developing a message system, and each Message has a foreign key for sender.

We're using ModelForms, and there is a MessageForm that infers its fields from Message.
Of course, we don't want the user to be able to spoof sender by posting a different sender ID.

Therefore, we must exclude sender from ModelForm and fill it from session on post.

Where and how should I assign arbitrary data to ModelForm fields?

In my example, I probably want to access session so we need to access to request as well.
Does this mean the code has to be in the view, right after the form has been created?

How do we assign a form field from code and make sure it overrides POST data?

(Of course, the example is pretty fictional and is here just to illustrate the problem.)

2

There are 2 answers

5
Dominic Santos On BEST ANSWER

You can just exclude it as you have and then when processing the form in the view do:

obj = form.save(commit=False)
obj.sender = request.user
obj.save()

Alternatively, you can do it in the save method of the form; for this you need to save the request object momentarily in the form. Something like the following:

class MyForm(forms.ModelForm):
  def __init__(self, request, *args, **kwargs):
    self._request = request
    super(MyForm, self).__init__(*args, **kwargs)

  def save(self, commit=False):
    obj = super(MyForm, self).save(commit=False)
    obj.sender = self._request.user
    if commit:
      obj.save()

    return obj

I prefer the second way myself as it helps encapsulate the logic regarding that model and it's data in one neat place.

1
Platinum Azure On

Just exclude the sender field from the ModelForm and, when you instantiate the object in the view on the POST endpoint (just before saving), make sure you populate the sender field with the appropriate session or user ID.

When you exclude the field, there is no way to add the field to the post data[1], so a user can't spoof it.

[1] With JavaScript, a &sender=someSenderId could be added in theory, but in your view, you don't need to look for a sender field. It won't be serialized into the ModelForm object.