Django form fails validation on a unique field - python

I have a simple model that is defined as:
class Article(models.Model):
slug = models.SlugField(max_length=50, unique=True)
title = models.CharField(max_length=100, unique=False)
and the form:
class ArticleForm(ModelForm):
class Meta:
model = Article
The validation here fails when I try to update an existing row:
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid(): # POOF
form.save()
Creating a new entry is fine, however, when I try to update any of these fields, the validation no longer passes.
The "errors" property had nothing, but I dropped into the debugger and deep within the Django guts I saw this:
slug: "Article with this None already exists"
So it looks like is_valid() fails on a unique value check, but all I want to do is update the row.
I can't just do:
form.save(force_update=True)
... because the form will fail on validation.
This looks like something very simple, but I just can't figure it out.
I am running Django 1.0.2
What croaks is BaseModelForm.validate_unique() which is called on form initialization.

I don't think you are actually updating an existing article, but instead creating a new one, presumably with more or less the same content, especially the slug, and thus you will get an error. It is a bit strange that you don't get better error reporting, but also I do not know what the rest of your view looks like.
What if you where to try something along these lines (I have included a bit more of a possible view function, change it to fit your needs); I haven't actually tested my code, so I am sure I've made at least one mistake, but you should at least get the general idea:
def article_update(request, id):
article = get_objects_or_404(Article, pk=id)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return HttpResponseRedirect(to-some-suitable-url)
else:
form = ArticleForm(instance=article)
return render_to_response('article_update.html', { 'form': form })
The thing is, as taurean noted, you should instantiate your model form with the object you wish to update, otherwise you will get a new one.

I was also searching for a way to update an existing record, even tried form.save(force_update=True) but received errors??
Finally by trial & error managed to update existing record. Below codes tested working. Hope this helps...
models.py from djangobook
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField(blank=True, verbose_name='e-mail')
objects = models.Manager()
sel_objects=AuthorManager()
def __unicode__(self):
return self.first_name+' '+ self.last_name
class AuthorForm(ModelForm):
class Meta:
model = Author
# views.py
# add new record
def authorcontact(request):
if request.method == 'POST':
form = AuthorForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
form = AuthorForm()
return render_to_response('author_form.html', {'form': form})
update existing record
def authorcontactupd(request,id):
if request.method == 'POST':
a=Author.objects.get(pk=int(id))
form = AuthorForm(request.POST, instance=a)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
a=Author.objects.get(pk=int(id))
form = AuthorForm(instance=a)
return render_to_response('author_form.html', {'form': form})

All i can guess is that you are getting an object to fill a form, and trying to save it again.
Try using a ModelForm, and intantiate it with desired object.

It appears that your SlugField is returning None and because a null/blank slug already exists somewhere in the database, its giving an 'already exists' error. It seems like your slug field isn't saving correctly at all.

Related

Cannot add model instance to OneToManyField in django - "None has no attribute add"

