Django, m2m count annotation related model with condition - django

so my models:
class User(models.Model):
username = models.CharField(max_lenght=255)
class Group(models.Model):
users = models.ManyToManyField('User')
class ActiveGroup(models.Model):
group = models.ForeignKey('Group', null=True, blank=True, default=None)
user = models.OneToOneField('User')
So the deal is every user can be a member of any group ( which is represented by m2m field on Group model). But can be active member in only one of them at the same time ( thats why oneToOneField relation on activegroup model).
Problem: need to make a queryset for Group models with count of users which are active.
so its suppose to be like
SELECT group.id, COUNT(
SELECT COUNT(*) FROM activegroup WHERE activegroup.group_id = group.group.id
) as users_count FROM group.
I've tried django.db.models Case and When things but nothing helps.

I would suggest you changing your database design. You have rows with partially-empty entries AND duplication. And that's not good. If this project is expected to grow, you might face problems in the future.
For example: Group -> User and ActiveGroup.group -> user. It is the same information stored two times. I'd change it before going to production. My suggestion would be using through:
https://docs.djangoproject.com/en/1.10/topics/db/models/#extra-fields-on-many-to-many-relationships
You could create a boolean field called "active" and simply switch that every time the active group is changed.
But of course, these are only suggestions. :)
Your solution is:
Group.objects.annotate(total=Count('activegroup__user'))
ag = Group.objects.annotate(total=Count('activegroup__user'))
for g in ag:
print(g, g.total)
>> group_1 2
group_2 1
In your model, you might face another group called null, which will be users without group.
https://docs.djangoproject.com/en/1.10/ref/models/querysets/#annotate

Related

Django ORM: Get all records and the respective last log for each record

I have two models, the simple version would be this:
class Users:
name = models.CharField()
birthdate = models.CharField()
# other fields that play no role in calculations or filters, but I simply need to display
class UserLogs:
user_id = models.ForeignKey(to='Users', related_name='user_daily_logs', on_delete=models.CASCADE)
reference_date = models.DateField()
hours_spent_in_chats = models.DecimalField()
hours_spent_in_p_channels = models.DecimalField()
hours_spent_in_challenges = models.DecimalField()
# other fields that play no role in calculations or filters, but I simply need to display
What I need to write is a query that will return all the fields of all users, with the latest log (reference_date) for each user. So for n users and m logs, the query should return n records. It is guaranteed that each user has at least one log record.
Restrictions:
the query needs to be written in django orm
the query needs to start from the user model. So Anything that goes like Users.objects... is ok. Anything that goes like UserLogs.objects... is not. That's because of filters and logic in the viewset, which is beyond my control
It has to be a single query, and no iterations in python, pandas or itertools are allowed. The Queryset will be directly processed by a serializer.
I shouldn't have to specify the names of the columns that need to be returned, one by one. The query must return all the columns from both models
Attempt no. 1 returns only user id and the log date (for obvious reasons). However, it is the right date, but I just need to get the other columns:
test = User.objects.select_related("user_daily_logs").values("user_daily_logs__user_id").annotate(
max_date=Max("user_daily_logs__reference_date"))
Attempt no. 2 generates as error (Cannot resolve expression type, unknown output_field):
logs = UserLogs.objects.filter(user_id=OuterRef('pk')).order_by('-reference_date')[:1]
users = Users.objects.annotate(latest_log = Subquery(logs))
This seems impossible taking into account all the restrictions.
One approach would be to use prefetch_related
users = User.objects.all().prefetch_related(
models.Prefetch(
'user_daily_logs',
queryset=UserLogs.objects.filter().order_by('-reference_date'),
to_attr="latest_log"
)
)
This will do two db queries and return all logs for every user which may or not be a problem depending on the number of records. If you need only logs for the current day as the name suggest, you can add that to filter and reduce the number of UserLogs records. Of course you need to get the first element from the list.
users.daily_logs[0]
For that you can create a #property on the User model which could look roughly like this
#property
def latest_log(self):
if not hasattr('daily_logs'):
return None
return self.daily_logs[0]
user.latest_log
You can also go a step further and try the following SubQuery inside Prefetch to limit the queryset to one element but I am not sure on the performance with this one (credits Django prefetch_related with limit).
users = User.objects.all().prefetch_related(
models.Prefetch(
'user_daily_logs',
queryset=UserLogs.objects.filter(id__in=Subquery(UserLogs.objects.filter(user_id=OuterRef('user_id')).order_by('-reference_date').values_list('id', flat=True)[:1] ) ),
to_attr="latest_log"
)
)

In Django, How to call the 'User Group' as a foreign key in a model class?

