Django query on event list: finding most recent events - django

I have a django model that collects events. It looks roughly like this:
class Events(models.Model):
class EventType(models.TextChoices):
OPEN = ...
CLOSE = ...
OTHER = ...
READ = ...
user = models.ForeignKey(User, on_delete=models.CASCADE)
box = models.ForeignKey(Box, on_delete=models.CASCADE)
event_timestamp = models.DateTimeField(default=django.utils.timezone.now)
event_type = models.CharField(max_length=6, choices=EventType.choices)
Events occur when users do things with boxes.
I have four very related queries I want to do, and I'm not at all sure how to do this without resorting to SQL, which usually isn't the right solution with django.
For box B and user U, is the user most recently open or closed for this box?
For Box B, how many users are in an open state?
For Box B, I'd like to plot day by day how many users are open, so I'd like to get a grouped array of users who opened and closed on each day.
I'd like a table over all boxes of how many users are in an open state.
For example, in SQL these would have forms something like this (not tested code, I'm hoping it's easier to use django's query language):
-- 1
SELECT event_type FROM Events
WHERE event_type in ('OPEN', 'CLOSE')
AND user = U
HAVING max(event_timestamp);
-- 2
SELECT count(*) FROM (
SELECT user, event_type FROM Events
WHERE event_type in ('OPEN', 'CLOSE')
GROUP BY user
HAVING max(event_timestamp)
) as T
WHERE event_type = 'OPEN';
-- 3
// A bit complicated any way I slice it due to the
// mapping from datetime to date.
-- 4
SELECT box, count(user) FROM (
SELECT box, user, event_type FROM Events
WHERE event_type in ('OPEN', 'CLOSE')
GROUP BY box, user
HAVING max(event_timestamp)
) as T
WHERE event_type = 'OPEN'
GROUP BY box;
I've explained my four use cases, but I suspect once I understand one of them, the others will follow. Clearly I've just not grokked something from the docs (I hope).
Many thanks for any pointers.

#jma - I am assuming- For Box B, how many events are in an open state? You mentioned Users are in an open state? But state/event_type(Open,Closed) is tied to the Events model.
box = Box.objects.get(name='B') --- assuming you have a name field in Box entity
events = Events.objects.filter(box=box,event_type='open') #queryset
for event in events:
#your logic here
print(event)
3.For box B and user U, is the event most recently open or closed for this box?
Assuming there can be more than one event for a box B with that user U
box = Box.objects.get(name='B') --- assuming you have a name field in Box entity
user = User.objects.get(username='U') -- you can filter based on any field.
Also, this depends on where you want to implement this functionality. If you are using the Django view then you won't need to query the database for the user object. Since the request object has the user attribute.
In django view, user = request.user
events = Events.objects.filter(box=box,user=user)
for event in events:
status = event.event_type
if status=='Open':
#do something
elif status=='Closed':
#do something
else:
#logic here
Also, just a suggestion
Model name should be singular. Event
If you simply name the event_type field type instead of event_type it's better. We are already in the Event model. So, we don't need to name the fields by tagging the model name as a prefix. However, I would stay away from naming a field type. I prefer category or something else. Same with event_timestamp. You can explicitly name this field to let's say to_be_held_on or created_at (based on your use case)

Related

Queryset with different datasets based on obj type

I want to implement in my application something similar to instagram's local notifications.
The main problem is that local notifications can be different types. For example:
1) Simple one: "user started following you" (this is pretty straightforward)
2) More complicated: "user and +7 other liked your photo X"
The first step of solution is to GROUP all notifications by targeted_user, event_type and user_who_triggered_event. For the first case it's okay as we will have all information. For the second case we will lack some information as Count of similar notifications. Also what if I need some additional information about those +7 user's who also liked photo X?
What I've tried:
I've tried to use GROUP BY which can work for the second case but is not okay for the first. Also I tried to do 2 separate querysets which has all needed data for each type of notification. But then I'll have a problem that I cannot combine them and also cannot use pagination. Also if I'll return 2 querysets it will be hard for front-end to sort them by datetime
The first queryset:
ReceivedNotification.objects.filter(user=user)
The second one:
ReceivedNotification.objects.filter(user=user, notification__event=Notification.LIKED_PHOTO).values_list('receiver', 'notification__event')
class Notification(TimeStampedModel):
OPEN_PROFILE = 'open_profile'
OPEN_PHOTO = 'open_photo'
ACTION_CHOICES = (
(OPEN_PROFILE, _('Open profile')),
(OPEN_PHOTO, _('Open photo')),
)
LIKED_PHOTO = 'liked_photo'
NEW_FOLLOWER = 'new_follower'
EVENT_CHOICES = (
(LIKED_PHOTO, _('Liked photo')),
(NEW_FOLLOWER, _('New follower'))
)
action = models.CharField(
max_length=255,
choices=ACTION_CHOICES,
)
event = models.CharField(
max_length=255,
choices=EVENT_CHOICES,
)
title = models.CharField(
max_length=255,
)
body = models.TextField()
class Meta:
db_table = 'notifications'
For this example I have 2 notifications in data base.
Also I have ReceivedNotification model with FK to user and FK to notification and also data field(JSONField) to store actual information
which particular user will get.
Expected result is to get all local notifications for particular user is a single query.
Some notifications should have only basic info and other should have Counter of similar notifications if there are more than 3 similar and also list of Id's of that users.

