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

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

Related

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?

Django objects order_by give me duplicates users

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

How to get one field from model in django

I have such a model, in my django application. I want to draw only one field of this model and put them in the view. My solution below does not work:
obj = Text.objects.get(subsID)
My model
result = braintree.Subscription.create({
"payment_method_token": payment_method_token,
"plan_id": "67mm"
})
subscription_id = result.subscription.id
class Text(models.Model):
title = models.CharField(max_length=255)
text = models.TextField()
date_from = models.DateTimeField('date from', blank=True, null=True)
date_to = models.DateTimeField('date to', blank=True, null=True)
subsID = models.CharField(default=subscription_id, max_length=255)
def __unicode__(self):
return self.title
My view
def get_history(request):
subscription_id = Text.objects.filter(subsID)
history = braintree.Subscription.find(subscription_id)
return render(request, "sheet/history.html", {"history": history})
Generally, When filter or get, you have to put query inside it, like
subscription_id = Text.objects.filter(fieldname="searchterm")
This will return a queryset.So to view this
subscription_id.values() #returns a list of objects(dicts)
If you want to get only subsID
subscription_id.values("subsID")
This also return you list which contains
[{"subsID":"value"}, {"subsID":"value"} ....]
If you want to get only values
subscription_id.values_list("subsID", flat=True)
This will return like
["value", "value", ....]
You have to equal subsID to the value you want to find.
subscription_id = Text.objects.filter(subsID=<your subscrition id variable>)
pay attention this will return a list []
subscription_id = Text.objects.get(subsID=<your subscrition id variable>)
This will return an object
Link
You can't use the model in the view, you need to use the ModelForm or Form.
Once you use that one, you can specify which field is active or not or simply setting the attribute in the ModelForm,
exclude=['paramiter_name']
and it's done.
Good luck.

how to save Many to Many relationship in django

How to create an object for a Django model with a many to many field?
From above question i come to know we can save Many to Many field later only.
models.py
class Store(models.Model):
name = models.CharField(max_length=100)
class Foo(models.Model):
file = models.FileField(upload_to='')
store = models.ManyToManyField(Store, null=True, blank=True)
views.py
new_track.file = request.FILES['file']
new_track.save()
And file uploading working fine then later i modify my code to add store then i am here...
Now i am sure db return id's here. Then i tried with my below code but that's given me error only
x = new_track.id
new = Foo.objects.filter(id=x)
new.store.id = request.POST['store']
new.save()
ok so the error here is 'QuerySet' object has no attribute 'store'
And also i tried with add that's now working either.
So the question is how to save()
the right way of saving objects with manytomany relations would be:
...
new_track.file = request.FILES['file']
new_track.save()
new_store = Store.objects.get(id=int(request.POST['store']))
new_track.store.add(new_store)
As of 2020, here's my approach to saving ManyToMany Field to a given object.
Short Answer
class HostingRequestView(View):
def post(self, request, *args, **kwargs):
form = VideoGameForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.updated_by = request.user
obj.save()
selected_categories = form.cleaned_data.get('category') #returns list of all selected categories e.g. ['Sports','Adventure']
#Now saving the ManyToManyField, can only work after saving the form
for title in selected_categories:
category_obj = Category.objects.get(title=title) #get object by title i.e I declared unique for title under Category model
obj.category.add(category_obj) #now add each category object to the saved form object
return redirect('confirmation', id=obj.pk)
Full Answer
models.py
class Category(models.Model):
title = models.CharField(max_length=100, null=True, unique=True)
class VideoGame(models.Model):
game_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50, blank=False, null=False)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, on_delete=models.CASCADE)
category = models.ManyToManyField(Category) #ManyToMany Category field
date_added = models.DateTimeField(auto_now_add=True, verbose_name="date added")
forms.py ModelForm
class VideoGameForm(forms.ModelForm):
CATEGORIES = (
('Detective', 'Detective'),
('Sports', 'Sports'),
('Action', 'Action'),
('Adventure', 'Adventure'),
)
category = forms.MultipleChoiceField(choices=CATEGORIES, widget=forms.SelectMultiple())
class Meta:
model = VideoGame
fields = ['name', 'category', 'date_added']
views.py on POST
class HostingRequestView(View):
def post(self, request, *args, **kwargs):
form = VideoGameForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.updated_by = request.user
obj.save()
selected_categories = form.cleaned_data.get('category') #returns list of all selected categories e.g. ['Sports','Adventure']
#Now saving the ManyToManyField, can only work after saving the form
for title in selected_categories:
category_obj = Category.objects.get(title=title) #get object by title i.e I declared unique for title under Category model
obj.category.add(category_obj) #now add each category object to the saved form object
return redirect('confirmation', id=obj.pk)
URL path for redirect
urlpatterns = [
path('confirmation/<int:id>/', Confirmation.as_view(), name='confirmation'),
]
I hope this can be helpful. Regards
new.stores.all()
returns all stores linked to the object.
Maybe:
Change Foo to Tracks
Tracks.objects.filter(id=x) to Tracks.objects.get(id=x)
Let me know how it goes
why this confusion so much.. you are getting the id there then, call the store like
new_track.save()
new_track.store.add(request.POST['store'])

django query to use annotate with a filter for each annotations

I have the following database model -
class ObjectDetail(models.Model):
title = models.CharField()
img = models.ImageField()
description = models.TextField()
uploaded_by = models.ForeignKey(User, related_name='uploaded_by')
class Vote(models.Model):
vote_type = models.BooleanField(default = False)
voted_by = models.ForeignKey(User, related_name='voted_by')
voted_for = models.ForeignKey(User, related_name='voted_for')
shared_object = models.ForeignKey(ObjectDetail, null=True, blank=True)
dtobject = models.DateTimeField(auto_now_add=True)
Now, in my views I want to get the number of upvotes and downvotes for each of the objects.
One way of doing it would be to add a function under class ObjectDetails
as follows -
#property
def upvote(self):
upvote = Vote.objects.filter(shared_object__id = self.id,
vote_type = True).count()
return upvote
#property
def downvote(self):
downvote = Vote.objects.filter(shared_object__id = self.id,
vote_type = False).count()
return downvote
But this would, cause two queries for each of the object, present in the database.
Another method would be to use annotate
obj = ObjectDetail.objects.select_related().filter(FILTER_CONDITION).annotate(upvote=Count('vote'), downvote=Count('Vote')).order_by('-shared_time')
The above statement is wrong in a sense that it just gives me the counts of votes, irrespective of upvotes and downvotes.
if you see into the model, you can get upvote by filtering vote__vote_type = True and a downvote by vote__vote_type=False
How to add these two conditions/filters in the query statement?
So my prime objective is to get the two values of upvote and downvote for each of the items, with making least db queries, such that in the template, if i do
{{ obj.upvote }} I can get the number of upvote on the object, and the similar for downvote.
Please let me know, thanks.
Did you try with values() to group the different vote_types?
https://docs.djangoproject.com/en/dev/topics/db/aggregation/#values
Vote.objects.select_related().filter(FILTER_CONDITION).values('shared_object', 'vote_type').annotate(vote_count=Count('vote_type'))
At this point you can use regroup in the template to loop on ObjectDetailss
https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup

Categories