I created a model class named Question. I want only particular user group can answer the questions. NOW, How can I bring a foreign key here from user group?
In my DB there is a Question Table. There will be a lot of questions. But not all the users are allowed to view or answer the questions. This will depend on in which user group the user belongs to. So I have to specify which user group is allowed to answer the question in the Question table. Now, I have to create a field in question table where I (or more specifically the admin) can select an user group. To do so, I have to link the "Group" table as a foreign key in the table.
from django.contrib.auth.models import Group
class Question(models.Model):
# Fields
qs_title = models.CharField(max_length=350)
qs_status = models.IntegerField()
# Relationship Fields
qs_f_track = models.ForeignKey('cmit.QuestionTrack', )
responsible = models.ForeignKey(Group) # (Is this line Okay? not working though.)
The above code is not working as when the admin is trying to add a question it says,
No Such Column: cmit_question.qs_f_responsible_id
Ok... Got a solution... Kind of, per se...
The relation should be Many To Many...
Lets see the code...
qs_f_group = models.ManyToManyField(Group)
qs_group = models.CharField(max_length=350, null=True)
def get_group(self):
return self.qs_f_group.all().first()
The function is to get the first entry of the group.
Thanks a lot.
(Though I am not using the same. A function wont help me at all.
Rather I went static:
CHOICE_LIST = (('0', 'Undefined'), ('staffGroup', 'staffGroup'), ('staffGroup02', 'staffGroup02'), ('staffGroup03', 'staffGroup03'), ('staffGroup04', 'staffGroup04'), ('staffGroup05', 'staffGroup05'),)
qs_responsible = models.CharField(max_length=350, default=0, choices=CHOICE_LIST, verbose_name='Responsible Party')
Not recommended, Obviously! )

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

How to ask the Django manytomany manager to match several relations at once?

I've got this model:
class Movie(models.Model):
# I use taggit for tag management
tags = taggit.managers.TaggableManager()
class Person(models.Model):
# manytomany with a intermediary model
movies = models.ManyToManyField(Movie, through='Activity')
class Activity(models.Model):
movie = models.ForeignKey(Movie)
person = models.ForeignKey(Person)
name = models.CharField(max_length=30, default='actor')
And I'd like to match a movie that has the same actors as another one. Not one actor in common, but all the actors in common.
So I don't want this:
# actors is a shortcut property
one_actor_in_common = Movie.object.filter(activities__name='actor',
team_members__in=self.movie.actors)
I want something that would make "Matrix I" match "Matrix II" because they share 'Keanu Reeves' and 'Laurence Fishburne', but not match "Speed" because they share 'Keanu Reeves' but not 'Laurence Fishburne'.
The many to manytomany manager cannot match several relations at once. Down at the database level it all comes down to selecting and grouping.
So naturally speaking the only question the database is capable to answer is this: List acting activites that involve these persons, group them by movie and show only those movies having the same number of those acting activities as persons.
Translated to ORM speak it looks like this:
actors = Person.objects.filter(name__in=('Keanu Reaves', 'Laurence Fishburne'))
qs = Movie.objects.filter(activity__name='actor',
activity__person__in=actors)
qs = qs.annotate(common_actors=Count('activity'))
all_actors_in_common = qs.filter(common_actors=actors.count())
The query this produces is actually not bad:
SELECT "cmdb_movie"."id", "cmdb_movie"."title", COUNT("cmdb_activity"."id") AS "common_actors"
FROM "cmdb_movie"
LEFT OUTER JOIN "cmdb_activity" ON ("cmdb_movie"."id" = "cmdb_activity"."movie_id")
WHERE ("cmdb_activity"."person_id" IN (SELECT U0."id" FROM "cmdb_person" U0 WHERE U0."name" IN ('Keanu Reaves', 'Laurence Fishburne'))
AND "cmdb_activity"."name" = 'actor' )
GROUP BY "cmdb_movie"."id", "cmdb_movie"."title", "cmdb_movie"."id", "cmdb_movie"."title"
HAVING COUNT("cmdb_activity"."id") = 2
I also have a small application that I used to test this, but I don't know wether someone needs it nor where to host it.

Many-to-many "by proxy" relathionship in django

My data model consists of three main entities:
class User(models.Model):
...
class Source(models.Model):
user = models.ForeignKey(User, related_name='iuser')
country = models.ForeignKey(Country, on_delete=models.DO_NOTHING)
description = models.CharField(max_length=100)
class Destination(models.Model):
user = models.ForeignKey(User, related_name='wuser')
country = models.ForeignKey(Country)
I am trying to create a queryset which is join all sources with destinations by user (many to many). In such a way I would have a table with all possible source/destination combinations for every user.
In SQL I would simple JOIN the three tables and select the appropriate information from each table.
My question is how to perform the query? How to access the query data?
In django queries are done on the model object, its well documented. The queries or querysets are lazy and when they execute they generally return a list of dict, each dict in the list contains the field followed by the value eg: [{'user':'albert','country':'US and A :) ','description':'my description'},....].
All possible source,destination combinations for every user?
I think you will have to use a reverse relation ship to get this done eg:
my_joined_query = User.objects.values('user','source__country','source__description','destination__country')
notice that i'm using the smaller case name of the models Source and Destination which have ForeignKey relationship with User this will join all the three tabels go through the documentation its rich.
Edit:
To make an inner join you will have to tell the query, this can be simply achieved by using __isnull=False on the reverse model name:
my_innerjoined_query = User.objects.filter(source__isnull=False,destination__isnull=False)
This should do a inner join on all the tables.
Then you can select what you want to display by using values as earlier.
hope that helps. :)