django ORM - order by status value - django

I have a model with three statuses: draft, launching, launched. What I want is to display models in a particular order: first drafts, then launching ones, and then launched ones. I don't want to name my attributes as 0_draft, 1_launching, 2_launched as this will be a problem down the road; i need clear values in my DB.
Is there a way to annotate these values with some integers?
The ideal syntax for this would look like this:
def detect_status_number(campaign):
if campaign.status == 'draft':
return 1
... etc ...
cs = Campaign.objects.annotate(new_arg=lambda campaign: detect_status_number(campaign))
cs = cs.order_by('new_arg').all()
this obviously doesn't work; but is there a way to make this work?

I think this will work
cs = Campaign.objects.extra(
select={"weight_status":"case when campaign.status='draft' then 0 when campain.status = 'launching' then 1 else 2 end"}
).order_by('weight_status')

Although you could certainly make your approach work, let me recommend another approach: using the choices field and "enumeration". Here is an example:
STATUS_TYPE = (
(1, "draft"),
(2, "launching"),
(3, "launched"),
)
And then for your model field:
status = models.IntegerField(choices=STATUS_TYPE)
I realize that this may not be an acceptable answer if your model design is already set, but this is a good design pattern to know.

Related

Django - Days of the week (int) isn't rendering a string

I have a model in my app that stores the a single day of the week as:
DAYS = (
(0, 'Monday'),
(1, 'Tuesday'),
(2, 'Wednesday'),
(3, 'Thursday'),
(4, 'Friday')
)
day = models.IntegerField(validators=[MaxValueValidator(4), MinValueValidator(0)], blank=True, choices = DAYS)
According to the Django Documentation,:
The first element in each tuple is the actual value to be set on the model, and the second element is the human-readable name
However, that doesn't seem to be the case for me. For instance, in this template here in the same app, when I have something like:
{{q.day}}
This gives me: 1 instead of Tuesday.
I tried some suggestions from SO, including creating your own model, and even considered passing custom functions through Jinja2, though I feel this to be unnecessarily complex. What would be the best way to go about this. Do I not understand the functionality of this clearly?
Note: I want to store the day to be int because my app is running some complex algorithms and I want to just convert it for display purposes.
Try:
{{ q.get_day_display }}
(without parenthesis in the template)
https://docs.djangoproject.com/fr/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display
When you have a choice in you model field you must use {{object.get_attribut_display}} not {{object.attribut}}.
Don't forget to read documentation carefully.

How can I search the choice field using display name?

I want to query the item from the database using django SmallIntegerField. The code I used is
Here is the class defination:
class Equipment(models.Model):
asset_status = (
(0, 'online'),
(1, 'offline'),
(2, 'unknown'),
(3, 'wrong'),
(4, 'x'),
)
status = models.SmallIntegerField(
choices=asset_status, default=0, verbose_name='Device Status')
The query code I used is
def filter_queryset(self, qs):
sSearch = self.request.GET.get('search[value]', None)
print(sSearch)
if sSearch:
qs = qs.filter(Q(status__icontains=sSearch))
return qs
I want to query this column by 'online', 'offline' and so on. Do you know how to do this?
The reference I have searched are
Search choice field by the name in django - python
I also see the Choices API in
https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
But there is no Q. I am not sure whether it works. Could you please tell me about this?
And I searched for a method called get_FOO_display, but I don't think it can be solved. Neither did I think it can be combined with Q.
Thanks
Choices follow the format of a actual_value_for_db, human_readable_name tuple, where the human_readable_name is only for display purposes. That is, you cannot query for it as only the first item of the tuple is stored in the database.
If you want to make it a bit easier to find out what value you need to use you can use an enum-like declaration of the choices:
class Equipment(models.Model):
ONLINE = 0
OFFLINE = 1
UNKNOWN = 2
WRONG = 3
X = 4
asset_status = (
(ONLINE, 'online'),
(OFFLINE, 'offline'),
(UNKNOWN, 'unknown'),
(WRONG, 'wrong'),
(X, 'x'),
)
Then you can query for a choice by using a 'name' of the member of the Equipment model: Equipment.objects.filter(status=Equipment.OFFLINE)
It is not clear what exactly the value in your request is and where it comes from. I would suggest just using a ModelForm to ask the user for the right status type. A ModelForm should take care of the choices for that formfield and return the right type for the selected choice (which should be an integer, not a string!). Use a generic FormView and the rest should be a piece of cake.
status is a SmallIntegerField so, what you have in the database are integers. When you provide code like this:
asset_status = (
(0, 'online'),
(1, 'offline'),
(2, 'unknown'),
(3, 'wrong'),
(4, 'x'),
)
you're telling Django: "hey, I want to be able to display the 0 option value as 'online' , 1 as 'offline' ..." and so on.
So, you Django don't write such values ('online', 'offline', ...) to the database hence you can't query on them.
But ...
I suppose you are having trouble getting the right integer value for the query after the user selects one of the options for filtering, you should take a look at ChoiceField, perhaps you can use it for your filter form.

