Fetch relations from ManyToMany Field using Annotation - python

I have my database here. Where I have 2 users connected to one instance of ChatRoomParticipants with a ManyToManyField.
I'm trying to get list of related users from a ManyToMany Relation Field from ChatRoomParticipants where I don't want to show the currently authenticated user in the list with other fields i.e room present in the model.
Considering user f4253fbd90d1471fb54180813b51d610 is currently logged in and is related to all ChatRooms via ChatRoomParticipants model.
Things I've tried but couldn't get the desired output
chatrooms = list(ChatRoomParticipants.objects.filter(user=user).values_list('user__chatroom_users__user__username', 'room').distinct())
#####
chatrooms = ChatRoomParticipants.objects.filter(user=user).annotate(user!=User(user))
####
chatrooms = Chatrooms.objects.filter(user=user)
rooms = chatrooms.values('user__chatroom_users__user__username').distinct().exclude(user__chatroom_users__user__username=user)
I want an output like
[
{
'user': '872952bb6c344e50b6fd7053dfa583de'
'room': 1
},
{
'user': '99ea24b12b8c400689702b4f25ea0f40'
'room': 2
},
{
'user': 'eecd66e748744b96bde07dd37d0b83b3'
'room': 3
},
]
models.py
class ChatRoom(models.Model):
name = models.CharField(max_length=256)
last_message = models.CharField(max_length=1024, null=True)
last_sent_user = models.ForeignKey(
User, on_delete=models.PROTECT, null=True)
def __str__(self):
return self.name
class Messages(models.Model):
room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.PROTECT)
content = models.CharField(max_length=1024)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.content
class ChatRoomParticipants(models.Model):
user = models.ManyToManyField(User, related_name='chatroom_users')
room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)

I think the database design is wrong. Especially, the user field in the ChatRoomParticipants model is defined as ManyToManyField but I think it should be just as ForeignKey because there needs to be the M2M relationship between User and ChatRoom, not between User and ChatRoomParticipants.
class ChatRoomParticipants(models.Model):
user = models.ForeignKey(User, related_name='chatroom_users', on_delete=models.PROTECT)
room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)
Then the filter function should work.
First you need to get the list of rooms that the current authenticated user is in:
room_ids = list(ChatRoomParticipants.objects.filter(user=user).values_list('room__id', flat=True))
And you get the roommates:
participants = ChatRoomParticipants.objects.exclude(user__id = user.id).filter(room__id__in = room_ids)

