How to add users to Model from group in Django? - python

I have a Company that has juniors and seniors. I would like to add users by adding groups instead of individually. Imagine I have Group 1, made of 3 seniors, instead of adding those 3 individually, I'd like to be able to just add Group 1, and have the 3 seniors automatically added to the list of seniors. I'm a little stuck in my current implementation:
class Company(django.model):
juniors = m2m(User)
seniors = m2m(User)
junior_groups = m2m(Group)
senior_groups = m2m(Group)
# currently, I use this signal to add users from a group when a group is added to company
def group_changed(sender, **kwargs):
if kwargs['action'] != 'post_add': return None
co = kwargs['instance']
group_id = kwargs['pk_set'].pop()
juniors = MyGroup.objects.get(pk=group_id).user_set.all()
co.juniors = co.juniors.all() | juniors
co.save()
m2m_changed.connect(...)
The main problem is this looks messy and I have to repeat it for seniors, and potentially other types of users as well.
Is there a more straightforward way to do what I'm trying to do?
Thanks in advance!

are you trying to optimize and avoid having the group object used in your queries ?
if you are ok with a small join query you could use this syntax to get the juniors in company with id = COMP_ID
this way you don't need to handle the users directly and copy them all the time
juniors = User.objects.filter(groups__company_id = COMP_ID , groups__type = Junior)
seniors = User.objects.filter(groups__company_id = COMP_ID , groups__type = Senior)
assuming that
you add related_name "groups" to your m2m relation between groups and users
your groups have type which you manage
you called your foreign-key field 'company' on you Group model
this query can be added as a Property to the company Model , so it give the same programmatic peace of mind

Related

How can I convert Inner Join and Group By to Django ORM?

