Unable to use Case, When, Then in django queryset - python

my models are as follows
class Loan(models.Model):
loan_value = models.IntegerField()
channel_seller = models.ForeignKey(ChannelSeller, on_delete=models.CASCADE)
class ChannelSeller(models.Model):
LEVEL_CHOICES = (
('1','Business Consultants'),
('2','Unit Managers'),
('3','Account Managers'),
('4','Branch Managers'),
)
level = models.CharField(max_length=2, choices = LEVEL_CHOICES, null = True, blank = True)
pin_no = models.CharField(max_length=255)
unit_manager = models.ForeignKey('self', limit_choices_to = {"level": '2'}, on_delete=models.DO_NOTHING, null = True, blank = True, related_name='unit_manager_2')
Loan can be created by business_consultant or unit_manager. Each business_consultant will have a unit_manager, however, unit_manager will have unit_manager as blank
with that said,
I'm trying to sort my query by unit_manager field using case, when, then as follows
transactions = Loan.objects.annotate(unit_manager_pin = Case(When('channel_seller__level' == '2', then='channel_seller_pin_no'), When('channel_seller__level' == '1', then='channel_seller__unit_manager__pin_no'))).filter(channel_seller__level__in = ['1','2']).order_by('channel_seller__level')
This query however throws me error __init__() takes either a Q object or lookups as keyword arguments

The condition needed to be applied is like a filter and also get the value using F function. Once try this.
transactions = Loan.objects.annotate(unit_manager_pin = Case(When(channel_seller__level='2', then=F('channel_seller__pin_no')), When(channel_seller__level='1', then=F('channel_seller__unit_manager__pin_no')))).filter(channel_seller__level__in = ['1','2']).order_by('channel_seller__level')

