Django: Check if record exists in two different states - django

I am using two models to build a chat system between users:
class Chat(models.Model):
created = models.DateTimeField(auto_now_add=True)
class Participant(models.Model):
chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='participants')
sender = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
receiver = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
One record in the participants model represents the ability to send messages from the user sender to the user receiver.
Thus, for a valid private chat between A and B, two records will exist, one with A as sender and B as receiver and vice versa.
Since one user will always be the one starting the chat but the first participant record could be with A as sender or B as sender, I need to know if there's a clean and cheap way to check if both records exist when a user tries to initiate a chat, and return the chat id if it exists.
How do I search for the existence of records (sender=A, receiver=B) and (sender=B, receiver=A) in the same query?

You can use Q objects to create complex queries including matching on one condition OR another
query = Participant.objects.filter(Q(sender=A, receiver=B) | Q(sender=B, receiver=A))
query.count() == 2 # If you want to check that 2 records exist
| in this case creates a filter with an "OR"

You can make use of two JOINs here, like:
Chat.objects.filter(
participants__sender=user_a,
participants__receiver=user_b
).filter(
participants__sender=user_b,
participants__receiver=user_a
)
This will result in a query like:
SELECT chat.id, chat.created
FROM chat
INNER JOIN participant ON chat.id = participant.chat_id
INNER JOIN participant T5 ON chat.id = T5.chat_id
WHERE participant.receiver_id = user_b AND participant.sender_id = user_a
AND T5.receiver_id = user_a AND T5.sender_id = user_b
It will thus return all the Chat objects for which two such Participant objects exist.
The above is not ideal however, since we make two JOINs. In case there is a unique_together constraint on the participants, as in:
class Participant(models.Model):
chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='participants')
sender = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
receiver = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
class Meta:
unique_together = ['sender', 'receiver']
We can just count the number of Participant objects, like:
from django.db.models import Count, Q
Chat.objects.filter(
Q(participants__sender=user_a, participants__receiver=user_b) |
Q(participants__sender=user_b, participants__receiver=user_a)
).annotate(
nparticipants=Count('participants')
).get(
nparticipants=2
)
This will use the following query:
SELECT chat.id, chat.created, COUNT(participant.id) AS nparticipants
FROM chat
INNER JOIN participant ON chat.id = participant.chat_id
WHERE (participant.receiver_id = user_b AND participant.sender_id = user_a)
OR (participant.receiver_id = user_a AND participant.sender_id = user_b)
GROUP BY chat.id
HAVING COUNT(participant.id) = 2
We can use .get(..) here, since due to the unique_together constraint, it is guaranteed that there is at most one Chat object for which this will exist. We can thus then handle the situation with a Chat.DoesNotExist exception.
I am however not really convinced that the above modeling is ideal. First of all the number of records will scale quadratic with the number of participants: for three participants, there are six records. Furthermore a Chat is probably conceptually speaking not "directional": there is no sender and receiver, there are two or more peers that share information.

Related

Race condition when two different users inserting new records to database in Django

There is a race condition situation, when I want to create a new instance of model Order.
There is a daily_id field that everyday for any category starts from one. It means every category has its own daily id.
class Order(models.Model):
daily_id = models.SmallIntegerField(default=0)
category = models.ForeignKey(Categoty, on_delete=models.PROTECT, related_name="orders")
declare_time = models.DateField()
...
}
daily_id field of new record is being calculated using this method:
def get_daily_id(category, declare_time):
try:
last_order = Order.objects.filter(declare_time=declare_time,
category=category).latest('daily_id')
return last_order.daily_id + 1
except Order.DoesNotExist:
# If no order has been registered in declare_time date.
return 1
The problem is that when two different users are registering orders in the same category at the same time, it is highly likely that the orders have the repetitive daily_id values.
I have tried #transaction.atomic decorator for post method of DRF APIView and it didn't work!
You must use an auto increment and add a view that computes your semantic order like :
SELECT *, ROW_NUMBER() OVER(PARTITION BY MyDayDate ORDER BY id_autoinc) AS daily_id

Getting distinct objects of a queryset from a reverse relation in Django

