Django ORM relations one-to-many - python

I have a model:
class Product(models.Model):
title = models.CharField(max_length=200)
url = models.URLField()
pub_date = models.DateTimeField()
votes_total = models.IntegerField(default=1)
image = models.ImageField(upload_to='images/')
icon = models.ImageField(upload_to='images/')
body = models.TextField()
hunter = models.ForeignKey(User, on_delete=models.CASCADE)
Now I'd like to add a functionality of upvoters to know on what products user has already voted. I need this to allow users vote on the one product only once.
Again, to clarify - user can vote on several products but only once on each.
So the relation is one product - many users (upvoters).
I tried to add the next field but cannot make a migration even if default field is provided. Also I tried to clear the database but again cannot make it work.
upvoters = models.ForeignKey(User, on_delete=models.CASCADE, related_name='upvoted')
I suppose it works the next way:
Field to determine upvoted products.
To check if user has been upvoted on product, call: User.upvoted.filter(id=product.id).count() == 1
This means that user has already upvoted on this product.
What's wrong? What should I change to make it work?

You will have to use ManyToMany, but you can use a custom through model to restrict the product/vote combinations.
To Product class, add:
voters = models.ManyToManyField(User, through='ProductVote', related_name='product_voters')
Then add the custom through model:
class ProductVote(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
product = models.ForeignKey(Vote, on_delete=models.CASCADE)
class Meta:
unique_together = ['user', 'product']
If you try to add a vote for the same user/product combination, an IntegrityError will be raised.

Related

How can i perform the right reverse query in ManytoMany in Django

I'm trying to perform a reversed query for a manytomany fields in Django, but it keeps gives me nothing, here is my code
models.py
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=120)
image = models.ImageField(upload_to='products')
branch = models.ManyToManyField(Branch, related_name='branches')
class Branch(models.Model):
area = models.ForeignKey(Area, on_delete=CASCADE)
name = models.CharField(max_length=1200)
phone = models.CharField(max_length=30, null=True, blank=True)
address = models.CharField(max_length=1200, null=True, blank=True)
tax_value = models.DecimalField(decimal_places=2, max_digits=4)
views.py
for branch in product_object.branches.all():
print(branch)
The branch is always nothing !!
For some reason, the related name is not calling it anymore. I called it using the model name (lower cased).
This is how it worked
for branch in product_object.branch.all():
Just to complete your answer above, I think the way you have your model set up is a little misleading and confusing. I think this would improve clarity:
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=120)
image = models.ImageField(upload_to='products')
branches = models.ManyToManyField(Branch, related_name='products')
Since you have a many to many field, a product can have multiple branches, the attribute name should reflect that
When you use the related_name, this would be if you are going from the m2m object. For example, if you have a branch, you could get all it's products by doing branch.products

Get site-specific user profile fields from user-created object

