0

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.)

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511

2 Answers2

1

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.

Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
1

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.

Dominic Santos
  • 1,900
  • 12
  • 14
  • That's a valuable addition, and I appreciate the samples. Thanks. – Dan Abramov Oct 10 '11 at 16:20
  • Can you please explain what's the point of `commit=False` in this case? – Dan Abramov Oct 10 '11 at 16:21
  • 1
    @Dan Abramov: The `commit=False` is an argument you can pass to the `ModelForm.save()` method which will prevent the underlying model object from being saved. Instead the model is created (with the appropriate fields populated) and returned. Then you can set the other model fields and save the model. – Platinum Azure Oct 10 '11 at 16:29
  • There's a problem with this approach: calling `obj.save` does not persist m2m relationships. I think calling `form.save` instead works. – Dan Abramov Oct 10 '11 at 17:52
  • If you want to persist the m2m relations then you would have to do that separately yourself; you might want to take a look at the following: http://stackoverflow.com/questions/2216974/django-modelform-for-many-to-many-fields – Dominic Santos Oct 11 '11 at 10:37