Django is annotating wrong Count (possibly duplicates?) - python

I have a model ChatMessage that has a field sender which is a ForeignKey to User model.
I'm trying to annotate a number of all the ChatMessage objects that haven't been read (eg. have seen_at__isnull=True).
For a given user, there is only one sent message with seen_at__isnull=True but Django returns 11.
User.objects.select_related(...).annotate(
sent_unread_messages=Count('sent_chat_messages',
filter=Q(sent_chat_messages__seen_at__isnull=True))).get(pk=1234).sent_unread_messages
do you know where is the problem?
EDIT:
class ChatMessageManager(models.Manager):
def get_queryset(self) -> models.QuerySet:
return super().get_queryset().select_related('sender', 'recipient')
def as_sender_or_recipient(self, user) -> models.QuerySet:
return self.get_queryset().filter(Q(sender=user) | Q(recipient=user))
class ChatMessage(BaseModel):
objects = ChatMessageManager()
sender = models.ForeignKey('users.User', verbose_name='Odosielateľ', null=True, blank=True,
on_delete=models.SET_NULL, related_name='sent_chat_messages')
recipient = models.ForeignKey('users.User', verbose_name='Adresát', null=True, blank=True,
on_delete=models.SET_NULL, related_name='received_chat_messages')
content = models.TextField('Obsah')
attachment = models.FileField('Príloha', null=True, blank=True)
attachment_filename = models.CharField('Názov prílohy', null=True, blank=True, max_length=128)
meta = models.JSONField(verbose_name='Meta', null=True, blank=True, help_text='must be JSON')
seen_at = models.DateTimeField('Prečítané o', null=True, blank=True)
class CustomUserManager(UserManager):
def get_queryset(self):
return super().get_queryset().select_related('staff_profile', 'client_profile').annotate(
sent_unread_messages=Count('sent_chat_messages',
filter=Q(sent_chat_messages__seen_at__isnull=True))).annotate(
received_unread_messages=Count('received_chat_messages',
filter=Q(received_chat_messages__seen_at__isnull=True))).annotate(
sent_latest_message=Subquery(
ChatMessage.objects.filter(sender=OuterRef('pk')).order_by('-created').values('content')[:1])).annotate(
sent_latest_message_dt=Subquery(
ChatMessage.objects.filter(sender=OuterRef('pk')).order_by('-created').values('created')[:1])).annotate(
received_latest_message=Subquery(
ChatMessage.objects.filter(recipient=OuterRef('pk')).order_by('-created').values('content')[
:1])).annotate(
received_latest_message_dt=Subquery(
ChatMessage.objects.filter(recipient=OuterRef('pk')).order_by('-created').values('created')[:1]))

can you try using .distinct() method (which removes the duplicate elements from a queryset) when getting the messages ?

Related

How to display only the appointments that the current logged in user has made instead of fetching up all the appointments from the database in Django

