In one of my recent project, I created a website for users to submit their information in a multi-stage form, in each form I use get_or_create to see if the user submit information previously or not, for example, consider user education model as follows,
class UserEducation(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE)
university_name = models.CharField(max_length=100)
in the view, I have the following code,
def education_view(request):
if request.method == "POST":
uedu, created = UserEducation.objects.get_or_create(user=request.user)
uedu.university_name = request.POST['university_name']
uedu.save()
return HttpResponse("success")
I didn't set uploading for the submit button and the problem is some users have multiple education object!
Does anyone know why this happened and whats is wrong with get_or_create?
Insted you can use update_or_create
uedu, created = UserEducation.objects.update_or_create(
user=request.user,uedu.university_name = request.POST['university_name'],
defaults={'user': 'default_value'},
)
I think the reason is that, as every time you moved on to the next step, Django will think that you tell it to create a new object because, as every time you submit a form, a new Model will be created.
What you should do is to halt the process until everything is finished. Something like:
class Person(models.Model):
fn = models.CharField(max_length=40)
class Pet(models.Model):
owner = models.ForeignKey(Person)
name = models.CharField(max_length=40)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
class PetForm(forms.ModelForm):
class Meta:
model = Pet
exclude = ('owner',)
#views
def step1(request):
initial={'fn': request.session.get('fn', None)}
form = PersonForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['fn'] = form.cleaned_data['fn']
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = PetForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
pet = form.save(commit=False)
person = Person.objects.create(fn=request.session['fn'])
pet.owner = person
pet.save()
return HttpResponseRedirect(reverse('finished'))
return render(request, 'step2.html', {'form': form})
Reference
I find the following note in the documentation,
Warning
This method is atomic assuming that the database enforces uniqueness
of the keyword arguments (see unique or unique_together). If the
fields used in the keyword arguments do not have a uniqueness
constraint, concurrent calls to this method may result in multiple
rows with the same parameters being inserted.
the university does not have a unique constraint on the user foreign key and as the result, multiple objects will be saved in the concurrent calls.
Related
When I am trying to edit a profile to add info to a UserProfile model, I am getting this strange error:
IntegrityError at /profiles/edit/
UNIQUE constraint failed: user_profile.user_id
What is wrong here,
model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
bio = models.TextField(blank=True)
phone= models.CharField(max_length=10, blank=True)
address = models.CharField(max_length=1024)
age = models.PositiveIntegerField(blank=True,null=True)
gender = models.IntegerField(choices=GENDER_CHOICES, default=1)
form:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('phone','age','gender','address','bio')
view:
def edit_profile(request):
if request.method == 'POST':
form = UserProfileForm(request.POST)
print request.POST
if form.is_valid():
new_profile = UserProfile(
user = request.user,
bio = request.POST['bio'],
address = request.POST['address'],
age = request.POST['age']
)
new_profile.save()
return HttpResponseRedirect(reverse('user_public_profile', args=(request.user.username,)))
return render(request,'users/edit_profile.html', {'form': form})
else:
form = UserProfileForm()
return render(request,'users/edit_profile.html',
{'form': form})
It's not strange. You already have a profile for that user, so adding another one breaks the unique constraint. You need to edit the existing one, not add a new one.
Also note that you're not using the cleaned form data when you save, which you should be. Either use form.cleaned_data['bio'] etc, or even better just do form.save() which is the whole point of using a model form.
Putting that together:
try:
profile = request.user.userprofile
except UserProfile.DoesNotExist:
profile = UserProfile(user=request.user)
if request.method == 'POST':
form = UserProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
return redirect...
else:
form = UserProfileForm(instance=profile)
return render...
I was getting the same error multiple times and I was really disheartened but finally came over the solution.
Convert:
user = models.OneToOneField(User)
to
user = models.ForeignKey(User)
This should solve the issue.
add (instance = request.user) in UserProfileForm(request.POST)
I accidentally got this error because I forgot to specify update_fields in my call to save. I had:
modified_instance.save(video_attr)
When I should have had:
modified_instance.save(update_fields=[video_attr])
The truthy string was being interpreted as the positional force_update parameter, which obviously causes problems if the record already exists.
Consider the case: If the same user makes the post request again on URL 'profile/edit' then your code will try to create the new UserProfile using the same user instance, but since this is a one to one field and you have already created one profile using that user hence it will throw integrity error.
So you should check first if the profile associated with that user already exists or not, then if it didn't exist then create it.
Yes you are making a OneToOne relation between User and UserProfile models that's why you are facing integrity error,
for fixing this you can change it to ForeignKey
Like :
class UserProfile(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
bio = models.TextField(blank=True)
...
...
I'd like to create a "update user's profile" page to let users modify their profiles, so I come up with the following models:
class Profile(models.Model):
user = models.OneToOneField(User)
nick_name = models.CharField(blank=True,max_length=100)
school = models.CharField(blank=True,max_length=100)
motto = models.CharField(blank=True,max_length=100)
class ProfileForm(ModelForm):
class Meta:
model = Profile
And my view is designed as:
#login_required
def update_profile_view(request):
if request.method == 'POST':
user = request.user
try:
profile = user.get_profile()
except Exception:
profile = Profile.objects.create(user=user)
form = ProfileForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
profile.nick_name = cd['nick_name']
profile.school = cd['school']
profile.motto = cd['motto']
profile.save()
return HttpResponseRedirect('/main_page/')
else:
form = ProfileForm()
return render(request, 'update_profile.html', {'form':form})
The relationship between an user and a profile is apparently 1to1, and with request I can determine the current user. So the form's user field needn't to be filled. Unfortunately, this couldn't pass "the form.is_valid()" test. And it seems hard to modify a form before "is_valid" invoked. For simplicity, I don't want to create my own Form Class, neither do I want to write customized form validation. Is there any other way to solve the problem?
Your view can be greatly simplified:
#login_required
def update_profile_view(request):
try:
profile = Profile.objects.get(user=request.user)
except Profile.DoesNotExist:
profile = None
form = ProfileForm(request.POST or None, instance=profile)
if request.method == 'POST':
if form.is_valid():
form.save()
return HttpResponseRedirect('/main_page/')
return render(request, 'update_profile.html', {'form':form})
There's no need to manually assign the fields like you're doing. Django ORM knows how to do an insert versus an update automatically. So if you simply pass the ProfileForm an instance of a Profile, it knows to do an update. If there's no instance of a profile, it's going to do an insert.
Now, if you want to make the assignment of the user transparent in the UI, you'll need to exclude the user field from the form and assign it yourself. There are a couple of different ways to do that.
I would also recommend leveraging reverse in your redirect so you don't have a hard-coded path.
You have basicly two choices:
1 Modification of ProfileForm:
class ProfileForm(ModelForm):
class Meta:
model = Profileclass
exclude = ('user',)
2 Change this lines as follows:
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
updated_profile = form.save()
You can either set the user field's value to not required in the init method (self.fields['user'].required = False) of the form or set the user not editable in the model (editable=False).
In your view method, call profile = form.save(commit=False), then do profile.user = your_user and profile.save()
You don't have to apply the cleaned data manually to the profile since the ModelForm does this.
Let's say that I have a model that handles recipes, and I want to allow users to input their own recipes via a form. I then want to associate that recipe entry with the user ID of the user who inputted it. My guess is that my model would look something like this:
class Recipe(models.Model):
name = models.CharField(max_length=100)
body = models.TextField()
creator = models.ManyToManyField(User)
def __unicode__(self):
return self.creator
Is that correct? And if I created a model form, it would look something like this:
class RecipeForm(ModelForm):
class Meta:
model = Recipe
But how would I go about automatically passing the user information to the Recipe model upon submission? Would this take place in my view?
My current view is like this:
def recipe(request):
if request.method == 'POST':
form = RecipeForm(request.POST) #if POST method, bound form to POST data
if form.is_valid():
form.save()
else:
form = RecipeForm() #unbound form.
recipe_list = Recipe.objects.all()
return render_to_response('forms/recipes.html',
{'form': form, 'recipe_list': recipe_list},
context_instance = RequestContext(request))
How would I set the user to the model before saving it?
Yes, your view would need to set the user on the recipe model before saving it.
Edit:
You should accept Ignacio's answer, since he added it in the comment.
Here is how you would add your user:
from django.shortcuts import render
def recipe(request):
if request.method == 'POST':
form = RecipeForm(request.POST) #if POST method, bound form to POST data
if form.is_valid():
obj = form.save(commit=False) # don't save to DB
obj.creator = request.user # adds the user
obj.save()
else:
form = RecipeForm() #unbound form.
recipe_list = Recipe.objects.all()
return render(request,'forms/recipes.html',
{'form': form, 'recipe_list': recipe_list})
I have a sample form:
class AdminDiscountForm(ModelForm):
class Meta:
model = Discount
exclude = ('company',)
the model it's pointing to is:
class Discount(models.Model):
class Meta:
verbose_name=_('Discount')
verbose_name_plural=_('Discounts')
unique_together = ('company','type')
company = models.ForeignKey(Company)
type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))
The form excludes the 'company' field because the user has already selected this using the UI.
i am planning on doing a:
company = blah
if form.is_valid():
obj = form.save(commit=False)
obj.company = company
obj.save()
The problem is that the combination of 'company' and 'type' should be unique (hence the 'unique_together'). This is enforced in the database, so django doesn't care.
I need to extend the clean() method of this form to check for uniqueness as such:
def clean(self):
cleaned_data = self.cleaned_data
# check for uniqueness of 'company' and 'type'
The problem here is that 'company' is not in there because it has been excluded.
What is the best way to raise a form validation error in this case?
-- edit
This is only for adding discount entries.
There's no initial instance.
Jammon's method is the one I use. To expand a bit (using your example):
models.py
class Discount(models.Model):
class Meta:
verbose_name=_('Discount')
verbose_name_plural=_('Discounts')
unique_together = ('company','type')
company = models.ForeignKey(Company)
type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))
forms.py
class AdminDiscountForm(ModelForm):
class Meta:
model = Discount
exclude = ('company',)
views.py
def add_discount(request, company_id=None):
company = get_object_or_404(Company, company_id)
discount=Discount(company=company)
if request.method == 'post':
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = AdminDiscountForm(instance=company)
context = { 'company':company,
'form':form,}
return render_to_response('add-discount.html', context,
context_instance=RequestContext(request))
This works by creating an instance of your discount model, then binding your form to this instance. This instance is not saved to your db but used to bind the form. This bound form has a value for company of the bound instance. It is then sent to your template for the user to fill out. When the user submits this form, and the form is validated, the model validation check will check for uniqueness of the unique together defined in Meta.
See Model Validation Docs and overriding clean for ModelForms
edit:
You can do a couple of things to catch non unique together entry attempts.
Inside your form.is_valid() you can except an Integrity Error like this:
if request.method == 'post':
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
try:
form.save()
return HttpResponse('Success')
except IntegrityError:
form._errors["company"] = "some message"
form._errors["type"] = "some message"
else:
...
Use self.instance within the model form's clean method to check for uniqueness.
You could try this:
discount = Discount(company = blah)
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
discount = form.save()
And the docs say: By default the clean() method validates the uniqueness of fields that are marked as ... unique_together
I have a form that edits an instance of my model. I would like to use the form to pass all the values as hidden with an inital values of username defaulting to the logged in user so that it becomes a subscribe form. The problem is that the normal initial={'field':value} doesn't seem to work for manytomany fields. how do i go about it?
my views.py
#login_required
def event_view(request,eventID):
user = UserProfile.objects.get(pk=request.session['_auth_user_id'])
event = events.objects.get(eventID = eventID)
if request.method == 'POST':
form = eventsSusbcribeForm( request.POST,instance=event)
if form.is_valid():
form.save()
return HttpResponseRedirect('/events/')
else:
form = eventsSusbcribeForm(instance=event)
return render_to_response('event_view.html', {'user':user,'event':event, 'form':form},context_instance = RequestContext( request ))
my forms.py
class eventsSusbcribeForm(forms.ModelForm):
eventposter = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.HiddenInput())
details = forms.CharField(widget=forms.Textarea(attrs={'cols':'50', 'rows':'5'}),label='Enter Event Description here')
date = forms.DateField(widget=SelectDateWidget())
class Meta:
model = events
exclude = ('deleted')
def __init__(self, *args, **kwargs):
super(eventsSusbcribeForm, self).__init__(*args, **kwargs)
self.fields['username'].initial = (user.id for user in UserProfile.objects.filter())
my models.py
class events(models.Model):
eventName = models.CharField(max_length=100)
eventID = models.AutoField(primary_key=True)
details = models.TextField()
attendanceFee = models.FloatField(max_length=99)
date = models.DateField()
username = models.ManyToManyField(UserProfile, related_name='user', blank=True)
eventposter = models.ForeignKey(UserProfile, related_name='event_poster')
deleted = models.BooleanField()
def __unicode__(self):
return u'%s' % (self.eventName)
Can you post your Event model? It's too hard to guess what you are trying to do without that. I have to assume a few things without it, so I'm sorry if I'm wrong.
First off, I'm guessing that you should not be using an Event ModelForm for the EventSubscriptionForm. That doesn't really make sense. Hopefully, you created a through class for Event and User, so in your Event model, you have something like
subscriber_users = models.ManyToManyField(User, through="Subscription")
and
class Subscription(models.Model):
user = models.ForeignKey(User, related_name="events",)
event = models.ForeignKey(Event, related_name="subscribers")
Then you can use a Subscription ModelForm.
Is there any reason you're using eventID instead of the django idiom, event_id? You should also import your Event and EventSubcribeForm classes with Pythonic casing. One very important thing is that you should be linking everything to User and not UserProfile.
Technically, it makes more sense to set initial in the view rather than the form init, because you would have to pass request.user to init anyway.
I think you should try this for your view...
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user}
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))
A few notes
use request.user.get_profile() for the current user's profile object
you can use request.POST or None to avoid the request.method cases
always use named urls so you can reverse on names instead of hard-coding urls into views
if you want user in your template context, just setup a context processor (see pinax for example on how to do this) instead of passing it in every single view. You can always use request.user also.
Keep in mind that this code will only work if you have that through class setup like I said and you use a form like
class EventSubcriptionForm(forms.ModelForm):
class Meta:
model = Subscription
exclude = ('event')
EDIT
Thanks a bunch for the ups. I'm not new to django, but somehow very new to SO.
Okay, you should really read some of the PEPs about Python conventions http://www.python.org/dev/peps/pep-0008/ or some SO posts about it What is the naming convention in Python for variable and function names?.
Here's what I recommend for your event app models.py:
class Event(models.Model):
name = models.CharField(max_length=100)
details = models.TextField()
attendance_fee = models.FloatField(max_length=99)
date = models.DateField()
poster = models.ForeignKey(User, related_name='events_posted')
deleted = models.BooleanField()
attendee_users = models.ManyToManyField(User, through="Attendance")
def __unicode__(self):
return self.name
class Attendance(models.Model):
user = models.ForeignKey(User, related_name="events",)
event = models.ForeignKey(Event, related_name="attendees")
Notes
The name of a class is capitalized and singular. You are not describing events, you are the blueprint for an Event.
you never need the name of the class in its attributes, i.e. event_name can just be name.
all variables are lowercase_and_underscored
always link to User, not your profile model. A lot of django code expects this.
So now you can access the users attending the event with event.attendees.
I found this while trying to set defaults for the manytomany. I didn't want to add a through table.
based on the view Casey posted, but adding the user in the manytomany relation.
for the initial post:
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user, 'username': [ request.user.id, ] } # makes the poster also an attendee
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))
updated version:
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user, 'subscriber_users': [ request.user.id, ] } # makes the poster also an subscriber
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))