So I been seeing the same question posted in different scenarios and I am unable to get mine to work. Basically trying to find a model instance if it already exists and create a new one if it doesn't - based on if the instance have the same field as the user's username
I tried get_object_or_404 and even changing the primary key to a field in the model class.
this is the models.py
class Cart(models.Model):
user = models.CharField(max_length=30)
#classmethod
def create(cls, user):
user = cls(user=user)
return user
def __str__(self):
"""String for representing the Model object."""
return f'{self.id} {self.user}'
this is the views.py
def cart(request, pk):
try:
pizza = PizzaInstance.objects.get(id=pk)
# get the topping(s)
topping_1 = int(request.POST["topping1"])
topping = PizzaTopping.objects.get(pk=topping_1)
# get the username
user = request.user.username
# check if the user already has an order
try:
order = Cart.objects.get(user=user)
except KeyError:
order = Cart.create([user])
order.save()
user creation
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = (
'username',
'first_name',
'last_name',
'email',
'password1',
'password2'
)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
I expected to see the order being saved and a new instance being created or even new instances added to the cart.
error is get is -
Cart matching query does not exist.
Thank you!
There is a couple of problems:
You should retrieve the user with request.user instead of request.user.username (the latter gives you the username which is a str instead of the User instance.
You shouldn't really create your objects with a custom class method. Instead, use the object manager and call Cart.objects.create(user=user) (and if you do need to make a custom create() function, it should be defined on a custom manager class).
With these changes you should be able to use Cart.objects.get_or_create(user=user), which returns a tuple containing your Cart object and a bool indicating wether the object was created or not (ie. existed in the first place).
So put together:
def cart(request, pk):
try:
pizza = PizzaInstance.objects.get(id=pk)
# get the topping(s)
topping_1 = int(request.POST["topping1"])
topping = PizzaTopping.objects.get(pk=topping_1)
# get the user
user = request.user
# get the Cart object associated with 'user' or create a new one
order, created = Cart.objects.get_or_create(user=user)
Try using the .get_or_create() method:
cart, created = Cart.objects.get_or_create(...)
You're doing some odd things here.
Cart.objects.get won't raise a KeyError. It raises the error you see: Cart.DoesNotExist. So that is the error you need to catch.
Additionally, for some reason you are wrapping the user in a list when you pass it in the except block. Don't do that.
try:
order = Cart.objects.get(user=user)
except Cart.DoesNotExist:
order = Cart.create(user)
order.save()
Related
Here i am simply using User model from django.contrib.auth.models import User and I have a custom userprofile model where user foreign key in that django built in User model , well i have created an email_address field which can manually update by a user, and there is an other email field which is built in inside django User model , I want to update that email field as userprofile email_address field update.
I am simply getting user object which username and try to get email of that user object but getting an error : 'QuerySet' object has no attribute 'email'
models.py
def save(self,*args, **kwargs):
user_obj = User.objects.filter(username=self.user.username)
print(user_obj,'user object')
print(user_obj.email,'email have a user')
email = self.email_address
user_obj.email = self.email_address
print(user_obj.email,'email have a user')
user_obj.save(user_obj.email)
super(UserProfile,self).save(*args, **kwargs)
user_obj is not a User object, it is a QuerySet of User objects.
You can retrieve a single User object with .get(…) [Django-doc]:
def save(self,*args, **kwargs):
user_obj = User.objects.get(username=self.user.username)
user_obj.email = self.email_address
user_obj.save()
super(UserProfile,self).save(*args, **kwargs)
But here you can actually simply use the self.user object:
def save(self,*args, **kwargs):
user = self.user
user.email = self.email_address
user.save()
super(UserProfile,self).save(*args, **kwargs)
That being said, I would advise not to store data twice. So in case the User object has an email address, there is no need to store this in the UserProfile model as well. You can easily access this with:
from django.conf import settings
class UserProfile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
#property
def email_address(self):
return self.user.email
This thus means that you access the email address of the related User object. This avoids data duplication, which will make it hard to keep data in sync. Especially since the .save() method will not run for example for bulk create/updates by the Django ORM.
I design Django Model as shown below :
`
class User(models.Model):
id=models.AutoField(primary_key=True)
username=models.CharField(max_length=25,verbose_name="username")
name=models.CharField(max_length=20,default="noName",verbose_name="name")
password=models.CharField(max_length=20,verbose_name="password")
email=models.EmailField()
userCode=models.CharField(default=" ",max_length=100,verbose_name="User Code")
def __str__(self):
return self.username
class Meta:
ordering = ['-username']`
Then I create an object in view.py. Although id is auto incremented, python want me to define an id. View.py is shown below.
def register(request,id):
if request.method=='POST':
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
name=form.cleaned_data["name"]
password = form.cleaned_data["password"]
email=form.cleaned_data.get("email")
newUser = User(1,username,name,password,email)
newUser.save()
return redirect('mainPage')
else:
form=RegisterForm()
context = {
"form" : form,
"id":id
}
return render(request,"SignupLogin.html",context)
User(1,username,name,password,email) in that line, 1 is the id number. When I delete it, error which is about missing variable id, is thrown. How can I get rid of 1?
Just introduce your parameters to model init function. That might help :)
newUser = User(username=username, name=name, password=password, email=email)
and if you have nullable fields (null=True) you must describe it on model.
You do not need to declare an autoincrementing primary key field by hand – Django implicitly adds an id field for you if there is none.
Your problem is likely not having set editable=False on that manually created field of yours.
I would like to prevent a save in a django model when a certain constraint is not met and give a validation error so that a django staff user knows what went wrong.
The constraint is the count() from an intermediate table specified using the through parameter.
models.py:
class Goal(models.Model):
name = models.CharField(max_length=128)
class UserProfile(models.Model):
goals = models.ManyToManyField(Goal, through=UserProfileGoals, blank=True)
class UserProfileGoal(models.Model):
goal = models.ForeignKey(Goals)
user_profile = models.ForeignKey(UserProfile)
class UserGoalConstraint(models.Model):
user_profile = models.OneToOneField(UserProfile)
max_goals = models.PositiveIntegerField()
So the UserGoalConstraint.max_goals gives me the number of the maximum definable UserProfile.goal which are stored in the UserProfileGoal model (same UserGoal can be stored more often to the UserProfile)
I have read and tried solutions from several posts, which are using ModelForm's clean(), Model's clean() and pre_save signal events,
but the actual problem I have is, how do I know if it is just an update or a new database entry, because
class UserProfileGoal(models.Model):
goal = models.ForeignKey(Goals)
user_profile = models.ForeignKey(UserProfile)
def clean(self):
goal_counter = self.user_profile.goals.count() + 1
try:
qs = UserGoalConstraint.objects.get(user_profile=self.user_profile)
except UserGoalConstraint.DoesNotExist:
raise ObjectDoesNotExist('Goal Constraint does not exist')
if goal_counter > qs.max_goals:
raise ValidationError('There are more goals than allowed goals')
does not really work, as clean() can also be an update and the +1 gives me a wrong result which leads to the ValidationError.
My client should use the django-admin interface to add goals to the user profile directly via an Inline:
admin.py:
class UserProfileGoalInline(admin.TabularInline):
model=UserProfileGoal
class UserProfileAdmin(admin.ModelAdmin)
...
inlines = [UserProfileGoalInline, ]
So he needs to be nicely informed when he adds to many goals to a user profile.
Maybe I am missing something obvious on how to solve this problem...?
I am looking for a working and somehow user friendly solution (= get informed in admin interface).
[UPDATE]:
I tried know to check wether it is created or not with the self.pk is None trick at the beginning of the clean()
if self.pk is not None:
return # it is not a create
...
I thought that would deal with the issue...
However, in the admin inline, when the staff user adds more than one goal at the same time, the clean() does not recognize these. Debug output shows for 2 goals added, that the goal counter holds the same number even the second entry should have one more and should give an validation error
Thanks to #zaidfazil for a starting solution:
class UserProfileGoalForm(forms.ModelForm):
class Meta:
model = UserProfileGoal
...
def clean(self):
cleaned_data = super(UserProfileGoalForm, self).clean()
if self.instance.pk is not None:
return cleaned_data
user_profile = self.cleaned_data.get('user_profile')
goal_count = user_profile.goals.count()
goal_limit = UserGoalConstraint.objects.get(user_profile=user_profile).max_goals # removed try catch for get for easier reading
if goal_count >= goal_limit:
raise ValidationError('Maximum limit reached for goals')
return cleaned_data
However, this does not handle the inline in the UserProfile admin interface: clean() won't handle correctly if you add more than one Goal at the same time and press save.
So I applied the UserProfileGoalForm to the inline and defined max_num :
class UserProfileGoalInline(admin.TabularInline):
model=UserProfileGoal
form = UserProfileGoalForm
def get_max_num(self, request, obj=None, **kwargs):
if obj is None:
return
goal_limit = UserGoalConstraint.objects.get(training_profile=obj).max_goals
return goal_limit # which will overwrite the inline's max_num attribute
Now my client can only add at maximum the max_goals value from the UserGoalConstraint, and also a possible admin form for UserProfileGoal will handle the constraint:
class UserProfileGoalAdmin(admin.ModelAdmin):
form = UserProfileGoalForm
You could handle it in ModelForm clean method,
class GoalForm(forms.ModelForm):
class Meta:
model = Goal
.....
def clean(self):
cleaned_data = super(GoalForm, self).clean()
if self.instance.pk is not None:
return cleaned_data
goal_limit = self.user_profile.usergoalconstraint.max_goals
goal_count = self.user_profile.goals.count()
if goal_count >= goal_limit:
raise ValidationError("Maximum limit reached for goals")
return cleaned_data
I have two different models that reference the django User, namely Recruiter and Userprofile.
I have a function that receives a User object as an argument. I need to check which of the related objects exist and take some action accordingly. I am using nested try-catch for doing this:
def some_function(user, ....):
...
try:
profile = user.userprofile
profile.profile_pic.save('{0}_social.jpg'.format(user.username))
profile.save()
except:
try:
recruiter = user.recruiter
recruiter.cover_pic.save('{0}_social.jpg'.format(user.username))
recruiter.save()
except:
pass
Is there a better/more elgant way of doing this?
EDIT: The models in consideration are as follows
class Recruiter(models.Model):
user = models.OneToOneField(User, unique=True, related_name='recruiter')
...
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True, related_name='userprofile')
...
You can use the .exists() QuerySet API, like so (presuming user is an instance of the User model, and both Userprofile and Recruiter key to that model with a field also named user):
def some_function(user, ...):
if Userprofile.objects.filter(user=user).exists()
profile = user.userprofile
profile.profile_pic.save('{0}_social.jpg'.format(user.username))
profile.save()
elif Recruiter.objects.filter(user=user).exists()
recruiter = user.recruiter
recruiter.cover_pic.save('{0}_social.jpg'.format(user.username))
recruiter.save()
else:
# User has neither Userprofile nor Recruiter associated with it!
# Do something here to handle that case, or just get rid of the else
Note that the .exists() call will first do a query to see if the object exists, and then the line that access that object (e.g, user.userprofile) will run another query to actually access that object. A slightly optimized way, though maybe less readable, might be:
def some_function(user, ...):
userprofiles = Userprofile.objects.filter(user=user)
recruiters = Recruiter.objects.filter(user=user)
if len(userprofiles):
profile = userprofiles[0]
profile.profile_pic.save('{0}_social.jpg'.format(user.username))
profile.save()
elif len(recruiters):
recruiter = recruiters[0]
recruiter.cover_pic.save('{0}_social.jpg'.format(user.username))
recruiter.save()
else:
# User has neither Userprofile nor Recruiter associated with it!
# Do something here to handle that case, or just get rid of the else
i'm using Django registration, and unlike everybody else, I seem to have the opposite problem. My User object is saved fine, but my UserProfile object isn't!
I followed this website:
http://birdhouse.org/blog/2009/06/27/django-profiles/
which was really good, and so now i have:
class ProfileForm(forms.ModelForm):
YESNO = [
(True,mark_safe('<img src="/static_files/greenTick.png"/>')),
(False,mark_safe('<img src="/static_files/redCross.png"/>'))]
class Meta:
model = UserProfile
exclude = ('isTweeting','points','user')
fields = ('display_name','first_name','last_name','email','gravatar')
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
self.fields['email'].initial = self.instance.user.email
self.fields['first_name'].initial = self.instance.user.first_name
self.fields['last_name'].initial = self.instance.user.last_name
self.fields['display_name'].initial = self.instance.user.username
self.fields['gravatar'].initial = self.instance.usesGravatar
#add in the input to size it juuuuust right.
email = forms.EmailField(label="Primary email",help_text='',widget=forms.TextInput(attrs={'class': 'wideInput'}))
first_name = forms.Field(label="First name",help_text='',required=False,widget=forms.TextInput(attrs={'class': 'wideInput'}))
last_name = forms.Field(label="Last name",help_text='',required=False,widget=forms.TextInput(attrs={'class': 'wideInput'}))
display_name = forms.Field(label="Display name",help_text='',widget=forms.TextInput(attrs={'class': 'wideInput'}))
gravatar = ImgModelChoiceField(label='Gravatar', choices=YESNO, widget=forms.RadioSelect(renderer=ImgRadioFieldRenderer))
def save(self, *args, **kwargs):
"""
Update the primary email address on the related User object as well.
"""
u = self.instance.user
u.email = self.cleaned_data['email']
u.username = self.cleaned_data['display_name']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
u.save()
self.instance.gravatar = (self.cleaned_data['gravatar'] == 'True')
profile = super(ProfileForm, self).save(*args,**kwargs)
return profile
this object is passed into the Django-profile as the form_class for use, as described in the website above. The problem i have is that when i submit my form, while the "User" data is updated correctly - any changes in the email or whatnot propagate to the db - the change to the "gravatar" value is not sent. Also no error is thrown.
Any ideas what I should do?
I'm going to hazard a guess here
def save(self, *args, **kwargs):
...
self.instance.gravatar = (self.cleaned_data['gravatar'] == 'True')
profile = super(ProfileForm, self).save(*args,**kwargs)
return profile
It seems you are using a custom widget, and by the looks of things you need to change the string 'True' (passed back from the form) to a boolean True before saving it to the DB. When you call save() on the next line though, the ModelForm will overwrite the value you have given self.instance.gravatar with the data directly from the form's cleaned_data:
https://github.com/django/django/blob/master/django/forms/models.py#L351
Also, in __init__, you don't need to include
self.fields['gravatar'].initial = self.instance.usesGravatar
as this field is already bound to the model form and will be automatically populated (if the UserProfile is being edited for example) when you instantiate the form along with an instance in your view.
Finally, in your Meta, you don't need to include both excludes and fields, one or the other should be fine.
First of all consider suggestions from #Timmy.
The only thing which else should be noticed is in this line:
profile = super(ProfileForm, self).save(*args,**kwargs)
By default the save method has commit=True. Verify that the function which is calling this Form might be sending commit=False in args or kwargs. If yes then you have to manually save the profile profile.save() before returning because commit=False means the changes will not reflect to the db.
And why you are allowing user to update both username and email? How you will keep track of the registration process if you are allowing to update both fields? Usually user sign up using their email. Define your criteria which field(username or email) you want to kept unchanged.
Update
Also you are doing one more thing wrong in your save function. You are updating the email, username, firstname and lastname in user taken from instance.user. But that instance overwritten when profile form default save is call here profile = super(ProfileForm, self).save(*args,**kwargs). What you should do is to update those fields using the user = profile.user The profile which is returned by the super. Your save function should be looked like this:
def save(self, *args, **kwargs):
"""
Update the primary email address on the related User object as well.
"""
profile = super(ProfileForm, self).save(*args,**kwargs)
u = profile.user
u.email = self.cleaned_data['email']
u.username = self.cleaned_data['display_name']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
u.save()
#profile.save() #if commit=False in kwargs
return profile