if you still want to keep your DB design then check the bellow link for query reference
[https://docs.djangoproject.com/en/4.0/topics/db/queries/#lookups-that-span-relationships]

You can do the query from the User model.
from django.db.models import F
User.objects.exclude(
username=user
).annotate(
room=F(
'chatroomparticipants__room'
)
).filter(room__isnull=False)
Also the != in your annotation you should consider look at the Q class
example,
With that you can do this negation.
from django.db.models import Q
User.objects.filter(
~Q(username=user)
)

Related

Django: Return values based on condition using SerializerMethodField

I have the tables: User, Event and EventTicket. I have to return total transactions and total tickets sold of each event. I am trying to achieve this using Django serializers. Using only serializers, I need to find the following:
1. total tickets sold: count of items in EventTicket table for each event
2. total transactions: sum of total_amount of items in EventTicket table for each event where payment_status: 1
models.py:
class User(AbstractUser):
first_name = models.CharField(max_length=50,blank=False)
middle_name = models.CharField(max_length=50,blank=True)
last_name = models.CharField(max_length=50,blank=True)
class Event(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
user = models.ForeignKey(User, related_name='%(class)s_owner', on_delete=models.CASCADE)
description = models.TextField(null=False, blank=False)
date_time = models.DateTimeField()
class EventTicket(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
event = models.ForeignKey(Event,on_delete=models.CASCADE, related_name='event_tickets')
payment_status = models.SmallIntegerField(default=0, null=False, blank=False) ## payment_success: 1, payment_failed: 0
total_amount = models.FloatField()
date_time = models.DateTimeField()
My desired output is:
"ticket_details": [
{
"event_id": 1,
"event_name": Event-1,
"total_transactions": 10000, ## Sum of all ticket amounts of event_id: 1, where payment_status: 1
"total_tickets": 24, ## Count of all tickets that belong to event_id: 1
},
{
"event_id": 2,
"event_name": Event-2,
"total_transactions": 10000, ## Sum of all ticket amounts of event_id: 2, where payment_status: 1
"total_tickets": 24, ## Count of all tickets that belong to event_id: 2
}]
This is what I have done:
serializers.py:
class EventListSerializer(serializers.ModelSerializer):
# ticket_id = serializers.IntegerField(source='id')
# event_id = serializers.PrimaryKeyRelatedField(source='event', read_only=True)
# event_name = serializers.PrimaryKeyRelatedField(source='event.name', read_only=True)
total_transactions = serializers.SerializerMethodField()
total_tickets = serializers.SerializerMethodField()
class Meta:
model = Event
fields = ('total_transactions', 'total_tickets')
def get_total_transactions(self, obj):
return obj.event_tickets.all().aggregate(sum('total_amount'))['total_amount__sum']
def get_total_tickets(self, obj):
return obj.event_tickets.all().count()
No matter whatever I try, I am always getting this error: 'EventTicket' object has no attribute 'event_tickets'
Firstly nice work on your question it is well-written! I don't see anything wrong with your code as it stands out, but the error message does say something that contradicts the code you've posted.
'EventTicket' object has no attribute 'event_tickets'
What this is basically saying is that in your serializer, although in the class Meta you've stated that model = Event for some reason the obj param in your serializer method is of type EventTicket, not of type Event.
It looks like you've set the correct related_name in your EventTicket model for the event field, nice.
This leads me to believe that your code is correct but the server seems to be running either an old version of the code, or is using a different serializer. Can we confirm that if you put a print statement in, the server actually prints it out in the serializer method?
The error says that obj is the instance of EventTicket. I guess.. at your view layer, you are calling EventListSerializer for EventTicket object. You have to call the serializer only for Event object.

Django REST - Incorrect type. Expected pk value, received str (pk is a CharField)

I have a ModelSerializer which I'm using to create new posts. It has a field book, of type PrimaryKeyRelatedField(queryset=Book.objects.all(), pk_field=serializers.CharField(max_length=255)).
When I post to this endpoint I get the error:
{
"book": ["Incorrect type. Expected pk value, received str."]
}
How can this be, as my Primary Key is a CharField.
The thing that really throws me off is, that I tried circumventing this with a SlugRelatedField, but when I do this, I get a really long and weird error:
DataError at /api/content/posts/
value "9780241470466" is out of range for type integer
LINE 1: ...020-05-22T20:14:17.615205+00:00'::timestamptz, 1, '978024147..., I don't understand it at all, as I am not setting an integer.
Serializer Code:
class PostCreationSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(queryset=Book.objects.all(), pk_field=serializers.CharField(max_length=255))
class Meta:
model = Post
fields = ['content', 'book', 'page', 'date_posted', 'user', 'id']
read_only_fields = ['date_posted', 'user', 'id']
Model Code:
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
pages = models.PositiveSmallIntegerField(null=True, blank=True)
image = models.URLField(null=True, blank=True)
date_published = models.CharField(max_length=4, null=True, blank=True)
publisher = models.CharField(max_length=140, null=True, blank=True)
isbn13 = models.CharField(max_length=13, primary_key=True)
objects = AutomaticISBNDBManager
def __str__(self):
return self.title
View Code:
class PostListCreate(UseAuthenticatedUserMixin, generics.ListCreateAPIView):
serializer_class = PostSerializer
queryset = Post.objects.order_by('-date_posted')
def get_serializer_class(self):
if self.request.method == 'POST':
return PostCreationSerializer
else:
return PostSerializer
Edit: The POST I'm sending:
{
"book": "9780241470466",
"content": "test",
"page": "10"
}
Note: the user and date_posted are set automatically.
You can use SlugRelatedField instead of PrimaryKeyRelatedField like that:
book = serializers.SlugRelatedField(
slug_field='isbn13',
queryset=Book.objects.all()
)
From the docs:
SlugRelatedField may be used to represent the target of the relationship using a field on the target.
For an alternative method, you can set book reference to your post model in serializer validate method:
1-) Replace PrimaryKeyRelatedField with CharField
2-) Find book object in your validate method and assign it to validated data.
class PostCreationSerializer(serializers.ModelSerializer):
book = serializers.CharField()
class Meta:
model = Post
fields = ['content', 'book', 'page', 'date_posted', 'user', 'id']
read_only_fields = ['date_posted', 'user', 'id']
def validate(self, attrs):
try:
attrs['book'] = Book.objects.get(isbn13=attrs['book'])
return attrs
except Book.DoesNotExist:
raise serializers.ValidationError("Book not found")
So, I don't really solved it, but I found out how to revert the problem:
I'm using PostgreSQL as a database backend. These problems seem to be coming from me choosing to use a custom primary key, or because my custom primary key, is a CharField. Luckily, I had made a DB Backup before making these changes, as I wasn't sure if everything would go smoothly and I reverted the code to using id as the primary key and used the SlugRelatedField to get the book.
So, the solution would be: Postgres doesn't like CharField as primary keys?

Django - Dynamic filtering in class based views

I am trying to write a Class Based View from an already filtered view. However I couldn't find out how to pass arguments to CBV.
I am using django 3.0.4 with pipenv python 3.8
as an example assume I have 2 models:
class Customer(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
email = models.EmailField()
class WorkOrder(models.Model):
date_created = models.DateField('Date', default=datetime.now)
due_date = models.DateField('Due Date')
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name='Customer')
STATUS_CHOICES = [
('pending', 'Pending'),
('approved', 'Approved'),
('completed', 'Completed'),
('cancelled', 'Cancelled'),
]
status = models.CharField('Status of the work order', max_length=10, choices=STATUS_CHOICES, default='pending')
description = models.TextField('Description of the work')
I wrote a FBV for listing the work orders of a customer filtered by status. Something like that:
def customer_workorders(request, pk):
customer = CustomUser.objects.get(pk=pk)
pending_workorders = WorkOrder.objects.filter(customer=pk).filter(status='pending')
approved_workorders = WorkOrder.objects.filter(customer=pk).filter(status='approved')
completed_workorders = WorkOrder.objects.filter(customer=pk).filter(status='completed')
cancelled_workorders = WorkOrder.objects.filter(customer=pk).filter(status='cancelled')
context = {
'customer': customer,
'pending': pending_workorders,
'approved': approved_workorders,
'completed': completed_workorders,
'cancelled': cancelled_workorders
}
return render(request, 'reports/cusomer_workorders.html', context)
I am trying to make a CBV list view of every work orders according to their status.
I know my Class Based View should be like this
class CustomerWorkOrderPendingView(LoginRequiredMixin, ListView):
template_name = 'reports/customer_workorders_list.html'
def get_queryset(self):
return WorkOrder.objects.filter(customer=customer).filter(status='pending')
My question is how can I get the customer object according to that CBV? More generally how can I pass a argument in order to make a query with it in CBV.
I know I can pass a variable like
{% url 'to_some_view' variable %}
and in FBV I can get the variable
def a_view(request, variable):
some logic with variable
How can I do that in CBV?
Thank you for your time.
For those who would be intrested I came up with a solution...
In my case it seems you have to do NOTHING!
The user id already passed in the url and instead of
return WorkOrder.objects.filter(customer=customer).filter(status='pending')
making a query just for status is enough.
return WorkOrder.objects.filter(status='pending')
This returns the work orders of 'The User' listed on the previous view and adds a second filter (status='pending').

Get User objects from foreign key

I am using the Django Rest Framework. I have two models as shown below:
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)
What I'd like to have an API that will return all the followers the user has. However, I don't want it to be returned in the Following model format, I'd like for the followers to be returned as users in this format:
[
{
"username": Bob,
"email": example#example.com
},
{
"username": Charlie,
"email": example#example.com
}
]
Right now, I have something like this:
class FollowingAPIView(ListAPIView):
serializer_class = FollowingSerializer
def get_queryset(self):
return Following.objects.filter(target=3)
class FollowingSerializer(serializers.ModelSerializer):
class Meta:
model = Following
fields = ('follower', 'target')
This only shows the follower and target I don't want this. I want the actual data from User Also, I put target=3 as a placeholder just to test.
you should use a serializer for the User not following:
class UserSerializer(serializers.ModelSerializer):
is_following = serializer.IntegerField()
class Meta:
model = User
fields = ('username', 'email', 'is_following')
also, change your queryset in view as below:
from django.db import models
from rest_framework.generics import get_object_or_404
class FollowingAPIView(ListAPIView):
serializer_class = FollowingSerializer
### so add this method to get the requested user based on the username in the url
def get_requested_user(self):
##get the user based on the user name in url
filter_kwargs = {'username': self.kwargs['username']}
obj = get_object_or_404(User.objects.all(), **filter_kwargs)
return obj
def get_queryset(self):
requested_user = self.get_requested_user()
return User.objects.filter(targets__target=requested_user)\
.annotate(is_following=models.Count('followers', filter=models.Q(followers__follower=requested_user), distinct=True))
if you are using Django<2.0, your get_queryset should be like:
def get_queryset(self):
requested_user = self.get_requested_user()
return User.objects.filter(targets__target=requested_user)\
.annotate(is_following=models.Count(models.Case(models.When(models.Q(followers__follower=requested_user), 1))))
because you want to return a list of users, not Following instances. use following only to filter( targets__ in the filter above) the users that their target in Following is the currently authenticated user(at least in one of its targets).
updated
also, change your url to something like this:
path('/api/followers/<username>/', FollowingAPIView.as_view(), name='get_user_followers')

