class Book(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=255)
class Meta:
unique_together = ('user', 'name')
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ('name',)
user = User.objects.get(pk=1)
book1 = Book.objects.create(user=user, name='Harry Potter')
#now lets build our form
book2 = Book(user=user)
form = BookForm(data={'name': 'Harry Potter'}, instance=book2)
if form.is_valid(): #yes the form will be valid
book2 = form.save() #IntegrityError will be raised with UNIQUE constraint failed
I believe this could save a lot of headache to people. If there is no guarantee that an instance cannot be saved after the form has been validated, then do not give me an option to shoot myself into the foot.
My only concern here actually is not that we didn't know what will happen (I think we know that), but how to show the user that the developer got messed up, i.e. how to render an error for a field that is not rendered to the user? Should this error be propagated to a NON-FIELD error or something else?
I feel its perfectly fine to ask for a complete model instance at this stage of the validation process (lots of methods got passed from the beginning - form.clean() and instance.clean()).
Settings values programmatically is a cumulative operation most of the time, however when its not and things depend on each other (like your example), then even the docs suggests than one can use the form.clean method.
If there is some other dependency outside form.cleaned_data I would prefer to use dependency injection in order to get this data and do the job done. I'm sorry I just see commit=False as an anti-pattern, because the validation needs to be repeated after that (as your example in the first post).
Showing an error message which the user cannot correct is a horrible UX indeed, but still its a UX, and some people may find it as a better alternative to a `500 server error page`, where there is no UX at all. Even a generic message like 'sorry we messed up' would be useful, because the user data that will be preserved into the form. However, in the example shown here, this is not even the case, there is something that the user can do to prevent the error.
It does? Can you give me a link to that please?
The form subclass’sclean()
method can perform validation that requires access to multiple form fields. This is where you might put in checks such as “if fieldA
is supplied, fieldB
must contain a valid email address”. This method can return a completely different dictionary if it wishes, which will be used as thecleaned_data
.
Suppose we add another requirement to our contact form: if the cc_myself field is True, the subject must contain the word "help". We are performing validation on more than one field at a time, so the form’s clean() method is a good spot to do this.
This method should be used to provide custom model validation, and to modify attributes on your model if desired. For instance, you could use it to automatically provide a value for a field, or to do validation that requires access to more than a single field.
The generic error message is something that I supply in some forms of mine where race conditions can happen due to high concurrency. This is why I guard form.save, catch database errors and then use form.add_error to add a generic error message asking for a retry.
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ('name', 'user')
widgets = {'user': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
super(BookForm, self).__init__(*args, **kwargs)
self.fields['user'].disabled = True
#in the view
form = BookForm(data={'name': 'Harry Potter'}, initial={'user': user})
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ('user', 'name')
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(BookForm, self).__init__(*args, **kwargs)
self.fields['user'].widget = forms.HiddenInput()
self.fields['user'].initial = user
self.fields['user'].disabled = True
def book_add(request):
user = get_object_or_404(User, id=1)
if request.method == 'POST':
f = BookForm(request.POST, user=user)
if f.is_valid():
book = f.save(commit=False)
book.user = user
book.save()
messages.add_message(request, messages.INFO, 'book added.')
return redirect('book_add')
else:
f = BookForm(user=user)
return render(request, 'blog/book_add.html', {'f': f})
def post_update(request, post_pk):
user = get_object_or_404(User, id=1)
book = get_object_or_404(Book, pk=post_pk)
if request.method == 'POST':
f = BookForm(request.POST, instance=book, user=user)
if f.is_valid():
post = f.save(commit=False)
post.user = user
post.save()
messages.add_message(request, messages.INFO, 'Post added.')
return redirect('post_update', post.pk)
else:
f = BookForm(instance=book, user=user)
return render(request, 'blog/book_update.html', {'f': f})
The code for models and modelform is exactly same as yours.
Am I doing something wrong?
--To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/44b8461e-2f29-4ac4-82f4-705b52c81560%40googlegroups.com.
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
Book.objects.filter(user=form.user, name=form.cleaned_data["name"]). exists()
class BookForm(forms.ModelForm): class Meta: model = Book fields = ('user', 'name')
def __init__(self, *args, **kwargs): user = kwargs.pop('user') super(BookForm, self).__init__(*args, **kwargs) self.fields['user'].widget = forms.HiddenInput() self.fields['user'].initial = user self.fields['user'].disabled = True
Then the form will raise a Validation error saying there is already a book with that user and that name. Confusing the user.
If you think a ModelForm giving guarantee that the save method will work make sense, the best way to proceed is a third party module.
Simply do a Book.objects.filter(user=form.user, name=form.cleaned_data["name"]). exists()
I would add a clean_book method on the form and use it to check if the user already have a book with that name.