-
-
Save codeinthehole/6178034 to your computer and use it in GitHub Desktop.
# Question - how best to handle model fields that are excluded from a form's | |
# fields. | |
# ------------------------------------------------------------ | |
# Option 1: assign fields to instance before passing into form | |
# ------------------------------------------------------------ | |
# views.py | |
instance = Vote(review=review, user=request.user) | |
form = VoteForm(data=request.POST, instance=instance) | |
# -------------------------------------------------------- | |
# Option 2: pass excluded field values to form constructor | |
# -------------------------------------------------------- | |
# views.py | |
form = VoteForm(data=request.POST, review=review, user=request.user, | |
instance=instance) | |
# forms.py | |
class VoteForm(forms.ModelForm): | |
def __init__(self, review, user, *args, **kwargs): | |
super(VoteForm, self).__init__(*args, **kwargs) | |
self.instance.review = review | |
self.instance.user = user | |
# ----------------------------------------------------------------------------- | |
# Option 3: pass excluded field values to form constructor but defer assignment | |
# ----------------------------------------------------------------------------- | |
# views.py | |
form = VoteForm(data=request.POST, review=review, user=request.user, | |
instance=instance) | |
# forms.py | |
class VoteForm(forms.ModelForm): | |
def __init__(self, review, user, *args, **kwargs): | |
self.review = review | |
self.user = user | |
super(VoteForm, self).__init__(*args, **kwargs) | |
def save(self, commit=True): | |
vote = super(VoteForm, self).save(commit=False) | |
vote.review = self.review | |
vote.user = self.user | |
if commit: | |
vote.save() | |
return vote | |
# (This method is bad as the model's clean method doesn't have access to the | |
# review and user instances). | |
# Option 1 is best right? (Confession - I've been using option 2 for ages) |
I like #1.
Also on the same topic, in a response to @howieweiner post I don't like things like this:
def __init__(self, *args, **kwargs):
kwargs.pop('user')
Makes it really hard to understand how I need to use this class. I want to see more explicit definition, at least like this:
def __init__(self, user, *args, **kwargs):
Yes django forms has many kwargs but all are well documented and django form is generic. Form that you write probably won't be well documented and is for a very specific use case.
For example in most forms you won't need files
kwarg, and the same thing applies for most of other.
I think that in some cases it is a good idea to redefine __init__
with only the attributes that you are guaranteed that your form will support:
def __init__(self, user, data=None, initial=None, instance=None):
Also I vote for comment on #1 by @AndrewIngram:
Anyway, if you have to choose between Option 1 and 2, I'd go with 2. With option 1 you can't just look at the form's init method and see that we require a review and a user just to make the form work.
Very well noted. But there are also many cases where form actually doesn't need those excluded fields to work in this case #1 should be fine.
For the record, I think option 2 is better for Oscar as the API is simpler (as @AndrewIngram pointed out). It's more cryptic when you have to assign things to the instance
argument.
However, option 1 is slightly easier to unit test as you don't have to create instances of a review
and user
to pass to the constructor.
Assuming you're using factories, I'm not sure it's much of a problem to generate a review and a user.
Alternatively, just pass in None if your test doesn't hit the part of the code where review and user are actually required to be anything in particular. And if it does hit those parts, you're gonna need to construct proper instances anyway.
I vote for #1. It keeps the form limited to what it really does. And unlike
.save(commit=False)
it allows you to validate the values in their destination context (e.g. limit some choices based on values of uneditable fields).