Django possible to nest prefetch related?

Suppose I have the following models.
class Item(models.Model):
seller = models.ForeignKey('seller.Seller')
class ItemSet(models.Model):
items = models.ManyToManyField("Item", related_name="special_items")
What I'd like to do is
For a given seller, retrieve all items which is stored as special_items in ItemSet.
The following code is what I come up with, havent tried, just a hunch it won't work. :(
(I want to retrieve item_founds and item_specials)
item_founds = Item.objects.filter(seller=seller).prefetch_related(
Prefetch(
"special_items",
queryset=Items.objects.prefetch_related("items")
)
)
item_specials = Item.objects.none()
for item_found in item_founds.all():
for special_item in item_found.special_items.all():
item_specials |= special_item.items.all()
In Django you must always perform your queries from the model that you want in the end. In your case that's seller.Seller. Otherwise you get both poor code and risk bad performance (like in your code where you have two lookups inside a loop...)
Seller.objects.get(pk=<seller id here>).prefetch_related(
'items',
queryset=Item.objects.exclude(
special_items=None
),
to_attr='item_specials'
)
Which should give you a Seller with a set of special items in item_specials attribute.

Django compare values of two objects

I have a Django model that looks something like this:
class Response(models.Model):
transcript = models.TextField(null=True)
class Coding(models.Model):
qid = models.CharField(max_length = 30)
value = models.CharField(max_length = 200)
response = models.ForeignKey(Response)
coder = models.ForeignKey(User)
For each Response object, there are two coding objects with qid = "risk", one for coder 3 and one for coder 4. What I would like to be able to do is get a list of all Response objects for which the difference in value between coder 3 and coder 4 is greater than 1. The value field stores numbers 1-7.
I realize in hindsight that setting up value as a CharField may have been a mistake, but hopefully I can get around that.
I believe something like the following SQL would do what I'm looking for, but I'd rather do this with the ORM
SELECT UNIQUE c1.response_id FROM coding c1, coding c2
WHERE c1.coder_id = 3 AND
c2.coder_id = 4 AND
c1.qid = "risk" AND
c2.qid = "risk" AND
c1.response_id = c2.response_id AND
c1.value - c2.value > 1
from django.db.models import F
qset = Coding.objects.filter(response__coding__value__gt=F('value') + 1,
qid='risk', coder=4
).extra(where=['T3.qid = %s', 'T3.coder_id = %s'],
params=['risk', 3])
responses = [c.response for c in qset.select_related('response')]
When you join to a table already in the query, the ORM will assign the second one an alias, in this case T3, which you can using in parameters to extra(). To find out what the alias is you can drop into the shell and print qset.query.
See Django documentation on F objects and extra
Update: It seems you actually don't have to use extra(), or figure out what alias django uses, because every time you refer to response__coding in your lookups, django will use the alias created initially. Here's one way to look for differences in either direction:
from django.db.models import Q, F
gt = Q(response__coding__value__gt=F('value') + 1)
lt = Q(response__coding__value__lt=F('value') - 1)
match = Q(response__coding__qid='risk', response__coding__coder=4)
qset = Coding.objects.filter(match & (gt | lt), qid='risk', coder=3)
responses = [c.response for c in qset.select_related('response')]
See Django documentation on Q objects
BTW, If you are going to want both Coding instances, you have an N + 1 queries problem here, because django's select_related() won't get reverse FK relationships. But since you have the data in the query already, you could retrieve the required information using the T3 alias as described above and extra(select={'other_value':'T3.value'}). The value data from the corresponding Coding record would be accessible as an attribute on the retrieved Coding instance, i.e. as c.other_value.
Incidentally, your question is general enough, but it looks like you have an entity-attribute-value schema, which in an RDB scenario is generally considered an anti-pattern. You might be better off long-term (and this query would be simpler) with a risk field:
class Coding(models.Model):
response = models.ForeignKey(Response)
coder = models.ForeignKey(User)
risk = models.IntegerField()
# other fields for other qid 'attribute' names...