I already have seen this bug in other post, but still in trouble.
I'm trying to create a social network like instagram where users will be able to publish posts (photos).
I have User class which herit from AbstractUser, and got a OneToMany field of posts: each user can publish many posts.
After successfully pulling my photo from: PostForm(request.POST, request.FILES) and saving it correctly, I cannot add this photo to the current user's publications/posts and got error:
'NoneType' object has no attribute 'add'
def blog_and_photo_upload(request):
form = PostForm()
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
user = get_user(request) # user instance is correct with good pk
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.save()
user.posts.add(post) # row doesnt work
redirect('home')
return render(request, 'base/upload_post.html', {'form': form})
models.py
class Post(models.Model):
...
image = ResizedImageField(size=[300, 300], blank=True, upload_to='posts')
class User(AbstractUser):
...
posts = models.ForeignKey(Post, on_delete=models.Cascade, null=True)
You can simply update the form like this:
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.posts = post
user.save()
return redirect('home')
But, I think the design of the model is wrong, User to Post relation should be like this:
Class User(...):
posts = models.ManyToManyField(Post)
In that way, your original implementation should work. (Probably you don't need user.save() call in your view).
At first there should be return redirect(...) not only redirect() and secondly try to use the following view:
def blog_and_photo_upload(request):
form = PostForm()
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
user = get_user(request) # user instance is correct with good pk
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.posts.add(post) # add post to user's posts field
user.save()
return redirect('home')
return render(request, 'base/upload_post.html', {'form': form})
You need to bind first Post with User model like add a ForeignKey or a ManyToManyFields to relate them
posts = models.ForeignKey(User)
then you will be able to call it like you did
user.posts # this won't return None
Check this many to many field docs: https://docs.djangoproject.com/en/4.1/topics/db/examples/many_to_many/

How to bind a django form to an object instance

I come from DRF background so please already assume that I might be getting something wildly wrong here. I am trying to use Django Form as a sort of proxy for DRF serializers. As in, when I fetch an object, I can quickly render it back, and I can of course accept POST requests and store them. What I can't seem to find is how do I use my object instances to process them with forms.
Here's my form:
class ProfileForm(ModelForm):
class Meta:
model = UserProfile
fields = ('name', 'profile_pic')
The actual model:
class UserProfile(models.Model):
user = models.OneToOneField(CustomUser, null=True, on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True)
profile_pic = models.ImageField(upload_to='profile_image', null=True, blank=True)
def __str__(self):
return str(self.user)
My view:
def get_profile(request):
if request.user.is_authenticated:
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
# update the model
profile = request.user.userprofile
form = ProfileForm(model_to_dict(profile))
if not form.is_valid():
form = ProfileForm()
return render(request, 'profile-edit.html', {'form':form})
else:
return render(request, 'index.html')
So, in my view (which is a GET view), I'm doing something like this:
from django.forms import model_to_dict
profile = request.user.userprofile
form = ProfileForm(model_to_dict(profile))
return render(..., form)
Is this even the right approach? Besides, the problem is that my form doesn't seem to handle the profile_pic field properly (i.e. the img src field in the from html is just __).
Ideally I would want to build the form from the object instance itself, but that also doesn't work.
form = ProfileForm(instance = profile) is bound, but not valid
form = ProfileForm(UserProfile.objects.all()[0]) is bound, and I can access data items through form.data.<fields>, but as soon as do form.is_valid(), I get:
AttributeError: 'UserProfile' object has no attribute 'get'
UPDATE: form = ProfileForm(request.<GET/POST>, instance=profile) is actually bound but not valid with the error that field name is required, even though profile.name is indeed valid CharField.
So how can I take my object, then bind it to a form, and then return it to be rendered properly. Imgine this is a user profile that I render, but also let the user update it whenever they want. What is the Django way of doing it? (I come from DRF background.)
Most of the SO answers that I have found almost always initialize the form with request.POST which I don't need.
EDIT: My use case, I have an endpoint /profile which, when requested as GET, should return a profile (with all the info that their UserProfile object holds but the user can also edit any of the fields that they want to. Say they have edited a field, they can simply hit update, which would then submit the form. On the backend, there profile would get updated, and they would get served the latest profile details. Of course, I can do it by rendering a from using a template, but I instead wanted to use the form which is already there. As I mentioned model_to_dict approach kind of serves this point to some extent, except it breaks for images... so I thought there might be a solution.
Looking at your view you make some mistakes which simply make using the forms difficult. Firstly if you want to update a form you should instantiate the form with the model instance so instead of form = ProfileForm(model_to_dict(profile)) it should be form = ProfileForm(instance=profile).
Also right after this line you write:
if not form.is_valid():
form = ProfileForm()
Why check is_valid on a form that is not bound?
Your view should ideally look something like:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def get_profile(request):
profile = request.user.userprofile
form = ProfileForm(instance=profile)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
return redirect('some-view-name')
return render(request, 'profile-edit.html', {'form':form})

Issue with ModelForm and related objects

I have come to an impasse when using a ModelForm.
I'm extending the User model that comes with Django, and I'm also using a ModelForm so the user can edit it.
Following the same example in the documentation, I would have this code.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# In this case, department is optional, so I have set 'blank' and 'null' to True.
department = models.CharField(max_length=100, blank=True, null=True)
forms.py
class DepartmentForm(ModelForm):
class Meta:
model = Employee
fields = ['department',]
The problem comes at the view. I found that I need to pass an instance of the model to the form so the save() function works without having to customize it, but of course, user.employee has not been created yet, therefore it throws an error.
views.py
def DepartmentView(request):
# Here is the issue.
department = request.user.employee
if request.method == 'POST':
# I need to pass the instance here.
form = DepartmentForm(request.POST, instance=department)
if form.is_valid():
form.save()
else:
# And also here so it autocompletes the form.
form = DepartmentForm(instance=department)
return render(request, 'employee.html', {'form': form})
It works if I manually add a value to user.employee.department through the shell and then reload the page, otherwise the error is as follow.
RelatedObjectDoesNotExist at [something]
User has no employee.
Or something like that... I'm sorry, I didn't try the code above so the error could be a little different, but the concept is exactly the same.
I'm also sorry if this has been asked before. I did a Google search and couldn't find an answer to this issue.
You could use get_or_create to fetch the employee from the db, or create it if it doesn't exist.
department, created = Employee.objects.get_or_create(user=request_or_user, department='')
if request.method == 'POST':
form = DepartmentForm(request.POST, instance=department)
...
Another option is to use a signal, so that the related model is created when the user is created. Then you can assume that the employee already exists, and you can use request.user.employee instead of get_or_create.

Django 1.6: How to print the number of objects a user uploaded?

I'm looking to get the number of objects a user uploaded into the database. For example:
Let's say i have a model called "Link" (which adds a url with its title and description) into my models.py:
class Link(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=200)
link = models.URLField(max_length=200)
description = models.TextField()
With its form:
class UrlForm(ModelForm):
class Meta:
model = Link
And its view:
def linkurl(request):
form = UrlForm()
if request.method == 'POST':
form = UrlForm(data=request.POST, instance=Link())
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect("home")
else:
form = UrlForm()
return render(request, "addurl.html", {'form': form})
So, knowing that the user can add urls to my app, i'm looking to print the number of urls the user uploaded into my app. Is that actually possible?
Thank you, in advance.
Use filter and count:
# change request.user to user id or user instance you need
user_links_count = Link.objects.filter(user=request.user).count()
There are multiple ways to achieve this.
Use a query and pass it into your template:
ndpu has already pointed it out here:
https://stackoverflow.com/a/22288960/659900
user_links_count = Link.objects.filter(user=request.user).count()
render(request, "addurl.html", {'form': form, linkscount: user_links_count})
Use a model property which you can use to access your model over the form directly, without touching the render method in your view.
add the following method to your link model: (warning: untested!)
#property
def user_links_count(self):
try:
return self.objects.filter(user=self.user).count()
except:
return 0 #or any error message you want
now in your template, you can access your link model directly via the form you are using. assuming you are not using an empty model:
{{ form._meta.model.user_links_count }}
However I would recommend ndpu's solution for a one off solution. Consider an enhancement to your model if you need this functionality more often

form.is_valid() always returning false

Model class
class Fuzz_Engine(models.Model):
id = PositiveTinyIntField(primary_key = True)
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
View Class
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
# input validation for add phone model form
if form.is_valid():
fuzzEngineToAdd = Fuzz_Engine (engine_name = request.POST['engine_name'], version = request.POST['version'])
fuzzEngineToAdd.save(force_insert=True)
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': fuzzEngineToAdd.id,'fe': fuzzEngineToAdd,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
I have looked into a few similar questions on this issue, tried to print out the errors but doesn't seem to appear.
Django Formsets - form.is_valid() is False preventing formset validation
form.is_valid() always returning false
I have a feeling that the cause of the error lies in the structure of my model form class.
The .is_valid is false as I have placed a code in that if statement and it doesn't run, however if I have an else statement(which is not here) for if it is not valid, it will appear.
Can anyone provide another way of debugging this kind of error?
Couple of issues, it's hard to debug the code if the code you paste in isn't formatted well. The indentations were a mess so I'm not sure if that's causing a problem.
It seems like you are manually assigning a foreign key for your model. I would suggest just letting django handle the id for the model:
class Fuzz_Engine(models.Model):
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
Your form looks fine:
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
Some problems I see in your views include:
you shouldn't use request.POST['field_names'] directly. you should be getting the cleaned_data from your form.
you can save the form directly because it is a ModelForm. if you need the instance that you just created, that is what is returned from the save method, you can set a variable and use that as shown below.
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
if form.is_valid():
instance = form.save()
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': instance.id,'fe': instance,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
With your original view, it looks like you are trying to save a Fuzz_Engine instance with no id.
DTing has some great points, but I suspect your actual problem is related to your explicit definition of the id field on your model. Unless you have a really good reason, you should never do this - Django defines an autoincrement field automatically, and there is rarely any point overriding this, unless you are using a legacy db that can't be changed.
In your case, you have defined it as a tinyint without autoincrement. That means that field is going to be required on any form, as it needs to be specified manually every time you create a new instance. You haven't shown the template you're using to display the form so it's impossible to be sure, but I imagine you're not showing this field at all.
If you really really want to carry on doing it this way, you will need to specify exclude = ('id',) on the form Meta. Then in your is_valid clause, taking on board DTing's recommendations, you'll need to do this:
if form.is_valid():
instance = form.save(commit=False)
instance.id = some_function_for_calculating_id()
instance.save()
But as I say, you shouldn't be doing that at all.

Categories

Resources