Django Query: How to order posts by amount of upvotes? - django

I'm currently working on a website (with Django), where people can write a story, which can be upvoted by themselves or by other people. Here are the classes for Profile, Story and Upvote:
class Profile(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
class Story(models.Model):
author = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="author")
title = models.CharField(max_length=50)
content = models.TextField(max_length=10000)
class Upvote(models.Model):
profile = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="upvoter")
story = models.ForeignKey('Story', on_delete=models.CASCADE, related_name="upvoted_story")
upvote_time = models.DateTimeField(auto_now=True)
As you can see, Upvote uses two foreign keys to store the upvoter and the related story. Now I want to make a query which gives me all the stories, sorted by the amount of upvotes they have. I've tried my best to come up with some queries myself, but it's not exactly what I'm searching for.
This one doesn't work at all, since it just gives me all the stories in the order they were created, for some reason. Also it contains duplicates, although I want them to be grouped by story.
hot_feed = Upvote.objects.annotate(upvote_count=Count('story')).order_by('-upvote_count')
This one kind of works. But if I'm trying to access a partical story in my template, it just gives me back the id. So I'm not able to fetch the title, author and content from that id, since it's just an integer, and not an object.
hot_feed = Upvote.objects.values('story').annotate(upvote_count=Count('story')).order_by('-upvote_count')
Could someone help me out with finding the query I'm searching for?

You are querying from the wrong model, you here basically fetch Upvotes ordered by the number of stories, or something similar.
But your probaby want to retrieve Storys by the number of upvotes, so you need to use Story as "queryset root", and annotate it with the number of upvotes:
Story.objects.annotate(
upvote_count=Count('upvoted_story')
).order_by('-upvote_count')
I think the related_name of your story is however a bit "misleading". The related_name is the name of the relation "in reverse", so probably a better name is upvotes:
class Upvote(models.Model):
profile = models.ForeignKey(
'accounts.Profile',
on_delete=models.CASCADE,
related_name='upvotes'
)
story = models.ForeignKey(
'Story',
on_delete=models.CASCADE,
related_name='upvotes'
)
upvote_time = models.DateTimeField(auto_now=True)
In that case the query is:
Story.objects.annotate(
upvote_count=Count('upvotes')
).order_by('-upvote_count')

Related

Get list of values from grandchildren records