I am using Django sites framework (Django 2.1) to split an app into multiple sites. All of my models except the User model are site-specific. Here is my Post model:
post.py
class Post(models.Model):
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='children',
related_query_name='child',
blank=True,
null=True,
)
title = models.CharField(
max_length=255,
blank=True,
)
body_raw = models.TextField()
body_html = models.TextField(blank=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
I have no problem separating posts out by site. When I want to get the posts, I call:
posts = Post.on_site.filter(...)
I have a separate model called UserProfile. It is a many-to-one profile where there is a unique profile created for each user-site combination (similar to profile implementation at SE). The profile has a reputation attribute that I want to access when I get any post. This reputation attribute should be different for each site (like how on SE you have different rep on each site you are a member of).
user_profile.py
class UserProfile(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
reputation = models.PositiveIntegerField(default=1)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
How do I access the user's username (on the User model) as well as the user's reputation (on the UserProfile model) when I get Posts from a query?
I'd like to do something like:
Post.on_site.filter(...)
.select_related('user__userprofile')
.filter_related(user__userprofile.site == get_current_site())
How do I filter a Many-To-One related model?
Better to make UserProfile -> User relationship to be OnetoOne,
because Django doesn't know which of many profiles to show
(but you also need to define related_name)
models.OneToOneField(get_user_model(), related_name='userprofile_rev')
Then you will be able to do this
qs = Post.on_site.filer().select_related('user', 'user__userprofile_rev')
for post in qs:
print(post.user.username, post.user.userprofile_rev.reputation)
If you don't want to change your DB structure you can do like this
(but you need to specify which profile to return)
qs = Post.on_site.filer().select_related('user').prefetch_related('user__userprofile_set')
for post in qs:
print(post.user.username, post.user.userprofile_set[0].reputation)

Users as foreign key in Django

I have the below in my models.py file:
class Film(models.Model):
title = models.CharField(max_length=200)
director = models.CharField(max_length=200)
description = models.CharField(max_length=200)
pub_date = models.DateField('date published')
class Comment(models.Model):
film = models.ForeignKey(Film, on_delete=models.CASCADE)
body = models.CharField(max_length=200)
When I logged into Django admin I added some films, and then added some comments, selecting which film object the comment related to. I then created a couple of users via the admin panel also.
I would like my relationships to be:
Film can have many comments / Comments belong to film
User can have many comments / Comments belong to user
I think, like with comments and films, I just need to define user as a foreign key to comment. I am struggling to do this. I am working through the Django tutorials but I can't see the tutorials covering how I can link other tables to the user.
I thought I would be able to do something like this:
user = models.ForeignKey(User, on_delete=models.CASCADE)
While importing User like this:
from django.contrib.auth.models import User
The result at the moment is if I keep user = models.ForeignKey(User, on_delete=models.CASCADE) I get err_connection_refused
Maybe have you changed your default user model in the settings?
Instead of using User directly with the the Foreign key, you should use user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) in your Comment Model, as follow
class Comment(models.Model):
film = models.ForeignKey(Film, on_delete=models.CASCADE)
body = models.CharField(max_length=200)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
You need to apply migrations to be able to add user to Comment,
python manage.py makemigrations
python manage.py migrate
if at the moment that you are applying migrations, shell shows a message telling You are trying to add a non-nullable field 'user' to comment without a default
You have 2 Options
Skip migrations and add a default value to the field in the models or set the attribute as nullable, whatever else that you need
ie
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)
and apply migrations again
Or select a default value to the new field, should be an id of an existing user in databse
This is because django should populate existing records in database, if exist
Use "settings.AUTH_USER_MODEL".
So, import "settings" from "django.conf", then use "settings.AUTH_USER_MODEL" as shown below:
from django.db import models
from django.conf import settings # Here
class Comment(models.Model):
film = models.ForeignKey(Film, on_delete=models.CASCADE)
body = models.CharField(max_length=200)
# Here
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Django Models Relationship Confusions

I have the following models:
class UserPost(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
class User(AbstractUser):
MALE = 'M'
FEMALE = 'F'
GENDER_CHOICES = (
(MALE, 'Male'),
(FEMALE, 'Female')
)
posts = models.ManyToManyField(Post, through='UserPost')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
status = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
When I run python manage.py makemigrations, it raises the following error:
users.User.posts: (fields.E303) Reverse query name for 'User.posts' clashes with field name 'Post.user'.
HINT: Rename field 'Post.user', or add/change a related_name argument to the definition for field 'User.posts'.
There is a many-to-many relationship between User and Post models. Each user can like many posts and each post can be liked by many users.
There is also a many-to-one relationship between User and Post models. Each user can write many posts and each post can be written by only one user.
Shouldn't reverse query name for 'User.posts' be user_set by default. If so, why is this name clashing with field name 'Post.user'? Can someone explain the meaning of this error? Thanks.
Do you need the UserPost model? It looks to have all the same fields as Post, and if you're after efficient querying, Django automatically creates database indexes on foreign keys. Here's a simple setup that should work pretty well:
class User(AbstractUser):
# Your fields go here, but you might not need the posts field
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='posts')
This would let you do a user.posts.all() to get all of the Post instances that belong to that user.

Correct Django models relationship

Preface: I have two models (Product and UserProfile) and I would to implement a mine comments system. The comment is related to an object, Product or UserProfile.
class Product(models.Model):
name = models.CharField(max_length = 40)
comments = models.ManyToMany(Comment)
class UserProfile(models.Model):
user = models.ForeignKey(User, unique = True)
comments = models.ManyToMany(Comment)
class Comment(models.Model):
text = models.TextField()
author = models.ForeignKey(User)
timestamp = models.DateTimeField(auto_now_add = True)
Is it correct the logic under these models? I'm doubtful, because this way means a Product can has many comments (and it's correct) but also a Comment can has many products (I don't think it's correct).
It isn't?
Your comment should have have a ForeignKey to the UserProfile or Product i.e. A single comment can only belong to a single product or user profile, but a user profile/product can have many different comments
def Comment(models.Model):
profile = models.ForeignKey(UserProfile)
product = models.ForeignKey(Profile)
Obviously this isn't ideal as there are two relationships you need to manage, and sometimes you will only want to use one etc.
To get over this, you can use a Generic Foreign Key:
https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#generic-relations
this allows you to link a comment to any type of content (products, user profiles and more) without having to specify the models up front
def Comment(models.Model):
...
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')

Categories

Resources