class Customer(models.Model):
name = models.CharField(max_length=189)
class Message(models.Model):
message = models.TextField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="messages")
created_at = models.DateTimeField(auto_now_add=True)
What I want to do here is that I want to get the queryset of distinct Customers ordered by the Message.created_at. My database is mysql.
I have tried the following.
qs = Customers.objects.all().order_by("-messages__created_at").distinct()
m = Messages.objects.all().values("customer").distinct().order_by("-created_at")
m = Messages.objects.all().order_by("-created_at").values("customer").distinct()
In the end , I used a set to accomplish this, but I think I might be missing something. My current solution:
customers = set(Interaction.objects.all().values_list("customer").distinct())
customer_list = list()
for c in customers:
customer_list.append(c[0])
EDIT
Is it possible to get a list of customers ordered by according to their last message time but the queryset will also contain the last message value as another field?
Based on your comment you want to order the customers based on their latest message. We can do so by annotating the Customers and then sort on the annotation:
from dango.db.models import Max
Customer.objects.annotate(
last_message=Max('messages__crated_at')
).order_by("-last_message")
A potential problem is what to do for Customers that have written no message at all. In that case the last_message attribute will be NULL (None) in Python. We can specify this with nulls_first or nulls_last in the .order_by of an F-expression. For example:
from dango.db.models import F, Max
Customer.objects.annotate(
last_message=Max('messages__crated_at')
).order_by(F('last_message').desc(nulls_last=True))
A nice bonus is that the Customer objects of this queryset will have an extra attribute: the .last_message attribute will specify what the last time was when the user has written a message.
You can also decide to filter them out, for example with:
from dango.db.models import F, Max
Customer.objects.filter(
messages__isnull=False,
).annotate(
last_message=Max('messages__crated_at')
).order_by('-last_message')

Django query of table with implicit join on itself

I've read the documentation and looked at other questions posted here, but I can't find or figure out whether this is possible in Django.
I have a model relating actors and movies:
class Role(models.Model):
title_id = models.CharField('Title ID', max_length=20, db_index=True)
name_id = models.CharField('Name ID', max_length=20, db_index=True)
role = models.CharField('Role', max_length=300, default='?')
This is a single table that has pairs of actors and movies, so given a movie (title_id), there's a row for each actor in that movie. Similarly, given an actor (name_id), there's a row for every movie that actor was in.
I need to execute a query to return the list of all title_id's that are related to a given title_id by a common actor. The SQL for this query looks like this:
SELECT DISTINCT r2.title_id
FROM role as r1, role as r2
WHERE r1.name_id = r2.name_id
AND r1.title_id != r2.title_id
AND r1.title_id = <given title_id>
Can something like this be expressed in a single Django ORM query, or am I forced to use two queries with some intervening code? (Or raw SQL?)
Normally I would break this into Actor and Movie table to make it easier to query, but your requirement is there so I will give it a go
def get_related_titles(title_id)
all_actors = Role.objects.filter(title_id=title_id).values_list('pk', flat=True)
return Role.objects.filter(pk__in=all_actors).exclude(title_id=title_id) # maybe u need .distinct() here
this should give you one query, validate it this way:
print(get_related_titles(some_title_id).query)

Django make a double foreign key lookup involving one to many to one relations

I have the following models:
def Order(models.Model):
receiver = models.ForeignKey(Receiver)
warehouse = models.ForeignKey(Warehouse)
def Receiver(models.Model):
user = models.ForeignKey(User) #this is not made one to one because user can have more than one receiver
name = ...
zipcode = ...
def Warehouse(models.Model):
city = ...
street = ...
I want to select all Warehouse entries related to request.User object. The only way i can do this now is:
orders = Order.objects.filter(receiver__user=request.User)
# here i set orders warehouse ids to list called ids
user_warehouses = Warehouse.objects.filter(pk__in=ids)
But i have a strong feeling that i am inventing the wheel. Is there a more simple Django-way of doing this?
Warehouse.objects.filter(order__receiver__user=request.user)
you can traverse relations backwards ("reverse lookup") and traverse multiple levels with the double-underscore syntax
https://docs.djangoproject.com/en/1.8/topics/db/queries/#lookups-that-span-relationships

Count foreign objects for model

I have following models (simplified) in Django app:
class Company(BaseModel):
user = models.OneToOneField(User)
title = models.CharField(max_length=128)
class Event(BaseModel):
company= models.ForeignKey(Company)
description = models.TextField(verbose_name="Description", blank=True,null=True,max_length=200,default="")
in one of my views, I want to count Event objects for company "owned" by logged user.
to access company object I use something like request.user.company
How can I count all Event objects related to single company?
EDIT:
I think I asked wrong question. What I want to do:
I select single event object:
event = Event.objects.get(uuid=event_uuid)
and now I want to get number of Event but within single company, not global ID.
I found hard to understand your question, do you want to count Event associated to a given Company?
c = Company.objects.get(...)
event_n = Event.objects.filter(company=c).count()
or
n = 2
event_n = Event.objects.filter(company__pk=n).count()
or
u = User.objects.get(...)
event_n = Event.objects.filter(company__user=u).count()
Do you want to collect one (some) company (companies) with the number of Event associated?
from django.db.models import Count
company = Company.objects.filter(pk=n).annotate(event_n=Count('event')).get()
print company.event_n
or
companies = Company.objects.filter(...).annotate(event_n=Count('event'))
for c in companies:
print c.event_n
If you already have an event and you want the number of events associated to its company you can try
e = Event.objects.get(...)
event_n = Event.objects.filter(company=e.company).count()
or
n = 3
e = Event.objects.filter(pk=n).annotate(event_n=Count('company__event')).get()
print e.event_n