You are coding up a query to be executated by the DBM. You can't use Python code in that context ('channel_seller__level' == '2' ). See the doc on conditional expressions.
Please format your code legibly! Use line breaks inside brackets to continue.
In this case, you just change == to =, but it's important to understand why. The argument to When is similar to the argument to .filter. So to test for greater-than, it would be When( field__gt = 2, ...)
You also need to use F to refer to the value in the database. Without F it would (probably, not sure) be an error. (If you wanted a fixed value you would use Value('fixed-value'))
transactions = Loan.objects.annotate(unit_manager_pin =
Case(When('channel_seller__level' = '2',
then=F('channel_seller_pin_no')),
When('channel_seller__level' = '1',
then=F('channel_seller__unit_manager__pin_no'))
)
).filter(
...

Related

Update field with Django ORM based on computed value

I have a basic model:
class MyModel(Model):
id = models.IntegerField()
is_admin = models.BooleanField()
What I want to achieve is to update the value of the is_admin field for the entire table at once, based on whether or not the id value is in a certain list of values.
Basically, in raw SQL, this is the query I want:
UPDATE my_model
SET is_admin = (id IN (1, 2, 3, 4))
How can I achieve this with Django's ORM?
This is what I tried so far:
from django.db.models import F, Value
admin_ids = (1, 2, 3, 4)
MyModel.objects.update(is_admin=F("id") in admin_ids)
# Resulting query is:
# UPDATE my_model SET is_admin = false
MyModel.objects.update(is_admin=F("id") in Value(admin_ids))
# TypeError: argument of type 'Value' is not iterable
MyModel.objects.filter(id__in=admin_ids).update(admin=True)
MyModel.objects.exclude(id__in=admin_ids).update(admin=False)
# it works... but can I do this in a single query instead of two?
I'm using Django 3.2 and PostgreSQL.
You can use a CASE / WHEN construction.
https://docs.djangoproject.com/en/3.2/ref/models/conditional-expressions/
MyModel.objects.update(
is_admin=Case(
When(id__in=admin_ids, then=Value(True)),
default=Value(False)
)
)
P.S. If you need this kind of queries a lot, you can use the following custom expression (also useful for annotations). Though be careful, I've had it break once for one query, maybe due to the old version of Django used. On other projects, I am using this in production without any issues.
class BooleanQ(ExpressionWrapper):
output_field = BooleanField()
def __init__(self, *args, **kwargs):
expression = models.Q(*args, **kwargs)
super().__init__(expression, output_field=None)
def as_sql(self, compiler, connection):
try:
return super().as_sql(compiler, connection)
except EmptyResultSet:
return compiler.compile(Value(False))
MyModel.objects.update(is_admin=BooleanQ(id__in=admin_ids))

How do I display Django data from a related model of a related model?

I am trying to display data from several models that are related together through a QuerySet. My ultimate goal is to display some information from the Site model, and some information from the Ppack model, based on a date range filter of the sw_delivery_date in the Site model.
Here are my models:
class Site(models.Model):
mnemonic = models.CharField(max_length = 5)
site_name = models.CharField(max_length = 100)
assigned_tech = models.ForeignKey('Person', on_delete=models.CASCADE, null = True, blank = True)
hw_handoff_date = models.DateField(null = True, blank = True)
sw_delivery_date = models.DateField(null = True, blank = True)
go_live_date = models.DateField(null = True, blank = True)
web_url = models.CharField(max_length = 100, null = True, blank = True)
idp_url = models.CharField(max_length = 100, null = True, blank = True)
def __str__(self):
return '(' + self.mnemonic + ') ' + self.site_name
class Ring(models.Model):
ring = models.IntegerField()
def __str__(self):
return "6." + str(self.ring)
class Ppack(models.Model):
ppack = models.IntegerField()
ring = models.ForeignKey('Ring', on_delete=models.CASCADE)
def __str__(self):
return str(self.ring) + " pp" + str(self.ppack)
class Code_Release(models.Model):
Inhouse = 'I'
Test = 'T'
Live = 'L'
Ring_Location_Choices = (
(Inhouse, 'Inhouse'),
(Test, 'Test'),
(Live, 'Live'),
)
site_id = models.ForeignKey('Site', on_delete=models.CASCADE)
type = models.CharField(max_length = 1, choices = Ring_Location_Choices, blank = True, null = True)
release = models.ForeignKey('Ppack', on_delete=models.CASCADE)
def __str__(self):
return "site:" + str(self.site_id) + ", " + self.type + " = " + str(self.release)
If I use the following,
today = datetime.date.today()
future = datetime.timedelta(days=60)
new_deliveries = Site.objects.select_related().filter(sw_delivery_date__range=[today, (today + future)])
I can get all of the objects in the Site model that meet my criteria, however, because there is no relation from Site to Code_Release (there's a one-to-many coming the other way), I can't get at the Code_Release data.
If I run a for loop, I can iterate through every Site returned from the above query, and select the data from the Code_Release model, which allows me to get the related data from the Ppack and Ring models.
site_itl = {}
itl = {}
for delivery in new_deliveries:
releases = Code_Release.objects.select_related().filter(site_id = delivery.id)
for rel in releases:
itl[rel.id] = rel.release
site_itl[delivery.id] = itl
But, that seems overly complex to me, with multiple database hits and possibly a difficult time parsing through that in the template.
Based on that, I was thinking that I needed to select from the Code_Release model. That relates back to both the Site model and the Ppack model (which relates to the Ring model). I've struggled to make the right query / access the data in this way that accomplishes what I want, but I feel this is the right way to go.
How would I best accomplish this?
You can use RelatedManager here. When you declare ForeignKey, Django allows you to access reverse relationship. To be specific, let's say that you have multiple code releases that are pointing to one specific site. You can access them all via site object by using <your_model_name_lowercase>_set attribute. So in your case:
site.code_release_set.all()
will return QuerySet of all code release objects that have ForeignKey to object site
You can access the Releases from a Site object. First, you can put a related_name to have a friendly name of the reverse relation between the models:
site_id = models.ForeignKey('Site', on_delete=models.CASCADE, related_name="releases")
and then, from a Site object you can make normal queries to Release model:
site.releases.all()
site.releases.filter(...)
...

Iterating with Django ORM through large datasets is slow

I'm using Django ORM to get data out of a database with a few million items. However, computation takes a while (40 minutes+), and I'm not sure how to pin point where the issue is located.
Models I've used:
class user_chartConfigurationData(models.Model):
username_chartNum = models.ForeignKey(user_chartConfiguration, related_name='user_chartConfigurationData_username_chartNum')
openedConfig = models.ForeignKey(user_chartConfigurationChartID, related_name='user_chartConfigurationData_user_chartConfigurationChartID')
username_selects = models.CharField(max_length=200)
blockName = models.CharField(max_length=200)
stage = models.CharField(max_length=200)
variable = models.CharField(max_length=200)
condition = models.CharField(max_length=200)
value = models.CharField(max_length=200)
type = models.CharField(max_length=200)
order = models.IntegerField()
def __unicode__(self):
return str(self.username_chartNum)
order = models.IntegerField()
class data_parsed(models.Model):
setid = models.ForeignKey(sett, related_name='data_parsed_setid', primary_key=True)
setid_hash = models.CharField(max_length=100, db_index = True)
block = models.CharField(max_length=2000, db_index = True)
username = models.CharField(max_length=2000, db_index = True)
time = models.IntegerField(db_index = True)
time_string = models.CharField(max_length=200, db_index = True)
def __unicode__(self):
return str(self.setid)
class unique_variables(models.Model):
setid = models.ForeignKey(sett, related_name='unique_variables_setid')
setid_hash = models.CharField(max_length=100, db_index = True)
block = models.CharField(max_length=200, db_index = True)
stage = models.CharField(max_length=200, db_index = True)
variable = models.CharField(max_length=200, db_index = True)
value = models.CharField(max_length=2000, db_index = True)
class Meta:
unique_together = (("setid", "block", "variable", "stage", "value"),)
The code I'm running is looping through data_parsed, with relevant data that matches between user_chartConfigurationData and unique_variables.
#After we get the tab, we will get the configuration data from the config button. We will need the tab ID, which is chartNum, and the actual chart
#That is opened, which is the chartID.
chartIDKey = user_chartConfigurationChartID.objects.get(chartID = chartID)
for i in user_chartConfigurationData.objects.filter(username_chartNum = chartNum, openedConfig = chartIDKey).order_by('order').iterator():
iterator = data_parsed.objects.all().iterator()
#We will loop through parsed objects, and at the same time using the setid (unique for all blocks), which contains multiple
#variables. Using the condition, we can set the variable gte (greater than equal), or lte (less than equal), so that the condition match
#the setid for the data_parsed object, and variable condition
for contents in iterator:
#These are two flags, found is when we already have an entry inside a dictionary that already
#matches the same setid. Meaning they are the same blocks. For example FlowBranch and FlowPure can belong
#to the same block. Hence when we find an entry that matches the same id, we will put it in the same dictionary.
#Added is used when the current item does not map to a previous setid entry in the dictionary. Then we will need
#to add this new entry to the array of dictionary (set_of_pk_values). Otherwise, we will be adding a lot
#of entries that doesn't have any values for variables (because the value was added to another entry inside a dictionary)
found = False
added = False
storeItem = {}
#Initial information for the row
storeItem['block'] = contents.block
storeItem['username'] = contents.username
storeItem['setid'] = contents.setid
storeItem['setid_hash'] = contents.setid_hash
if (i.variable != ""):
for findPrevious in set_of_pk_values:
if(str(contents.setid) == str(findPrevious['setid'])):
try:
items = unique_variables.objects.get(setid = contents.setid, variable = i.variable)
findPrevious[variableName] = items.value
found = True
break
except:
pass
if(found == False):
try:
items = unique_variables.objects.get(setid = contents.setid, variable = i.variable)
storeItem[variableName] = items.value
added = True
except:
pass
if(found == False and added == True):
storeItem['time_string'] = contents.time_string
set_of_pk_values.append(storeItem)
I've tried to use select_related() or prefetch_related(), since it needs to go to unique_variables object and get some data, however, it still takes a long time.
Is there a better way to approach this problem?
Definitely, have a look at django_debug_toolbar. It will tell you how many queries you execute, and how long they last. Can't really live without this package when I have to optimize something =).
PS: Execution will be even slower.
edit: You may also want to enable db_index for the fields you use to filter with or index_together for more than one field. Ofc, measure the times between your changes so you make sure which option is better.

How to filter in many-to-many relationship in Django

Since I could not find the exact answer for me, I decided to ask.
I have the following two models:
class Schedule(models.Model):
     transport = models.ForeignKey(Transport)
     stop = models.ForeignKey(Stop)
     from_to_stop = models.ForeignKey(Direction)
     time = models.ManyToManyField(TimeTable)
     type_day = models.ForeignKey(TypeDay)
     created = models.DateTimeField(_('Created at'), auto_now_add = True)
     updated = models.DateTimeField(_('Modified at'), auto_now = True)
class TimeTable(models.Model):
     time_arrival = models.TimeField()
     created = models.DateTimeField(_('Created at'), auto_now_add = True)
     updated = models.DateTimeField(_('Modified at'), auto_now = True)
A script that must be dynamically added things in 'time' field on the 'Schedule'. Here's what I do:
time = self.get_or_create_time(time)
count = Schedule.objects.filter(time__in = [time]).count()
if not count:
schedule.time.add(time)
def get_or_create_time(self, time):
obj, created = TimeTable.objects.get_or_create(time_arrival=time)
return obj
Here I'm not getting the required result for me, because it searches the entire table if there is such a connection somewhere. I want to see if there is such a link only for the current object ' schedule ' is the current object. All I want is for him to see whether there is a connection with the ' TimeTable '. How to do this in Django?
For a start, there is no point in using __in with a single element which you then wrap in a list. The fact that you had to do that should have given you a hint that you are using the wrong predicate: just use the default (which is __eq, but since it's the default you can leave it out altogether).
Also, if you only want to know if an object exists, use .exists() rather than .count(), as the latter is a more expensive query if there can be multiple objects.
But basically your problem is simple. You want to filter the times belonging to a schedule: so, start with that schedule, not the whole Schedule model.
exists = self.times.filter(time_arrival=time.time_arrival).exists()

I need to query for a set of objects whose primary keys are contained inside of a list

As the title says, I need a way to perform this query. I have tried the following:
user_list_ids = []
user_lists = []
user_entries = OwnerEntry.objects.filter(name=request.user)
for user in user_entries:
user_list_ids.append(user.list_id)
user_lists = ListEntry.objects.filter(id__in=user_list_ids)
for user in user_entries:
user_list_ids.append(user.list_id)
user_lists = ListEntry.objects.filter(id__in=user_list_ids)
However, I get an error on the last line: int() argument must be a string or a number, not 'ListEntry'
Here are the relevant models:
class OwnerEntry(models.Model):
name = models.CharField(max_length=32)
list_id = models.ForeignKey(ListEntry)
class Meta:
ordering = ('name',)
class ListEntry(models.Model):
name = models.CharField(max_length=64)
# active_date = models.DateTimeField('date of last list activity')
expire_date = models.DateField('date of expiration')
create_date = models.DateField('date created')
to answer your question directly, please note that you have a list_id rather than list as a ForeignKey name (OwnerEntry model). In order to actually extract the fk value, you should use list_id_id instead (or rename list_id to list ;))
Please also note that django supports object references, like so:
someowner = OwnerEntry.objects.get( ... )
ownerslist = someowner.listentry_set.all()
cheers!
You can define OwnerEntry's foreign key to ListEntry as :
list_id = models.ForeignKey(ListEntry, related_query_name='owner_entry')
and then do this one-liner in your code:
user_lists = ListEntry.objects.filter(owner_entry__name=request.user)
What this does is exactly filter every ListEntry which has at least one owner_entry whose name is equal to request.user's.
The redefinition of the foreign key is just for the sake of giving a nice name to the query attribute.
For more details on queries that work with backward relationships: https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships

Categories

Resources