Django objects order_by give me duplicates users - python

I am trying to get all users (excepted request.user) and order them by datetime of last message they received.
Maybe I am doing it wrong.
#login_required
def get_users(request):
if request.method == 'POST':
users = list(User.objects.filter(~Q(username = request.user))
.order_by('personal_messages__sent_date').values())
return HttpResponse(dumps({'users': users}))
return redirect('message:index')
dumpsis from json_tricks.
Data are received by a Vue.js object with JS fetch
My models.py
from django.db import models
from django.conf import settings
class PersonalMessage(models.Model):
text = models.TextField()
sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='personal_messages', null=True, on_delete=models.SET_NULL)
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
sent_date = models.DateTimeField('sent date', auto_now_add=True)
The thing is if I only do users = list(User.objects.filter(~Q(username = request.user)).values()) it works well but if I add the order_by users = list(User.objects.filter(~Q(username = request.user)) .order_by('personal_messages__sent_date').values()) I get duplicates for each user. Seems it returns each user n times if user is linked with n messages.
Maybe there is another way.
Any Idea?

You need to use aggregation and the query looks like this:
User.objects.filter(
~Q(username = request.user)
).annotate(
last_message_sent_date=Max('personal_messages__sent_date')
).order_by(
'last_message_sent_date'
)

Related

Django: how to use .filter( ) method in django?

I am trying to display quiz only for users that are registered in a particular course, i.e if a user is registered in a Frontend Crash Course i want them to see only the quiz related to that course they are registered in, and not all the quiz from the db.
i have a model UserCourse where i am storing all the courses a user have enrolled in, when i try filtering by that models while user_course is get like this below
user_course = UserCourse.objects.get(user=request.user)
quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
i get this error get() returned more than one UserCourse -- it returned 3! Now i have changed .get() to .filter() like this
user_course = UserCourse.objects.filter(user=request.user)
quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
i then get this error The QuerySet value for an exact lookup must be limited to one result using slicing.
What is the right way to write this query.
models.py
class UserCourse(models.Model):
user = models.ForeignKey(User , null = False , on_delete=models.CASCADE)
course = models.ForeignKey(Course , null = False , on_delete=models.CASCADE, related_name="usercourse")
class Quiz(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quizzes")
title = models.CharField(max_length=255)
course = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True, related_name="quizzes")
date = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(unique=True)
user_course = models.ForeignKey(UserCourse, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
The Problem in the Second Line
user_course = UserCourse.objects.filter(user=request.user)
quizzes=Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
remember that when You are using filter you get QuerySet not one object
if you want to return the quizes those related to user_course_queryset you can use __in filter
print(user_course) # print it to understand more
quizzes=Quiz.objects.filter(course__usercourse__in=user_course)
this will Return every Quiz Related to the QuerySet objects

Django - Query for posts along with it's number of replies?

So I'm trying to populate a news feed with posts of a certain category. Then I want to be able to display the number of replies on the post. The reason I'm confused on how to do this, is because I filtered through goals by category then I filtered all the posts associated with all those goals and now I have a list of a dictionary containing the post body and goal description and create date, but I'd like to also add in the number of replies and I'm not sure how to do this in an efficient way. Also I realize my way of doing things in the view.py is less than ideal so any suggestions would be great!
Model.py
class Post(AbstractBaseModel):
creator_id = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="post_creator_id")
goal_id = models.ForeignKey(Goal, on_delete=models.CASCADE)
body = models.CharField(max_length=511, validators=[MinLengthValidator(5)])
hash_tags = models.ManyToManyField(HashTag)
class ReplyPost(AbstractBaseModel):
creator_id = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="reply")
post_id = models.ForeignKey(Post, on_delete=models.CASCADE)
body = models.CharField(max_length=250)
View.py
#api_view(['GET'])
def get_most_recent_posts_by_category(request, category, count):
goals_list = list(Goal.objects.filter(category = category).values_list('uuid', flat=True))
data = list(Post.objects.filter(goal_id__in=goals_list).order_by('created').values('body', 'goal_id__description', 'created'))
data['goal_description'] = data['goal_id__description']
data['post_body'] = data['body']
del data['goal_description']
del data['body']
return JsonResponse(data, status=status.HTTP_200_OK)
You can use Count('replypost') to add the number of items:
from django.db.models import Count
data = list(
Post.objects.filter(
goal_id__in=goals_list
).order_by(
'created'
).values(
'body', 'goal_id__description', 'created', replies=Count('replypost')
)
)

