Django Model.objects.all() queryset not distinct - python

I have the following Django 3.0 models:
class Profile(models.Model):
id = ShortUUIDField(primary_key=True, unique=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Bounty(models.Model):
id = ShortUUIDField(primary_key=True, unique=True)
creator = models.ForeignKey('Profile', related_name="created_bounties", on_delete=models.SET_NULL, null=True, blank=True)
original_completion = models.OneToOneField('ProfileTrophy', related_name="original_bounty",on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=50)
class ProfileTrophy(models.Model):
id = ShortUUIDField(primary_key=True, unique=True)
profile = models.ForeignKey('Profile', related_name="bounty_completions", on_delete=models.CASCADE)
bounty = models.ForeignKey('Bounty', related_name="bounty_completions", on_delete=models.CASCADE)
name = models.CharField(max_length=50, null=True, blank=True)
So the premise is that there are profiles, and bountys. Profiles can create bountys (stored as creators on the bounty). Profiles can complete a bounty, which is stored as a ProfileTrophy (trophy for completing the bounty) and they can complete bountys they created or bountys created by others (bounties can have many completions, but the original by the creator is stored in original_completion).
The problem I'm running into is that if I have two Profiles, Bob and Jim, and Bob creates a bounty called "Bounty 1" and completes it everything is fine. If Jim then completes "Bounty 1" everything works fine, except when I call either Bounty.objects.all() or Bob.created_bounties.all() I get <QuerySet [<Bounty: Bounty 1>, <Bounty: Bounty 1>]> and queryset[0]==queryset[1].
The database shows only one bounty, and everything looks as it should. If I look at the queryset SQL of Bounty.objects.all() I see
SELECT "core_bounty"."id" FROM "core_bounty" LEFT OUTER JOIN "core_profiletrophy" ON ("core_bounty"."id" = "core_profiletrophy"."bounty_id")
Which if I'm reading right the left outer join is the problem because it will match both ProfileTrophys and so return the bounty twice.
Any help on why this duplication is happening in the queryset and what I'm doing wrong?
Edit: I should add that everything within the app works fine, I only noticed because the Bounty object shows up twice in the admin dashboard and it really bothered me.
Edit 2: Removing creator and original_completion from the Bounty model has no effect on the problem

Ok so it turns out it was related to something I left out, the Meta ordering was set to ["bounty_completions"] which was causing the ordering to need to call in the left join.

Related

How to add a many-to-many relationship after save in django?

I'm trying to build a simple ToDo app in django:
A 'box' contains multiple 'tasks', has an 'owner' (the person who created the box and has administrative privileges) and multiple 'users' (persons who can access the box and add/edit tasks).
I want to automatically add the owner to the users set. I've tried this multiple ways:
Overwriting the save():
class Box(models.Model):
owner = models.ForeignKey(User, related_name='boxes_owned', on_delete=models.CASCADE, null=True, blank=False)
users = models.ManyToManyField(User, related_name='boxes_assigned')
title = models.CharField(max_length=50)
description = models.TextField(null=True, blank=True)
def save(self):
super().save()
self.users.add(self.owner)
This does not work and neither does working with a post-save signal.
#receiver(post_save, sender=Box)
def update_box(sender, instance, **kwargs):
instance.users.add(instance.owner)
The owner won't be added to the users set.
What am I doing wrong, how can I fix this?
Is this the right approach at all?
EDIT:
It seems like the owner is added correctly to users by the save function as well as by the update_box function. At least that is what the debugger tells me. However, when I try to edit the box afterwards the owner is not in the users array.
Are there any events after post_save that could mess with my relationships?

Django ORM relations one-to-many

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.

django 2 - Nested query based off multiple models

Update: See bottom.
I'm trying to wrap my head around how to achieve a nested/chained query based on my needs. There might be a better way to get the results I need so please let me know.
Trying to get the authenticated user and get a list of friends, which I have working and I get a queryset object of friends. I would like to pass the queryset object of friends into another query that searches the Post model, matches the username found Friend.users to the Post.creator so I get back another queryset which will have all the Posts of all my friends which I can display in the template.
class Friend(models.Model):
users = models.ManyToManyField(User, blank=True)
owner = models.ForeignKey(User, related_name='owner_friend', on_delete=models.CASCADE, null=True, blank=True)
class Post(models.Model):
creator = models.ForeignKey(User, on_delete=None, null=True)
EDIT:
I'm trying to do this with my code
f = Friend.objects.all().filter(owner__username='admin').filter(users__username='jeff')
output
<QuerySet [<Friend: Friend object (1)>]>
p = Post.objects.all().filter(creator__username__in=f)
ValueError: Cannot use QuerySet for "Friend": Use a QuerySet for "User".
Thanks for the help.
Update 1:
I've changed my models to use ForeignKeys instead. I've been using the shell to test and I'm getting an unexpected result, see below.
class Friend(models.Model):
users = models.ForeignKey(User, related_name='user_friend', on_delete=models.CASCADE, null=True, blank=True)
owner = models.ForeignKey(User, related_name='owner_friend', on_delete=models.CASCADE, null=True, blank=True)
f = Friend.objects.filter(owner__username='admin').values_list('users__username', flat=True)
Output
<QuerySet ['jeff', 'sam']>
Post.objects.filter(creator__username__in=list(f))
Output
<QuerySet []>
If I put in the list manually
Post.objects.filter(creator__username__in=['admin', 'jeff'])
Output
<QuerySet [<Post: Post Title 1>]>
I think if I can get the __in=list(f) to work this should fix the issue.
Update 2
list(f) is actually working, I didn't have a Post linked for the user accounts for the friends being pulled through. Once I created the post for the friends, I'm now getting a queryset.
I'm not sure what I'm doing is the best way but it's working.
For future readers I hope this helps.
The Friend model is a bit strange and is causing you some difficulty. Really, Friend should be the through table in the many-to-many relationship between User and itself. Presumably you are using the built-in User model, which is why you have used an external model; but you can still simulate a through table with two foreign keys:
class Friend(models.Model):
from_user = models.ForeignKey(User, related_name='users_from', on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name='users_to', on_delete=models.CASCADE)
Now your query can be:
Post.objects.filter(creator__users_to__from_user__username='admin')
that is, give me all Posts whose creator is on the "to_user" side of the friend relationship where the from_user's username is "admin".

Django - get_or_create() with auto_now=True

I’m using Django and I'm having a problem with a Python script that uses Django models
The script that I'm using takes data from an api and loads it into my database.
my model:
class Movie(models.Model):
title = models.CharField(max_length=511)
tmdb_id = models.IntegerField(null=True, blank=True)
release = models.DateField(null=True, blank=True)
poster = models.TextField(max_length=500, null=True)
runtime = models.IntegerField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
edit = models.DateTimeField(auto_now=True, null=True, blank=True)
backdrop = models.TextField(max_length=500, null=True, blank=True)
popularity = models.TextField(null=True, blank=True)
the script:
movies = tmdb.Movies().upcoming()
results = movies['results']
ids = []
for movie in results:
data, created = Movie.objects.get_or_create(title=movie['title'],
tmdb_id=movie['id'],
release=movie['release_date'],
description=movie['overview'],
backdrop=movie['backdrop_path'],
poster=movie['poster_path'],
popularity=movie['popularity'])
The problem I'm having is that whenever I run the script, the entries are duplicated because the edit field is change, but the purpose I put the edit field is to know when exactly a movie got edited, ie: some other field got changed.
How can I avoid the duplicates, but also keep the edit field in case some real change happened?
but the purpose I put the edit field is to know when exactly a movie
got edited, ie: some other field got changed.
That probably means you are using the wrong function. You should be using update_or_create istead.
A convenience method for updating an object with the given kwargs,
creating a new one if necessary. The defaults is a dictionary of
(field, value) pairs used to update the object.
This is different from get_or_create, which creates an object if it does not exists, or simply fetches it when it does exist. update_or_create is the one that does the actually updating.
However, changing to this method doesn't solve this:
How can I avoid the duplicates, but also keep the edit field in case
some real change happened?
Duplicates are created because you do not have a unique index on any of your fields. Both get_or_create and update_or_create require that you have a unique field. It seems that the following change is in order:
class Movie(models.Model):
title = models.CharField(max_length=511)
tmdb_id = models.IntegerField(unique=True)

Counting and filtering objects in a database with Django

I'm struggling a little to work out how to follow the relation and count fields of objects.
In my Django site, I have a profile model:
user = models.ForeignKey(User, unique=True)
name = models.CharField(_('name'), null=True, blank=True)
about = models.TextField(_('about'), null=True, blank=True)
location = models.CharField(_('location'), null=True, blank=True)
website = models.URLField(_('website'), null=True, blank=True)
My understanding is that this is using the username as the foreign key.
I would like to be able to count and display the number of completed profiles my users have filled out, and ones that have a specific "element / field"? (name)* filled out. I tried:
Profile.objects.all().count()
That gave me the same number of profiles as users, which I am guessing is because the profile model exists for each user, even if it is blank.
I'm unsure how to count profiles that have one of these fields completed in them, and I am also unsure how to count the number of completed "name" fields that have been completed.
I tried:
Profile.objects.all().name.count()
Django has some good docs on queryset api, but its currently going a little over my head
please excuse my use of incorrect terminology.
You should be able to get them using:
Profile.objects.filter(name__isnull=False)

Categories

Resources