Django: Check which related object exists - python

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

Related

How can you add to a ManyToManyField extension of the default Django User model?

For my app, I want to add one extra ManyToManyField to the default User model (django.contrib.auth.models.User). This extra field is called 'favorites' and the posts favorited by a user should go there. This is what I have:
class Favorite(models.Model):
user = models.OneToOneField(User, related_name='favorites', on_delete=models.CASCADE)
favorites = models.ManyToManyField(Recipe, related_name='favorited_by')
This is what I get when trying to add to 'favorites' from the shell.
# imported Recipe, Favorite, User(default)
>>> recipe1 = Recipe.objects.all()[0]
>>> me = User.objects.all()[0]
>>> me.favorites.add(recipe1)
django.contrib.auth.models.User.favorites.RelatedObjectDoesNotExist: User has no favorites.
# Just checking if the the User object, me, has a 'favorites' attribute
>>> 'favorites' in dir(me)
True
What is the correct way to add a Recipe object to this 'favorites' field?
For more reference, I did something similar how I handled Friendships between users, but it was a bit simpler since I wasn't extending the User model. The code for that is below and works fine:
class Friend(models.Model):
users = models.ManyToManyField(User)
current_user = models.ForeignKey(User, related_name='owner', null=True, on_delete=models.CASCADE)
#classmethod
def make_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.add(new_friend)
#classmethod
def lose_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.remove(new_friend)
Resolved. My solution is below, but I'm not sure if this is good practice.
django.contrib.auth.models.User.favorites.RelatedObjectDoesNotExist: User has no favorites.
The User model may have the 'favorites' field, but I needed to actually fill it with a 'Favorite' object. I did this by writing a function in my views.py:
def add_favorite(request, pk):
# Check if the user has a favorites field. If not create one and add. If yes, just add
user_favorites, created = Favorite.objects.get_or_create(
user=request.user
)
recipe = get_object_or_404(Recipe, pk=pk)
user_favorites.favorites.add(recipe)
This seems to work and I can access a user's favorites now, but I maybe this isn't good practice. With my method, new models that are created do not have a 'Favorite' object within it. That will only get created when a user decides to add a favorite recipe and the above view will create one if it doesn't already exist.

Django Model matching query does not exist on newly created instances

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()

Validate count() constraint in many to many relation before saving an instance in django

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

How to check if user is in a certain table

I have my user table in django, and to differ all the users I created two tables, (Teacher and Student).
Both tables are getting an fk from user
So, in order to make authorization how do I check if one's user is in a certain table.
I need to check it this way
def test_func(self):
return self.request.user.check..if..it..exists..in..table
My models are like this.
class Teacher(models.Model):
User = models.OneToOneField(settings.AUTH_USER_MODEL)
This depends on how your models are set up.
If your Teacher model looks something like this;
class Teacher(models.Model):
user = models.ForeignKey(User)
Then you should be able to check if the user is a teacher by using the implicit backref;
self.request.user.teacher_set.exists()
As the question has been updated to show that the model is slightly different than I anticipated, here is an update.
class Teacher(models.Model):
user = models.OneToOneField(User)
Which means that the backref will be a little different.
hasattr(self.request.user, "teacher")
As you've mentioned that you are doing this inside a django template, I'm pretty sure that the following will work:
{% if user.teacher %}
Since you haven't posted your models, I am giving you a rough idea how to do it.
in your views.py -
from .models import Teacher,Student
def test_func(request):
user = request.user
if (Teacher.objects.filter(user=user).count() > 0) or (Student.objects.filter(user=user).count > 0):
#do your stuffs here..
One way is to query both tables:
teacher = Teacher.objects.filter(user=self.request.user)
student = Student.objects.filter(user=self.request.user)
if teacher or student:
# do what you want.
If you put in your relation the argument "related_name" you can do it using inverse relationship
class SomeTable(models.Model):
user = models.ForeignKey(
User, #Your user model or Django one
verbose_name = "User",
related_name = "inverse_relation_name"
)
Then you have to call using keyword arguments for the filters:
SomeTable.inverse_relation_name.filter(id=self.request.user.id) #You will get a queryset
Or
SomeTable.inverse_relation_name.get(id=self.request.user.id) # You will get the object or a exception

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Categories

Resources