Django get related info from Many to Many through table

I have something similar to the following model
class User(Model):
username = CharField(...)
class Event(Model):
event_name = CharField(...)
event_rating = IntegerField
users = ManyToManyField(User, through="UserEventAttendence")
class UserEventAttendence(Model):
user = ForeignKey(User)
event = ForeignKey(Event)
attended = BooleanField()
class EventComments(Model):
name = CharField(...)
content = CharField(...)
event = models.ForeignKey(Event, related_name='eventcomments', on_delete=models.CASCADE)
In my view I am trying to get all events, the users for each event, then from this filter out events that the current user is going to. Below is my current query (goes to a serializer afterwards and is then sent as a response)
events = models.Event.objects.using(db_to_use).prefetch_related(
Prefetch('users', queryset=models.UserProfile.objects.using(db_to_use))
).prefetch_related(
Prefetch('eventcomments', queryset=models.EventComments.objects.using(db_to_use))
).filter(usereventattendence__user_id=request.user.id)
But I need to somehow append the "attended" field from the "UserEventAttendence" model for each event (for the current user), but I don't have any clue how to go about it.
You can save the value of attended as an annotation on Event objects:
# events is your queryset as defined in the question
# I'm not entirely sure if the field name should be
# 'usereventattendence__attended'
# or
# 'user_event_attendence__attended'
events.annotate(attended=F('usereventattendence__attended'))
You can then access attended on the individual events:
for e in events:
print(e.attended)
One note on your intermediate model. It doesn't define unique constraints, which means that theoretically at least you could have several records for the same event-user pair (leading to duplicate Event objects in your query). If this is not desired, you would want to define a unique_together constraint on the intermediate model.
I think you have to get the attendence independently and build a list of tuples:
events_list = []
for event in events:
attended = event.usereventattendence_set.filter(user=request.user).attended
event_list.append((event, attended))
Then you can iterate:
for event, attended in event_list:
# do stuff
Or pass as context to the template processor.

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 Sort by backward foreign key