Neither set() nor add() django add to database entry

I have the following problem: I have a manytomanyfield (in model Toppings) and I can't populate it. I first tried using a list and set() and then I tried using just one object and add() in views.py but neither will return anything else than none. I have been looking at documentation and other forum questions but I just can't figure it out.
Any help is greatly appreciated!
views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.core import serializers
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import render
from django.urls import reverse
from orders.models import Meal, Topping, Order
def order(request):
# Request should be ajax and method should be POST.
if request.is_ajax and request.method == "POST":
# Get IDs of meal, topping(s) and user
idmeal = request.POST["idmeal"]
idmeal = Meal.objects.get(pk = idmeal)
# Topping[] is a list of numbers representing IDs for Topping database
topping = request.POST.getlist('topping[]')
for i in range(0, len(topping)):
topping[i] = int(topping[i])
user = request.user
userID = User.objects.get(username=user.username)
topping = Topping.objects.filter(pk__in=topping)
print(topping)
# Create object in Order table
order = Order.objects.create(customerID = userID, mealID = idmeal, price = 12, status = "pending")
# Add values to ManyToManyField
order.toppingsID.set(topping)
print(order.toppingsID)
return JsonResponse({"success": ""}, status=200)
else:
# some error occured
return JsonResponse({"error": ""}, status=400)
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Meal(models.Model):
meal = models.CharField(max_length=64)
classname = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return f"{self.meal} ({self.classname}) ({self.price}) (id: {self.id})"
# Create your models here.
class Topping(models.Model):
topping = models.CharField(max_length=64)
classname = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
def __str__(self):
return f"{self.topping} ({self.classname}) ({self.price}) (id: {self.id})"
class Order(models.Model):
customerID = models.ForeignKey(User, on_delete=models.CASCADE)
mealID = models.ForeignKey(Meal, on_delete=models.CASCADE)
toppingsID = models.ManyToManyField(Topping, blank=True)
price = models.DecimalField(max_digits=5, decimal_places=2)
status = models.CharField(max_length=64)
def __str__(self):
return f"{self.customerID} has ordered {self.mealID} with {self.toppingsID} which costs {self.price} (status : {self.status})"
The problem is not setting or adding to your field. The problem is printing your field.
In order to print the members of a ManyToManyField, you need to all .all() (or .filter() or any other function you add to a manager), like:
print(order.toppingsID.all())
If you print a manager, it will indeed print ModelName.None. For example Meal.objects will do the same. It is by using Meal.objects.all() that you construct a QuerySet.

Modify value in data table UserProfile using function

I am trying to run .save() to change the value of a user model field.
Here is my code:
Views.py:
def traffic_task(request):
tasks_traffic = Task.objects.filter(category="traffic")
random_task = random.choice(tasks_traffic)
task_id = random_task.pk
user = request.user
user.userprofile.daily_task = task_id
user.save()
return task_detail(request=request, pk=task_id)
Models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
daily_task = models.IntegerField(default=0)
daily_task_done = models.BooleanField(default=False)
daily_task_done_time = models.DateTimeField(default=datetime.now() - timedelta(days=2))
They are in two different apps so maybe it's an import missing?
You should save the UserProfile object, not the User object, so something like:
def traffic_task(request):
tasks_traffic = Task.objects.filter(category="traffic")
random_task = random.choice(tasks_traffic)
task_id = random_task.pk
userprofile = request.user.userprofile
userprofile.daily_task = task_id
# perhaps you want to set daily_task_done to False here
userprofile.save()
return task_detail(request=request, pk=task_id)
Furthermore based on the code you provide, it looks like you want to add a ForeignKey to Task, it is better not to save the value of the primary key, since the FOREIGN KEY constraints, etc. are not enforced:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
daily_task = models.ForeignKey('someapp.Task', null=True, default=None)
daily_task_done = models.BooleanField(default=False)
daily_task_done_time = models.DateTimeField(default=datetime.now() - timedelta(days=2))
Then you can use a Task object, like:
def traffic_task(request):
tasks_traffic = Task.objects.filter(category="traffic")
random_task = random.choice(tasks_traffic)
userprofile = request.user.userprofile
userprofile.daily_task = random_task
userprofile.save()
return task_detail(request=request, pk=task_id)
This thus creates extra validation, but it is also more convenient to work with the Task object, and in case you want to obtain the Tasks "in bulk", one can use .select_related(..), or .prefetch_related(..) (although one can do this with an IntegerField as well, it will require extra logic, and thus is less elegant).