In Django, how can I order by a multiple-choice CharField in arbitrary not alphabetical order?

Imagine a model Shirts with a size CharField, with values limited to a small number of choices, e.g. 'small', 'medium', 'large', 'xlarge' etc.
To get the shirts grouped by size, you'd do:
Shirts.objects.order_by('size')
But Django will (naturally) order the groups alphabetically, i.e. 'large' then 'medium' then 'small' then 'xlarge'. What I want is to have 'small' before 'medium' before 'large' etc.
I.e. what I naturally want to do is something like the following pseudocode:
size_order = {'small': 1, 'medium': 2, 'large': 3, 'xlarge': 4}
Shirts.objects.order_by('size_order[size]')
What's the best way to accomplish this?
EDIT: See my comments to answers below for thoughts on various suggested approaches. I've stumbled on a custom Manager/QuerySet approach using the SQL ORDER BY CASE syntax which I'm investigating.
I figured out the closest thing to what I'm looking for, which is to use QuerySet.extra() method to take advantage of SQL's CASE WHEN/THEN syntax, which Django doesn't support directly:
CASE_SQL = '(case when size="small" then 1 when size="medium" then 2 when size="large" then 3 when size="xlarge" then 4 end)'
Shirt.objects.extra(select={'shirt_order': CASE_SQL}, order_by=['shirt_order'])
This may well seem overkill and/or mucky given my (artificial) example, but it's the trick I was looking for! Thanks to everyone for the other perfectly valid approaches to this problem, which somehow indirectly sparked me to figure out this approach.
P.S. It's tempting to create a custom model Manager/QuerySet combo that provides a more native Django-interface for this sort of custom ordering via SQL's CASE WHEN/THEN syntax, but I'll leave that as a homework assignment for myself for another time!
NOTE: The syntax for the CASE WHEN/THEN is database-specific. The syntax above is for SQLite. For PostgreSQL, omit the parentheses and use escaped single quotes instead of double quotes.
It sounds like you don't want to hard-code the possible choices (because you used a charfield), but at the same time you say there are a small number of choices.
If you are content to hard-code the choices then you could change to an integerfield instead:
class Shirt(models.Model):
SIZE_CHOICES = (
(1, u'small'),
(2, u'medium'),
(3, u'large'),
(4, u'x-large'),
(5, u'xx-large'),
)
size = models.IntegerField(choices = SIZE_CHOICES)
If you don't want to hard-code the size choices then you probably want to move the available sizes out to a separate model and reference it as a foreignkey from your Shirt model. To make it arbitrarily sortable you would need an index of some sort other than the primary key that you can sort on. Maybe something like this:
class Size(models.Model):
sortorder = models.IntegerField()
name = models.CharField()
class Meta:
ordering = ['sortorder']
You should set up your size field with choice tuples ordered the way you want them. In your models.py you'd have something like:
from django.db import models
SHIRT_SIZE_CHOICES = (
(u"0", u"small"),
(u"1", u"medium"),
(u"2", u"large"),
(u"3", u"xlarge"))
class Shirt(models.Model):
...
size = models.CharField(max_length=2, choices=SHIRT_SIZE_CHOICES)
Then order_by will sort them as you intend.
Without writing a custom sorting function, just tack on an order field to the model.
class Shirt(models.Model):
...
order = models.IntegerField(default=0)
class Meta:
ordering = ('order',)
Shirt.objects.filter(name='Awesome Shirt')
Or, more appropriately, create a new model called Size.
If you don't want to store the field values as integers, then the built in order_by() method won't be able to handle your custom case. You'll have to create a function of your own to sort the data once you've retrieved it.
And to do that, of course the best way would be to map your arbitrary values to integers in respective order and then sort by that arrangement :).