Django - Database query with condition in group_by - django

I've the following models:
class UsersRoles(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
user = models.ForeignKey(User, db_column='USER_ID')
role = models.ForeignKey(Roles, db_column='ROLE_ID')
room= models.ForeignKey(Rooms, db_column='ROOM_ID')
d_begin = models.DateTimeField(db_column='D_BEGIN')
d_end = models.DateTimeField(db_column='D_END')
,
class Roles(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
name = models.CharField(db_column='NAME', max_length=45)
and also the django User model.
So, I want to retrieve the UsersRoles for the Room 3 being the Role id equal to 5.
Since there are multiple UsersRoles that fulfil this conditions, I want to group the UsersRoles by the User id, returning only the one that has the lastest d_begin date, but if exists one in which d_begin is NULL that's the one to return.
After these conditions, I would like to group_by the User name.
To get the UsersRoles of the room 3 with role 5, I would do:
users_roles = UsersRoles.objects.filter(room__id=3, role__id=5)
My problem is to group them by User id, getting for each user the UserRole with d_begin equal to NULL if there is one, and if not, it would be the one with the latest date.
To order them, I would do users_roles.order_by('user__name').

Related

Django query based on through table

I have a 4 models which are Contents, Filters,ContentFilter , Users.
a user can view contents.
a content can be restricted using Filters so users can't see it.
here are the models.
class Content(models.Model):
title = models.CharField(max_length=120)
text = models.TextField()
filters = models.ManyToManyField(to="Filter", verbose_name=_('filter'), blank=True, related_name="filtered_content",through='ContentFilter')
class Filter(models.Model):
name = models.CharField(max_length=255, verbose_name=_('name'), unique=True)
added_user = models.ManyToManyField(to=User, related_name="added_user", blank=True)
ignored_user = models.ManyToManyField(to=User, related_name="ignored_user", blank=True)
charge_status = models.BooleanField(blank=True, verbose_name=_('charge status'))
class ContentFilter(models.Model):
content = models.ForeignKey(Content, on_delete=models.CASCADE)
filter = models.ForeignKey(Filter, on_delete=models.CASCADE)
manual_order = models.IntegerField(verbose_name=_('manual order'), default=0,rst'))
access = models.BooleanField(_('has access'))
What it means is that 5 contents exist(1,2,3,4,5).
2 users exist. x,y
A filter can be created with ignored user of (x).
Contents of 1,2,3 have a relation with filter x.
so now X sees 4,5 and Y sees 1,2,3,4,5
what I'm doing now is that based on which user has requested find which filters are related to them.
then query the through table(ContentFilter) to find what contents a user can't see and then exclude them from all of the contents.(this helps with large joins)
filters = Filter.objects.filter(Q(added_user=user)|(Q(ignored_user=user))
excluded_contents = list(ContentFilter.objects.filter(filter__in=filters).values_list('id',flat=True))
contents = Contents.objects.exclude(id__in=excluded_contents)
Problem
I want a way so that Filters can have an order and filter a queryset based on top ContentFilter for each user.
for example content 1 can be blocked for all users with 1 filter ( filter x where ignored user has all the users)
but in ContentFilter has a manual_order of 0.
then in a second filter all users who have a charge status of True can see this content.(filter y where added user has all the users and charge status True.)
and in ContentFilter has a manual_order of 1.
I think I can do it using a for loop to check all the contents and choose the top most ContentFilter of them based on filters that include that user but it's both time and resource consuming.
and I prefer not to use raw SQL but I'm not sure if there is a way to do it using django orm
I managed to solve this using Subquery.
first I create a list of filters that user is part of.
filters = Filter.objects.filter(Q(added_user=user)|(Q(ignored_user=user))
then I create a subquery to assign each content with a access value (if any filter is applied on it.)
current_used_filters = ContentFilter.objects.filter(Q(filter__in=user_filters),content=OuterRef('pk')).order_by('-manual_order')
blocked_content_list = Content.objects.annotate(access=Subquery(current_used_filters.values('access')[:1])).filter(
access=False).values_list('id', flat=True)
this raises a problem
if any of my contents does not have a filter of filters associated with it then it would not be included in this.
so I filter the ones that have an access value of False
this means that this content has a filter with a high manual order which blocks it for this specific user.
so now I have a list of content IDs which now I can exclude from all contents.
so it would be:
contents = Contents.objects.exclude(id__in=blocked_content_list)

Filter objects that have a foreign key from another object - Django

I want to filter Employee, only those that have a ForeignKey, how to do it? My solution does not returned any results.
Models.py
class Employee(models.Model):
name = models.CharField(max_length=200)
class ExperienceCategory(models.Model):
name = models.CharField(max_length=100, unique=True)
class Experience(models.Model):
user = models.ForeignKey(Employee, on_delete=models.CASCADE)
category = models.ForeignKey(ExperienceCategory, on_delete=models.CASCADE)
Views.py
experience_category = *ExperienceCategory object (1)*
#solution - final query
employee_query = Employee.objects.filter(experience__category = experience_category)
How to get employees who have a foreign key from Experience?
What you have should work just fine, as an example to reproduce that
first_employee = Employee.objects.create(name='First')
second_employee = Employee.objects.create(name='Second')
experience_category = ExperienceCategory.objects.create(name='sample_category')
Experience.objects.create(user=first_employee, category=experience_category)
Experience.objects.create(user=second_employee, category=experience_category)
employee_query = Employee.objects.filter(experience__category = experience_category)
employee_query
>>> <QuerySet [<Employee: Employee object (1)>, <Employee: Employee object (2)>]>
If you get an empty queryset it's because either there is no Experience instance with related category equal to experience_category in your database. To get the count of how many Experience instances matching this filter exist in your database you can run
Experience.objects.filter(category=experience_category).count()
In the case I provided it would return 2. Check first that this method returns a number greater than 0. If it returns 0 you should create some Experience instances first with the corresponding category.

Django bulk create with foreign key to another field

I want to bulk create objects, when they have a foreign key and their foreign key is not id field. (When to_field value is id you can reference it with model_id in creation but I haven't found a way to do id with another field.)
I have a model named Credit:
class Credit(models.Model):
account = models.ForeignKey('finance.Account', to_field='account_id', on_delete=models.PROTECT)
amount = models.PositiveBigIntegerField()
and a model named Account:
class Account(models.Model):
account_id = models.UUIDField(
verbose_name=_("account id"),
db_index=True,
null=True,
unique=True,
)
and I tried to create objects with:
accounts = [] # list of uuids
credits = [
Credit(
account__account_id=a,
amount=amount,
) for a in accounts]
created_objects = Credit.objects.bulk_create(
credits, ignore_conflicts=True
)
and I get the following error:
TypeError: Credit() got an unexpected keyword argument 'account__account_id'
That's not possible because you are touching two tables: Credit and Account. So you need at least two INSERTs anyways.
accounts_uuids = []
amount = 0
accounts = [Account(account_id=uuid) for uuid in accounts_uuids]
Account.objects.bulk_create(objs=accounts)
credits = [Credit(account=account, amount=amount) for account in accounts]
Credit.objects.bulk_create(objs=credits, ignore_conflicts=True)

SQLAlchemy many-to-many relationship updating association object with extra column

In my application, a 'set' can have a number of 'products' associated with it. Products listed against a set must have quantities defined. For this many-to-many relationship I have followed the SQLAlchemy documentation to use an association table with an additional column (quantity).
I am trying to create a form where the user can assign products and quantities against a given set. Both the sets and products already exist in the database. The data from the form are:
set.id
product.id
quantity
This works to create a new association (e.g. set 1 is 'linked' to product 3 with quantity=XYZ) but I get an integrity error when I try to update an existing record.
I can manually add a relationship/record (dummy data) or within the Flask view function as follows:
s = Set.query.get(2)
p = Product.query.get(3)
a = Set_Product_Association(set=s, product=p, quantity=23)
db.session.add(a)
db.session.commit()
Updating the record (different quantity) manually as follows works:
s.products[0].quantity = 43
db.session.add(s)
db.session.commit()
However when I use the code from the first block instead (with the aim to update the quantity field for a given, existing set and product ID), i.e.:
a = Set_Product_Association(set=s, product=p, quantity=43)
I get an integrity error
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: set_product_association.set_id, set_product_association.product_id [SQL: 'INSERT INTO set_product_association (set_id, product_id, quantity) VALUES (?, ?, ?)'] [parameters: (2, 3, 43)]
I assume this is to tell me that I'm trying to append a new record rather than updating the existing one.
How should I approach this? The 'manual' method works but relies on working out the correct index in the list (i.e. for the correct product.id).
Curiously, if I use form.popluate_obj(set) in my Flask view function to process the form data as described in my question here, I can update fields but not create new 'associations'. Unfortunately, I don't know what goes on behind the scenes there....
My models are defined like so:
class Set_Product_Association(db.Model):
__tablename__ = 'set_product_association'
set_id = db.Column(db.Integer, db.ForeignKey('sets.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id'), primary_key=True)
quantity = db.Column(db.Integer)
product = db.relationship("Product", back_populates="sets")
set = db.relationship("Set", back_populates="products")
class Set(db.Model):
__tablename__ = 'sets'
id = db.Column(db.Integer, primary_key=True)
products = db.relationship("Set_Product_Association",
back_populates="set")
class Product(db.Model):
__tablename__= 'products'
id = db.Column(db.Integer, primary_key=True)
part_number = db.Column(db.String(100), unique=True, nullable=False)
sets = db.relationship("Set_Product_Association",
back_populates="product")
Edit:
I've also tried reversing the operation as suggested here:
s = Set.query.get(2)
a = Set_Product_Association()
a.quantity = 43
a.product = Product.query.get(3)
a.set = s
db.session.commit()
But I still get an error:
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: set_product_association.set_id, set_product_association.product_id [SQL: 'INSERT INTO set_product_association (set_id, product_id, quantity) VALUES (?, ?, ?)'] [parameters: (2, 3, 43)]
You get an integrity error because you are trying to create a new object with the same primary keys.
This:
a = Set_Product_Association(set=s, product=p, quantity=43)
Does not update, but create.
If you want to update the actual row in the table, you need to update the existing one:
assoc = Set_Product_Association.query.filter_by(set=s, product=p).first()
assoc.quantity = 43
db.session.commit()
Also, from the documentation it is advised to not use a model but an actual table.

Django query across foreign keys

I need to get a list of users who have been referenced as a ForeignKey in two other models. I'm still a little unclear on queries when they reach this complexity.
Models:
class Page(models.Model):
user = models.ForeignKey(User)
class EmailSent(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=200)
In English, what I want to get is: 10 active users who have 0 pages and have never had an email with the name check_in sent to them.
Here's where I am:
users = User.objects.filter(is_active=1).annotate(page_count=Count('pages')).filter(page_count=0)[10]
but not sure how to do what is essentially:
email_sent = EmailSent.objects.filter(user=user, name='check_in')
Any ideas?
One possible way to get what you want is:
users = User.objects.filter(is_active=1).annotate(page_count=Count('pages')).filter(page_count=0)
EmailSent.objects.filter(user__in=users, name='check_in')[10]
Another way is,
users = User.objects.filter(is_active=1).annotate(page_count=Count('pages')).filter(page_count=0)
users.emailsent_set.all()[10]
Try the following:
users = User.objects.filter(is_active=1)\
.exclude(emailsent__name='check_in')\
.extra(select={'page_count': "select count(*) from YOURAPP_page where user_id = auth_user.id"}, where=['page_count 0'])[:10]