Django Twitter clone. How to restrict user from liking a tweet more than once?

I'm not sure where to start. Right now, the user can press like as many times they want and it'll just add up the total likes for that tweet.
models.py
class Howl(models.Model):
author = models.ForeignKey(User, null=True)
content = models.CharField(max_length=150)
published_date = models.DateTimeField(default=timezone.now)
like_count = models.IntegerField(default=0)
rehowl_count = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse('howl:index')
def __str__(self):
return self.content
views.py
class HowlLike(UpdateView):
model = Howl
fields = []
def form_valid(self, form):
instance = form.save(commit=False)
instance.like_count += 1
instance.save()
return redirect('howl:index')
Django Twitter clone. How to restrict user from liking a tweet more than once?
As well as tracking how many Likes a post has, you'll probably also want to track who has "Liked" each post. You can solve both of these problems by creating a joining table Likes with a unique key on User and Howl.
The unique key will prevent any User from doing duplicate likes.
You can do this in Django with a ManyToManyField, note that since this means adding a second User relationship to Howl, we need to disambiguate the relationship by providing a related_name
Eg:
class Howl(models.Model):
author = models.ForeignKey(User, null=True, related_name='howls_authored')
liked_by = models.ManyToManyField(User, through='Like')
# ...rest of class as above
class Like(models.Model):
user = models.ForeignKey(User)
howl = models.ForeignKey(Howl)
class Meta:
unique_together = (('user', 'howl'))
like_count count then becomes redundant, since you can use Howl.liked_by.count() instead.
The other benefit of this is that it allows you to store information about the Like - eg when it was added.
An idea could be adding a column to your table named likers and before incrementing like_counts check if the models.likers contains the new liker or not. If not increment the likes, if yes don't.
Changed liked_count in my models.py to
liked_by = models.ManyToManyField(User, related_name="likes")
views.py
class HowlLike(UpdateView):
model = Howl
fields = []
def form_valid(self, form):
instance = form.save(commit=False)
instance.liked_by.add(self.request.user)
instance.like_count = instance.liked_by.count()
instance.save()
return redirect('howl:index')
index.html
{{howl.liked_by.count}}

Categories

Resources