I currently have the following models
class ChatRoom(models.Model):
creator = models.ForeignKey('User') # points to the initial user
class Message(models.Model):
room = models.ForeignKey('ChatRoom')
text = models.CharField(max_length=500)
date = models.DateTimeField()
from = models.ForeignKey('User') # points to some user
For any given user who logs into my website, I want to display a list of the chat rooms they have created, ordered by the date of their last message (latest activity) along with the last message entered to the chatroom
So something like
Your chat rooms
---------------
science - "hey guys, anyone know how to do calculus?" 5m
art - "hey guys, I like art" 10m
How would I perform this query in django? Keep in mind that a person might have many, many, many chat rooms and thus we can't just load all the chat_rooms with ChatRoom.objects.all() and manually iterate through it.
You need to have a additional relation to your last message, otherwise you will not be able to order by them.
class ChatRoom(models.Model):
creator = models.ForeignKey('User') # points to the initial user
# you will need to set this in view or with signals.(don't forget to handle delete)
last_message = models.ForignKey('Message')
And that you can do this
ChatRoom.objects.filter(...).order_by('last_message__date')

Chaining Foreign Keys

I have a few models that look like this:
class System(models.Model):
'''Defines a system'''
system_desc = models.CharField('System Name', max_length=50)
class SystemGroup(models.Model):
'''Groups within a 'System' (ie. Customers, regions, divisions, etc. '''
group_desc = models.CharField('Group Name',max_length=25)
system = models.ForeignKey(System)
class QueryData(models.Model):
'''Queries related to a 'System' (ie. Active users against System1, Orders today in system2, etc. '''
qry_desc = models.CharField('Query Description', max_length=50)
system = models.ForeignKey(System)
class UserDashboard(models.Model):
'''User specific Dashboard '''
user = models.ForeignKey(User)
board_name = models.CharField('Dashboard Name', max_length=50)
class UserDashboardGroup(models.Model):
'''Groups on the dashboard (ie. 'System 1's Key Indicators', 'System 2's Hot Sheet', etc. '''
group_desc = models.CharField('Group Display Title', max_length=50)
user = models.ForeignKey(User)
dashboard = models.ForeignKey(UserDashboard)
system = models.ForeignKey(System)
system_group = models.ForeignKey(SystemGroup)
class UserGroupQuery(models.Model):
'''Queries that run in a specific group on the dashboard (ie. Within 'System 1's Key Indicators, we run queries for active users today, orders in the last hour, etc. '''
user = models.ForeignKey(User)
dashboard = ChainedForeignKey(UserDashboard, chained_field='user', chained_model_field='user', show_all=False, auto_choose=True)
user_dashboard_group = ChainedForeignKey(UserDashboardGroup, chained_field='dashboard', chained_model_field='dashboard')
query = models.ForeignKey(QueryData)
I am having problems with the very last part of this. I want to restrict the 'query' that appears in a admin page based on the selected user_dashboard_group. I'm not sure how I can do this, based on my current models though. query and user_dashboard_group both have a foreign key to System, but not to each other. To get this though, I'd have to get the user_dashboard_group's system and then use that to filter the QueryData. How can I do this?
Edit
I'm adding in a picture to (hopefully) describe a little better what I want to do.
In step 1, the user inputs a name for this group of queries. This group is associated with a system (#2) and a predefined group within the system (#3) (think of #3 as a 'customer' or a 'region', etc and #1 and #3 are NOT the same thing, despite the similar naming). They then select 'Save and Continue editing' on this inline form and the drop down at step 4 becomes populated with information from the above form. Once step #4 has a selection made, I want #5 to populate with data only from the associated system. Since #2 contains this information, I am hoping it is fairly easy to do this, but I can't figure out the chaining.
I also realized that I didn't mention I was using django-smart-selects
I have never worked with django-smart-selects, but following the docs, I would expect
query = models.ChainedForeignKey(QueryData,**kwargs)
instead of
query = models.ForeignKey(QueryData)
since you want the options in query depend on the other selections. Is that understanding correct? If so, it was only about defining the right **kwargs.
For **kwargs, I would propose something like this
Update
chained_field='query', chained_model_field='user_dashboard_group__system__query_set__query'
assuming that the fields name is 'system in both cases.
I am pretty sure that this describes the relationship correctly. I am just not sure, if django-smart-selects supports that syntax.
Same way you would do it across one relation, except across two.
field__relation1__relation2