Checking if relationship exists with query

I am trying to check whether or not a following relationship exists using a query. First, I get all of the followers the user has and then I check whether or not the user follows those followers. Here are my models:
class Following(models.Model):
target = models.ForeignKey('User', related_name='followers', on_delete=models.CASCADE, null=True)
follower = models.ForeignKey('User', related_name='targets', on_delete=models.CASCADE, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{} is followed by {}'.format(self.target, self.follower)
class User(AbstractBaseUser):
username = models.CharField(max_length=15, unique=True)
email = models.EmailField(max_length=100, unique=True)
I am using the Django Rest-Framework so I go to the specific URL to get the information I need. After going to the URL, the output is expected. I get all the followers the user has.
views.py
class GetFollowersView(ListAPIView):
serializer_class = FollowingSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return User.objects.filter(targets__target=requested_user).order_by('-targets__created_at'). \
annotate(is_following=Count('followers__follower', filter=Q(followers__follower=requested_user), distinct=True))
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class FollowingSerializer(serializers.ModelSerializer):
is_following = serializers.IntegerField()
class Meta:
model = User
fields = ('id', 'username', 'follower_count', 'following_count', 'is_following')
However, the problem is in the is_following annotation. I'd like to see whether or not the user follows each specific follower. If they follow that follower, then is_following should be 1 if not, then it is a 0. I'm getting incorrect results in is_following is there a way I can check if the user follows each specific follower?
If you have Django Debug Toolbar installed and you check the query for your current filter/annotate, this is what it shows (for a single user)
SELECT "user"."id", "user"."username", "user"."email",
COUNT(DISTINCT T4."follower_id") AS "is_following" FROM "user"
INNER JOIN "following" ON ( "user"."id" = "following"."follower_id" )
LEFT OUTER JOIN "following" T4 ON ( "user"."id" = T4."target_id" )
WHERE "following"."target_id" = 4 GROUP BY "user"."id", "user"."username",
"user"."email", "following"."created_at" ORDER BY "following"."created_at"
DESC
However to get the count of the users the chosen user follows, you really want something like this
SELECT ue."id", ue."username", ue."email", COUNT(DISTINCT fe."target_id") AS
"is_following" FROM "user" u inner JOIN "following" fe ON ( u."id" =
fe."follower_id" ) inner join user ue on fe.target_id = ue.id and u.id = 4
GROUP BY ue."id", ue."username", ue."email"
I don't think it is possible to combine both the followers and the followee in the same query like you have done. You could possibly find the intersection and then proceed from there...Something like this..
def get_queryset(self):
username = self.request.query_params.get('username', None)
requested_user = models.User.objects.get(username=username)
following_me = models.User.objects.filter(targets__target=requested_user).order_by('-targets__created_at')
i_follow = models.User.objects.filter(followers__follower=requested_user).order_by('-followers__created_at')
common = following_me & i_follow
### Set is_following for common as 1, all others as 0.
#......
#......
return following_me
Why not use an M2M relationship? Seems like this could be simple:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=200)
followers = models.ManyToManyField('User')
#property
def follower_count(self):
# How many people follow me
return len(self.followers)
#property
def followee_count(self):
# How many people I follow
return len(self.user_set.all())
And you can modify the get_queryset() to only find followers:
User.objects.filter(followers__contains=self.request.user)
Does this help?

Categories