I want to change this raw SQL to Django ORM but I couldn't manage to convert.
most_read_students = LendedBook.objects.raw('
SELECT student_id as id, name, surname, COUNT(*) as count
FROM "Book_lendedbook"
INNER JOIN User_student on Book_lendedbook.student_id=User_student.id
where did_read=1
group by student_id
order by count DESC
LIMIT 5')`
I tried this and I get close result.But unfortunately, I couldn't do what I want. Because I want to join this table with another table.
most_read_students = LendedBook.objects.values('student_id')
.filter(did_read=True, return_date__month=(datetime.datetime.now().month))
.annotate(count=Count('student_id'))
When I use select_related with "User_student" table like this;
most_read_students = LendedBook.objects.select_related('User_student')
.values('student_id', 'name', 'surname')
.filter(did_read=True, return_date__month=(datetime.datetime.now().month))
.annotate(count=Count('student_id'))
It throws an error like Cannot resolve keyword 'name' into field. Choices are: book, book_id, did_read, id, is_returned, lend_date, return_date, student, student_id
But I should be able to get student properties like name and surname when I join "User_student" table.
Thank you for your help!
I solved it!
How to combine select_related() and value()? (2016)
Funny fact; my problem was not about ORM i guess. I didn't know I could reach student object's properties by just adding this to .values('student__name', 'student__surname') in the last code I've shared on this post.
This code ;
LendedBook.objects.select_related('User_student')
.values('student_id', 'name', 'surname')
.filter(did_read=True, return_date__month=(datetime.datetime.now().month))
.annotate(count=Count('student_id'))
To this code ;
LendedBook.objects.select_related('User_student')
.values('student_id', 'student__name', 'student__surname')
.filter(did_read=True, return_date__month=(datetime.datetime.now().month))
.annotate(count=Count('student_id'))
By the way, deleting .select_related('User_student') doesn't affect the result.
So, using _ _ solved my problem!

Django ORM: How to group on a value and get a different value of last element in that group

I have been trying to tackle this problem all week but I just can't seem to find the solution.
Basically I want to group on 2 values (user and assignment), then take the last element based on date and get a sum of these scores. Below a description of the problem.
With Postgres this would be easily solved by using the .distinct("value") but unfortunately I do not use Postgres.
Any help would be much appreciated!!
UserAnswer
- user
- assignment
- date
- answer
- score
So I want to group on all user / assignment combinations. Then I want to get the score of each last element in that group. So basically:
user_1, assignment_1, 2019, score 1
user_1, assignment_1, 2020, score 2 <- Take this one
user_2, assignment_1, 2020, score 1
user_2, assignment_1, 2021, score 2 <- Take this one
My best attempt is using annotation but then I do not have the score value anymore:
UserAnswer.objects.filter(user=student, assignment__in=assignments)
.values("user", "assignment")
.annotate(latest_date=Max('date'))
At the end, I had to use raw query rather than django's ORM.
subquery2 = UserAnswer.objects.raw("\
SELECT id, user_id, assignment_id, score, MAX(date) AS latest_date\
FROM soforms_useranswer \
GROUP BY user_id, assignment_id\
")
# the raw queryset from above raw query
# is very similar to queryset you get from django ORM query.
# The difference is now we add 'id' and 'score' to the fields,
# so later we can retrieve them, like below.
sum2= 0
for obj in subquery2:
print(obj.score)
sum2 += obj.score
print('sum2 is')
print(sum2)
Here, I assumed that both user and assignment are foreinkeys. Something liek below:
class Assignment(models.Model):
name = models.CharField(max_length=50)
class UserAnswer(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='answers')
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
#assignment = models.CharField(max_length=200)
score = models.IntegerField()
date = models.DateTimeField(default=timezone.now)

SQL/Python (Django) - Join each row to entire table

I'm currently creating an application which maps peoples skills against various technologies.
I have 3 tables;
Employees
Name
Department
Skill
Skill name
Results
Name (FK)
Skill (FK)
Skill level
I wish to be able to see every single employee with each skill listed in a table. I believe the correct procedure to retrieve this information would be to perform some sort of for loop and select the info from the 3 tables? The alternative is adding rows to the results table each time an employee or skill is added (although this doesn't seem like correct logic to me).
I think this is a correct logic. Since you have to keep the level of the skill for each employee.
Lets say you have created three models.
Employee
skill
Result
when you do
to get the skills of employee with id = 37
emp = Employee.objects.get(pk=37)
#here we will get an array which has tuple all the skills and its level for employee
skill_level_array = [(Skill.objects.filter(pk=x.skill), x.level) for x in Result.objects.filter(employee=emp)]
To get skills for all empoyees
all_emp = Employee.objects.all()
grand_array = {}
for emp in all_emp:
skill_level_array = [(Skill.objects.filter(pk=x.skill), x.level) for x in Result.objects.filter(employee=emp)]
grand_array[emp] = skill_level_array
Now grand_array has an array of dictionary, with key as employee and value as array of tuple

Dynamically add filter to SQLAlchemy TextClause

Assume I have a SQLAlchemy table which looks like:
class Country:
name = VARCHAR
population = INTEGER
continent = VARCHAR
num_states = INTEGER
My application allow seeing name and population for all Countries. So I have a TextClause which looks like
"select name, population from Country"
I allow raw queries in my application so I don't have option to change this to selectable.
At runtime, I want to allow my users to choose a field name and put a field value on which I want to allow filtering. eg: User can say I only want to see name and population for countries where Continent is Asia. So I dynamically want to add the filter
.where(Country.c.continent == 'Asia')
But I can't add .where to a TextClause.
Similarly, my user may choose to see name and population for countries where num_states is greater than 10. So I dynamically want to add the filter
.where(Country.c.num_states > 10)
But again I can't add .where to a TextClause.
What are the options I have to solve this problem?
Could subquery help here in any way?
Please add a filter based on the conditions. filter is used for adding where conditions in sqlalchemy.
Country.query.filter(Country.num_states > 10).all()
You can also do this:
query = Country.query.filter(Country.continent == 'Asia')
if user_input == 'states':
query = query.filter(Country.num_states > 10)
query = query.all()
This is not doable in a general sense without parsing the query. In relational algebra terms, the user applies projection and selection operations to a table, and you want to apply selection operations to it. Since the user can apply arbitrary projections (e.g. user supplies SELECT id FROM table), you are not guaranteed to be able to always apply your filters on top, so you have to apply your filters before the user does. That means you need to rewrite it to SELECT id FROM (some subquery), which requires parsing the user's query.
However, we can sort of cheat depending on the database that you are using, by having the database engine do the parsing for you. The way to do this is with CTEs, by basically shadowing the table name with a CTE.
Using your example, it looks like the following. User supplies query
SELECT name, population FROM country;
You shadow country with a CTE:
WITH country AS (
SELECT * FROM country
WHERE continent = 'Asia'
) SELECT name, population FROM country;
Unfortunately, because of the way SQLAlchemy's CTE support works, it is tough to get it to generate a CTE for a TextClause. The solution is to basically generate the string yourself, using a custom compilation extension, something like this:
class WrappedQuery(Executable, ClauseElement):
def __init__(self, name, outer, inner):
self.name = name
self.outer = outer
self.inner = inner
#compiles(WrappedQuery)
def compile_wrapped_query(element, compiler, **kwargs):
return "WITH {} AS ({}) {}".format(
element.name,
compiler.process(element.outer),
compiler.process(element.inner))
c = Country.__table__
cte = select(["*"]).select_from(c).where(c.c.continent == "Asia")
query = WrappedQuery("country", cte, text("SELECT name, population FROM country"))
session.execute(query)
From my tests, this only works in PostgreSQL. SQLite and SQL Server both treat it as recursive instead of shadowing, and MySQL does not support CTEs.
I couldn't find anything nice for this in the documentation for this. I ended up resorting to pretty much just string processing.... but at least it works!
from sqlalchemy.sql import text
query = """select name, population from Country"""
if continent is not None:
additional_clause = """WHERE continent = {continent};"""
query = query + additional_clause
text_clause = text(
query.format(
continent=continent,
),
)
else:
text_clause = text(query)
with sql_connection() as conn:
results = conn.execute(text_clause)
You could also chain this logic with more clauses, although you'll have to create a boolean flag for the first WHERE clause and then use AND for the subsequent ones.

GROUP BY in Django Queries

Dear StackOverFlow community:
I need your help in executing following SQL query.
select DATE(creation_date), COUNT(creation_date) from blog_article WHERE creation_date BETWEEN SYSDATE() - INTERVAL 30 DAY AND SYSDATE() GROUP BY DATE(creation_date) AND author="scott_tiger";
Here is my Django Model
class Article(models.Model):
title = models.CharField(...)
author = models.CharField(...)
creation_date = models.DateField(...)
How can I form aforementioned Django query using aggregate() and annotate() functions. I created something like this -
now = datetime.datetime.now()
date_diff = datetime.datetime.now() + datetime.timedelta(-30)
records = Article.objects.values('creation_date', Count('creation_date')).aggregate(Count('creation_date')).filter(author='scott_tiger', created_at__gt=date_diff, created_at__lte=now)
When I run this query it gives me following error -
'Count' object has no attribute 'split'
Any idea who to use it?
Delete Count('creation_date') from values and add annotate(Count('creation_date')) after filter.
Try
records = Article.objects.filter(author='scott_tiger', created_at__gt=date_diff,
created_at__lte=now).values('creation_date').aggregate(
ccd=Count('creation_date')).values('creation_date', 'ccd')
You need to use creation_date__count or customized name(ccd here) to refer the count result column, after aggregate().
Also, values() before aggregate limits group by columns and last value() declares the columns to be selected. There is no need to group by COUNT which is based on group of rows already.

Categories