I'm trying to access the grandchildren records in a list to avoid duplicate records. In this example, a tag can only be used once across articles for a given author. I will use the resulting list of grandchildren records in my clean function to return validation errors.
class Article(models.Model):
tag = models.ManyToManyField(Tag)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
class Tag(models.Model):
class Author(models.Model):
Right now I can do this:
print(author.articles.first().tag.first())
Travel
I'd like to be able to use something like author.articles.tags.all() to return the list and check the submitted form against it to raise a ValidationError message to the user.
How can this be done efficiently with the basic Many-to-Many setup without creating an intermediate table for the tag relationships? This is solely in the Admin interface, in case that matters at all.
i come from the link you posted on upwork,
the way i understand your question,
what you want to achieve seems to be impossible ,
what i think can work , is to fetch articles related to the author, with their corresponding tags,
after that they are retrieved you do filtering and remove duplicates.
otherwise the tag has nothing to connect it with the author,,
I'm so jealous that's a great answer #since9teen94
Be aware, I will not base my answer in the easiest solution but how we model reality (or how we should do it).
We put tags after things exists right?
In that order I will make your life horrible with something like this:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=30, null=False)
class Article(models.Model):
name = models.CharField(max_length=30, null=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
class Tag(models.Model):
name = models.CharField(max_length=30, null=False, unique=True)
articles = models.ManyToManyField(Article)
but believe me you don't want to follow this approach it will make your work harder.
You can search for Articles based on Tags directly
Tag.objects.filter(name='Scify').first().articles.all() # name is unique
The real issue with this is that the reverse lookup is really complex (I mean.. get ready for debug)
Article.objects.filter(
id__in=list(
Tag.objects.filter(name='Scify').first().articles.values_list('id', flat=True)
)
)
I am sure this does not solve your problem and I don't like complex code for no reason but if you're open to suggestions I don't mind to think different and add some options
Edit:
About the author and clean repeated tags.. well you don't have to deal with that and if you want to find all Tag your author has you could loop the tags
for tag in Tag.objects.all():
if tag.articles.filter(author__name='StackoverflowContributor'):
print(tag.name)
# > Scify
I am just saying that there are options not that this is the best for you but don't be afraid of the terminal, it's really cool
The Django ORM is pretty cool when you get used to it, but regular SQL is pretty cool too
# created_at, updated_at, name and tag_val are variables I
# added due to my slight ocd lol
class Author(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Tag(models.Model):
tag_val = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Article(models.Model):
author = models.ForeignKey(Author,related_name='articles', on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, related_name='articles')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I can write my query like this, assuming the variable 'author' has been assigned an Author object instance, and get a list of dictionaries [{'tags':1},{'tags':2}] where the value is the auto generated primary key id of the Tag object instance
author.articles.values('tags').distinct()

Editing dynamic choice fields in Django

I have a standard Django blog with a Post model, only on the model I have added a ManyToManyField for approvers, the idea being that the backend passes the post to 2 or more approvers to confirm the content before it is published.
class Post(models.Model):
author = models.ForeignKey(
get_user_model(), null=True, on_delete=models.SET_NULL)
title = models.CharField(max_length=30)
content = models.CharField(max_length=30)
approvers = models.ManyToManyField(Approvers)
I will probably learn towards something like django-fsm to create a finite state machine for the Post model to govern whether it is draft/in approval/published, but I would like to be able to change the approvers field so that the number and order of approvers (users) can be changed dynamically by the user.
What is the best way to do this? I thought I could try and change the approvers field to a JSONField so that users can add / delete / change the order of approvers and then handle the interpretation in the frontend and write some function to interface with django-fsm, but this feels like it conflates things too much. Am I missing a simpler route?
Why not make another model to do so like
class PostApprover(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='approvers')
user = models.ForeignKey(Approver, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
To access order in which post(let say with id 5) is approved (descending).you can do like
post = Post.objects.get(id=5)
post.approvers.order_by('-created_at')
you can change the value of created_at to change the order.
Or you can also make an integer field that determines your order

Python/Django recursion issue with saved querysets

I have user profiles that are each assigned a manager. I thought using recursion would be a good way to query every employee at every level under a particular manager. The goal is, if the CEO were to sign in, he should be able to query everyone at the company - but If I sign on I can only see people in my immediate team and the people below them, etc. until you get to the low level employees.
However when I run the following:
def team_training_list(request):
# pulls all training documents from training document model
user = request.user
manager_direct_team = Profile.objects.filter(manager=user)
query = Profile.objects.filter(first_name='fake')
trickle_team = manager_loop(manager_direct_team, query)
# manager_trickle_team = manager_direct_team | trickle_team
print(trickle_team)
def manager_loop(list, query):
for member in list:
user_instance = User.objects.get(username=member)
has_team = Profile.objects.filter(manager=user_instance)
if has_team:
query = query | has_team
manager_loop(has_team, query)
else:
continue
return query
It only returns the last query that was run instead of the compiled queryset that I am trying to grow. I've tried placing 'return' before 'manager_loop(has_team, query) in order save the values but it also kills the loop at the first non-manager employee instead of continuing to the next employee.
I'm new to django so if there is an better way than recursion to pull the information that I need, I'd appreciate suggestions on that too.
EDIT:
As requested, here is the profile model.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, blank=False)
last_name = models.CharField(max_length=30, blank=False)
email = models.EmailField( blank=True, help_text='Optional',)
receive_email_notifications = models.BooleanField(default=False)
mobile_number = models.CharField(
max_length=15,
blank=True,
help_text='Optional'
)
carrier_options = (
(None, ''),
('#txt.att.net', 'AT&T'),
('#messaging.sprintpcs.com', 'Sprint'),
('#tmomail.net', 'T-Mobile'),
('#vtext.com', 'Verizon'),
)
mobile_carrier = models.CharField(max_length=25, choices=carrier_options, blank=True,
help_text='Optional')
receive_sms_notifications = models.BooleanField(default=False)
job_title = models.ForeignKey(JobTitle, unique=False, null=True)
manager = models.ForeignKey(User, unique=False, blank=True, related_name='+', null=True)
Ok, so it's a hierarchical model.
The problem with your current approach is this line:
query = query | has_team
This reassigns the local name query to a new queryset, but does not reassign the name in the caller. (Well, that's what I think it's trying to do - I am a little rusty but I don't think you can just | together querysets like that.) You'd also need something like:
query = manager_loop(has_team, query)
to propagate the changes via the returned object.
That said, while Django doesn't have built-in support for recursive queries, there are some third party packages that do. Old answers eg (Django self-recursive foreignkey filter query for all childs and Creating efficient database queries for hierarchical models (django)) recommend django-mptt. Your tag mentions postgres, so this post might be relevant:
https://two-wrongs.com/fast-sql-for-inheritance-in-a-django-hierarchy
If you don't use a third-party approach, it should be possible to clean up the evolution of the queryset - cast it to a set and use update or something, since you're accumulating profiles. But the key error is not using the returned modified object.

Django - negative query in one-to-many relationship

Given these models:
class Profile(models.Model):
name = models.CharField(max_length=50)
class BlogPost(models.Model):
name = models.CharField(max_length=50)
created_by = models.ForeignKey(Profile, related_name='posts')
class Comment(models.Model):
blog = models.ForeignKey(BlogPost, related_name='comments')
body_text = models.TextField()
created_by = models.ForeignKey(Profile, null=True, blank=True, on_delete=models.SET_NULL)
Given a profile, I want to find all of the blog posts created by that profile, where there are either no comments, or only the creator of the post has commented.
For example:
profile = Profile.objects.get(id={id})
profile.posts.exclude(~Q(comments__created_by=profile))
I thought .exclude(~Q(comments__created_by=profile) would exclude all posts where a comment exists that has been created by someone other than the profile, but that's not working out. (It is finding posts where the created_by is null, and also posts where the profile has commented along with other users - which I'm trying to exclude from the set)
What you need is this:
comments_by_others_in_profile_posts = Comment.objects \
.filter(blog__created_by=profile) \
.exclude(created_by=profile)
profile.posts.exclude(comments=comments_by_others_in_profile_posts)
You can also try it like this (i believe this way it can be a little bit faster, but need to see the queries EXPLAIN output):
profile.posts.exclude(id__in=comments_by_others_in_profile_posts.values_list('blog', flat=True))
Well you were almost there, just need to include the conditions from your instincts. A good way to go about this is to use the django shell and a bunch of test data that matches your permutations. For more complex queries, its a good idea to write a unit test first.
profile.posts.filter(Q(comments__isnull=True)|~Q(comments__created_by=profile, comments__created_by__isnull=False))

Django ORM most occuring value

I have a model where basically i am Tracking users' activities. I want to know what is the page the user have accessed MOST.
Here are my modals.
class Visitor(models.Model):
session_key = models.CharField(max_length=40, primary_key=True)
user = models.ForeignKey(User, related_name='visit_history', null=True, editable=False)
....
class Pageview(models.Model):
visitor = models.ForeignKey(Visitor, related_name='pageviews')
url = models.CharField(max_length=500)
method = models.CharField(max_length=20, null=True)
view_time = models.DateTimeField()
Here is my query.
Pageview.objects.values('visitor__user__first_name', 'visitor__user__last_name', 'visitor__user').annotate(url_count=Count('url')).annotate(url_count_unique=Count('url', distinct=True))
Here i am getting users number of urls visited, and number of unique urls visited.
Here i also want to know which is the url user have visited the most?
EDIT
Translation of my query.
Goto PageViews and count the occurring of unique URLS.(how many times a url have occurred.) and give me the one that have most visited count against each user.
I hope the question is clear, if not let me know.
IMHO you're better off with a many-to-many relationship. You would have something like:
class VisitedURLs(models.Model):
page = models.ForeignKey(Visitor, ....)
user = models.ForeignKey(User, ....)
timestamp = models.DateTimeField(auto_now_add=True)
and the original models become something like:
class Visitor(models.Model):
members = models.ManyToManyField(PageView, through='VisitedURLs')
class PageView(models.Model):
url = models.CharField(max_length=500)
method = models.CharField(max_length=20, null=True)
In this case, you can use the count/distinct on the visitedURLs model and when you get an object of that type you'll have a FK to a Visitor object (which would give you the user...) and a FK to the URL.
Another way is to explicitly count each unique visitor/url combination and store it somewhere. Depending on usage (e.g. if you want to compute/display this often) you may be better off with the dedicated storage.
Here is the solution that i have come up with.
Pageview.objects.filter(visitor__user_id=user['visitor__user_id']).values(
'url').annotate(page_count=Count('id')).order_by('-page_count')
if max_visited_node:
user['max_visited_node'] = max_visited_node[0]
by this way i can get the count of the all the pages the user have visited. then i order them by that count and then i get the top first element which contains the URL and page_count.
This is what i was looking for. the suggestion of Laur lvan is worth considerable.