This the views.py file.
How can i display the appointments made by the current logged in user?
def user(request):
client = Client.objects.all()
appointments = Appointment.objects.all()
context = {'appointments': appointments, 'client': client,
}
return render(request, 'users/user.html', context)
Here is my Models.py. I need to display the appointments by a user when they are logged in to their profile.
class Appointment(models.Model):
CATEGORY = (
('Plumbing', 'Plumbing'),
('Electrical', 'Electrical'),
('Cleaning', 'Cleaning'),
)
STATUS = (
('Pending', 'Pending'),
('Delivered', 'Delivered'),
)
user = models.ForeignKey(Client, null=True, on_delete=models.SET_NULL)
name = models.CharField(max_length=200, null=True)
worker = models.ForeignKey(Worker, null=True, on_delete=models.SET_NULL)
category = models.CharField(max_length=200, null=True, choices=CATEGORY)
task_date = models.DateField(_("Task Date"), blank=True, null=True)
task_location = models.CharField(max_length=200, null=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
status = models.CharField(max_length=200, null=True, choices=STATUS)
budget = models.FloatField(null=True)
task_description = models.CharField(max_length=1000, null=True)
task_image = models.ImageField(
null=True, blank=True, help_text='Optional.')
def __str__(self):
return str(self.user)
instead of using all() in your query use filter()
all() gives you all the entries in the table.
do something like this:
appointments = Appointment.objects.filter(user = request.user)
the left side "user" inside the filter must be a column in the Appointment model/table. you can pass multiple parameters inside the filter.
Yea it worked. but i had to create a one to one relatioship between appointment and User

Duplicated django ORM query n+1

I have a models looks like this
class Transaction(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
income_period_choices = (("Weekly", "Weekly"), ("Fortnightly", "Fortnightly"))
chp_reference = models.CharField(max_length=50, unique=True)
rent_effective_date = models.DateField(null=True, blank=True)
class FamilyGroup(models.Model):
name = models.CharField(max_length=10, choices=name_choices)
transaction = models.ForeignKey(
Transaction, on_delete=models.CASCADE, related_name="family_groups"
)
family_type = models.ForeignKey(
FamilySituation, on_delete=models.PROTECT, null=True, blank=True
)
last_rent = models.DecimalField(
help_text="per week", max_digits=7, decimal_places=2, null=True, blank=True
)
#property
def rent_assessment_rate(self):
return RentAssessmentRate.objects.get(active="Yes") # here i think i should add select_related maybe or something, but im not sure
#property
def ftb_rate(self):
return self.rent_assessment_rate.ftb
#property
def cra_rate(self):
return self.rent_assessment_rate.cra
#property
def maintenance_rate(self):
return self.rent_assessment_rate.maintenance
views
def index(request):
transaction = Transaction.objects.all().prefetch_related('family_groups')
return render(request, 'cra/index.html', {"transaction":transaction})
So I'm getting a duplicated queries from RentAssessmentRate table while trying to retrieve the data on the FamilyGroup table.
What would be a good approach to avoid such duplicates? Thanks in advance

Add new item in many to many field via api in Django

I'm calling an API to update the my liked_products many to many model in Django but, when calling the prod ID to add the item to the list, I get the error:
AttributeError at /api/customer/like_product/
'ReturnDict' object has no attribute 'liked_products'
Here is my API:
#csrf_exempt
def updated_liked_products(request):
customer = get_user(request)
if not customer:
return JsonResponse({'invalid token'})
customer_details = CustomerDetailSerializer(CustomerDetails.objects.get(
customer=customer)).data
customer_details.liked_products.add(request.data['prodId'])
customer_details.save()
return JsonResponse({"success": 'updated'})
Customer Details Model:
age = models.IntegerField(default="21", blank=True)
address = models.CharField(
default='', max_length=254, null=True, blank=True)
nick_name = models.CharField(
default='', max_length=254, blank=True)
average_order = models.FloatField(default="0.0", blank=True)
completed_orders = models.IntegerField(default="0", blank=True)
customer = models.ForeignKey(
Customer, on_delete=models.CASCADE)
customer_type = MultiSelectField(
choices=CUSTYPE, default=CUSTYPE, max_length=100)
current_selfie = models.ImageField(
upload_to='sefies/', blank=True, default='')
email_confirmed = models.BooleanField(default=False)
last_signin = models.DateTimeField(default=timezone.now)
liked_products = models.ManyToManyField('Product')
needs_help_with = MultiSelectField(
choices=CATEGORIES, max_length=1000, default='')
phone = models.CharField(
I am using Postman to update the data like this so I can see the liked product field but, cannot access it.:
You're having this error because you're trying to access liked_products attribute on a serialized data that is an instance of ReturnDict and not CustomerDetails.
It seems like there is not much point in the serializer usage in this API so you should be able to achieve what you want with just this:
#csrf_exempt
def updated_liked_products(request):
customer = get_user(request)
if not customer:
return JsonResponse({'invalid token'})
customer_details = CustomerDetails.objects.get(customer=customer)
customer_details.liked_products.add(request.data['prodId'])
return JsonResponse({"success": 'updated'})

Django Inner Join on Derived Query

I have two models shown as follows. I want to be able to execute this query through the django ORM, essentially giving me the CustomUser class alongside two derived fields: max(message.sent_at) and max(case when read_at is null then 1 else 0 end). Those two fields would enable me to sort threads of messages by usernames and latest activity.
Here are my classes:
class CustomUser(AbstractBaseUser, PermissionsMixin):
username_validator = UnicodeUsernameValidator()
username = models.CharField(_('username'), max_length=150, unique=True, help_text=_('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'), validators=[username_validator], error_messages={'unique': _('A user with that username already exists.'),},)
email = models.EmailField(_('email address'), blank=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'),)
is_active = models.BooleanField(_('active'), default=True, help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'),)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
bio = models.TextField(max_length=500, null=True, blank=True)
location = models.CharField(max_length=30, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True)
phone_number = PhoneNumberField(default='+10000000000')
gender = models.CharField(max_length=32, choices=[(tag.name, tag.value) for tag in GenderChoice], default=GenderChoice.UNSPECIFIED.value)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
ordering = ['username']
verbose_name = _('user')
verbose_name_plural = _('users')
and
class Message(AbstractIP):
subject = models.CharField(_('Subject'), max_length=120, blank=True)
body = models.TextField(_('Body')) # Do we want to cap length or enforce non-blank?
sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sender_messages', verbose_name=_('Sender'), on_delete=models.CASCADE)
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='receiver_messages', verbose_name=_('Recipient'), blank=True, on_delete=models.CASCADE)
parent_msg = models.ForeignKey('self', related_name='next_messages', null=True, blank=True, verbose_name=_('Parent message'), on_delete=models.CASCADE)
sent_at = models.DateTimeField(_('sent at'), null=True, blank=True)
read_at = models.DateTimeField(_('read at'), null=True, blank=True)
replied_at = models.DateTimeField(_('replied at'), null=True, blank=True)
sender_deleted_at = models.DateTimeField(_('Sender deleted at'), null=True, blank=True)
recipient_deleted_at = models.DateTimeField(_('Recipient deleted at'), null=True, blank=True)
ip = models.GenericIPAddressField(verbose_name=_('IP'), null=True, blank=True)
user_agent = models.CharField(verbose_name=_('User Agent'), blank=True, max_length=255)
objects = MessageManager() # Manager for Message queries
def new(self):
"""Returns whether the recipient has read the message or not"""
if self.read_at is not None:
return False
return True
def replied(self):
"""Returns whether the recipient has written a reply to this message"""
if self.replied_at is not None:
return True
return False
def __str__(self):
if self.subject is not None:
return self.subject
if self.body is not None:
return self.body[:40]
return None
def get_absolute_url(self):
return reverse('messages_detail', args=[self.id])
def save(self, **kwargs):
if not self.id:
self.sent_at = timezone.now()
super(Message, self).save(**kwargs)
class Meta:
ordering = ['-sent_at']
verbose_name = _('Message')
verbose_name_plural = _('Messages')
The query I want to be able to perform equates to this, but I cannot figure out how to do it in the ORM, where %s is a placeholder for the CustomUser.id (pk) field of a given user.
SELECT webrtc_customuser.*
,MAX(webrtc_message.sent_at) AS sent_at
,MAX(CASE WHEN webrtc_message.read_at IS NULL AND webrtc_customuser.id <> webrtc_message.sender_id THEN 1 ELSE 0 END) AS has_unread
FROM webrtc_customuser
INNER JOIN webrtc_message
ON (
webrtc_customuser.id = webrtc_message.sender_id
AND webrtc_message.sender_id = %s
AND webrtc_message.sender_deleted_at IS NULL
) OR (
webrtc_customuser.id = webrtc_message.recipient_id
AND webrtc_message.recipient_id = %s
AND webrtc_message.recipient_deleted_at IS NULL
)
I managed to get the correct user_id and derived fields with the following queries but cannot figure out how to get the CustomUser properties joined alongside them.
messages = self.values(
user_fk=Case(When(sender=user, then='recipient'), default='sender', output_field=models.IntegerField())
).exclude(
sender=user, recipient=user
).filter(
Q(sender=user, sender_deleted_at__isnull=True) |
Q(recipient=user, recipient_deleted_at__isnull=True)
).annotate(
max_sent_at=Max('sent_at'),
has_unread=Max(Case(When(~Q(sender=user) & Q(read_at__isnull=True), then=1), default=0, output_field=models.IntegerField()))
).order_by()
Thank you in advance for your time!
Edit: updated ORM query
You need to specify the desired user properties individually:
messages = self.values(
user_email=Case(When(sender=user, then='recipient__email'), default='sender__email'),
user_username=Case(When(sender=user, then='recipient__username'), default='sender__username'),
)
Not very pretty, particularly as you have to repeat the CASE statement for every column and may even need to specify an output_field for every one.
To get around that, ie. to get all user properties without selecting them one by one, you'd either need to a) select from CustomUser.object (figuring out how to select the relevant users and get the relevant annotations), or b) select full message objects rather than just a values() dictionary. Then you can access the full user objects via message.senderand message.recipient. But here again, the challenge would be how to filter and annotate the messages queryset using subqueries, since just omitting values() will bust the aggregates in your annotations as every message object will then be unique.

Cannot resolve keyword 'article' into field

I know that there are multiple questions about this already but none of them resolves my problem.
I have split my models into multiple files under models folder, viz., models/articles.py, models/core.py etc.
When I try executing
User.objects.annotate(my_count=Count('article'))
django gives me the following error:
FieldError: Cannot resolve keyword 'articles' into field. Choices are: access, cfi_store_item_likes, collaborators, comment, date_joined, documentation, email, emailaddress, first_name, groups, id, images, is_active, is_staff, is_superuser, last_login, last_name, listing, logentry, makey, makey_removed, newproduct, note, password, productdescription, productimage, profile, socialaccount, space_admins, space_members, textdocumentation, tutorial, user_permissions, userflags, username, video
I have the following code in models/abstract.py
class BaseModel(models.Model):
added_time = models.DateTimeField('added time')
is_enabled = models.BooleanField(default=True)
score = models.IntegerField(default=0)
class Meta:
abstract = True
app_label = 'catalog'
I have the following in models/article.py
class Article(BaseModel):
url = models.URLField()
title = models.CharField(max_length=500, null=True, blank=True)
description = models.TextField(null=True, blank=True)
image_url = models.URLField(null=True, blank=True)
rating = models.IntegerField()
recommendation = models.TextField(null=True, blank=True)
user = models.ForeignKey(User, null=True, blank=True)
tags = models.ManyToManyField(ArticleTag, null=True, blank=True)
comments = models.ManyToManyField(Comment, null=True, blank=True)
new_user = models.ForeignKey('NewUser', null=True, blank=True)
class Meta:
app_label = 'catalog'
def __unicode__(self):
return self.title + ' (' + self.url + ')'
I have the following in models/core.py, along with many other models which have been listed as the available options.
class Tutorial(BaseModel):
user = models.ForeignKey(User, null=True, blank=True)
description = models.CharField(max_length=200, null=True, blank=True)
url = models.URLField(max_length=200)
votes = models.IntegerField(default=0)
images = models.ManyToManyField(Image, related_name="tutorialimages",
null=True, blank=True)
class Meta:
app_label = 'catalog'
def __unicode__(self):
return self.url
Why is django not picking up my ForeignKey to User from models from rest of the files? Why is it picking it up only from core.py?
I have posted the models and stacktrace at http://pastebin.com/v6hFdvAC and http://pastebin.com/nxYktwHn.
Because not all users have an article. If you see, your user foreign key in Article Class can be null.
user = models.ForeignKey(User, null=True, blank=True)
then when Django tries to apply the count on null values, it raises an error.
Maybe you can use somehthing like that:
User.objects.objects.filter(article__isnull=False).count()

Categories