How can I search the choice field using display name? - django

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.

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.

Django ORM. Filter many to many with AND clause

With the following models:
class Item(models.Model):
name = models.CharField(max_length=255)
attributes = models.ManyToManyField(ItemAttribute)
class ItemAttribute(models.Model):
attribute = models.CharField(max_length=255)
string_value = models.CharField(max_length=255)
int_value = models.IntegerField()
I also have an Item which has 2 attributes, 'color': 'red', and 'size': 3.
If I do any of these queries:
Item.objects.filter(attributes__string_value='red')
Item.objects.filter(attributes__int_value=3)
I will get Item returned, works as I expected.
However, if I try to do a multiple query, like:
Item.objects.filter(attributes__string_value='red', attributes__int_value=3)
All I want to do is an AND. This won't work either:
Item.objects.filter(Q(attributes__string_value='red') & Q(attributes__int_value=3))
The output is:
<QuerySet []>
Why? How can I build such a query that my Item is returned, because it has the attribute red and the attribute 3?
If it's of any use, you can chain filter expressions in Django:
query = Item.objects.filter(attributes__string_value='red').filter(attributes__int_value=3')
From the DOCS:
This takes the initial QuerySet of all entries in the database, adds a filter, then an exclusion, then another filter. The final result is a QuerySet containing all entries with a headline that starts with “What”, that were published between January 30, 2005, and the current day.
To do it with .filter() but with dynamic arguments:
args = {
'{0}__{1}'.format('attributes', 'string_value'): 'red',
'{0}__{1}'.format('attributes', 'int_value'): 3
}
Product.objects.filter(**args)
You can also (if you need a mix of AND and OR) use Django's Q objects.
Keyword argument queries – in filter(), etc. – are “AND”ed together. If you need to execute more complex queries (for example, queries with OR statements), you can use Q objects.
A Q object (django.db.models.Q) is an object used to encapsulate a
collection of keyword arguments. These keyword arguments are specified
as in “Field lookups” above.
You would have something like this instead of having all the Q objects within that filter:
** import Q from django
from *models import Item
#assuming your arguments are kwargs
final_q_expression = Q(kwargs[1])
for arg in kwargs[2:..]
final_q_expression = final_q_expression & Q(arg);
result = Item.objects.filter(final_q_expression)
This is code I haven't run, it's out of the top of my head. Treat it as pseudo-code if you will.
Although, this doesn't answer why the ways you've tried don't quite work. Maybe it has to do with the lookups that span relationships, and the tables that are getting joined to get those values. I would suggest printing yourQuerySet.query to visualize the raw SQL that is being formed and that might help guide you as to why .filter( Q() & Q()) is not working.

Django get count of each age

I have this model:
class User_Data(AbstractUser):
date_of_birth = models.DateField(null=True,blank=True)
city = models.CharField(max_length=255,default='',null=True,blank=True)
address = models.TextField(default='',null=True,blank=True)
gender = models.TextField(default='',null=True,blank=True)
And I need to run a django query to get the count of each age. Something like this:
Age || Count
10 || 100
11 || 50
and so on.....
Here is what I did with lambda:
usersAge = map(lambda x: calculate_age(x[0]), User_Data.objects.values_list('date_of_birth'))
users_age_data_source = [[x, usersAge.count(x)] for x in set(usersAge)]
users_age_data_source = sorted(users_age_data_source, key=itemgetter(0))
There's a few ways of doing this. I've had to do something very similar recently. This example works in Postgres.
Note: I've written the following code the way I have so that syntactically it works, and so that I can write between each step. But you can chain these together if you desire.
First we need to annotate the queryset to obtain the 'age' parameter. Since it's not stored as an integer, and can change daily, we can calculate it from the date of birth field by using the database's 'current_date' function:
ud = User_Data.objects.annotate(
age=RawSQL("""(DATE_PART('year', current_date) - DATE_PART('year', "app_userdata"."date_of_birth"))::integer""", []),
)
Note: you'll need to change the "app_userdata" part to match up with the table of your model. You can pick this out of the model's _meta, but this just depends if you want to make this portable or not. If you do, use a string .format() to replace it with what the model's _meta provides. If you don't care about that, just put the table name in there.
Now we pick the 'age' value out so that we get a ValuesQuerySet with just this field
ud = ud.values('age')
And then annotate THAT queryset with a count of age
ud = ud.annotate(
count=Count('age'),
)
At this point we have a ValuesQuerySet that has both 'age' and 'count' as fields. Order it so it comes out in a sensible way..
ud = ud.order_by('age')
And there you have it.
You must build up the queryset in this order otherwise you'll get some interesting results. i.e; you can't group all the annotates together, because the second one for count depends on the first, and as a kwargs dict has no notion of what order the kwargs were defined in, when the queryset does field/dependency checking, it will fail.
Hope this helps.
If you aren't using Postgres, the only thing you'll need to change is the RawSQL annotation to match whatever database engine it is that you're using. However that engine can get the year of a date, either from a field or from its built in "current date" function..providing you can get that out as an integer, it will work exactly the same way.

django ORM - order by status value

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.

Get django field "choice" from arbitrary value

I have a django class like this:
class my_thing(models.Model):
AVAILABLE = 1
NOT_AVAILABLE = 2
STATUSES = (
(AVAILABLE, "Available"),
(NOT_AVAILABLE, "Not available")
)
status = models.IntegerField(...., choices = STATUSES)
In another bit of code I have the number corresponding to a status, but due to some legacy code I need to compare it via strings (and I don't want to hard code it anywhere other than the model definition - DRY)
So in the code I have the number "1" and I want to get the text "Available".
My current (awful) workaround is to do the following:
status_getter = my_thing()
my_thing.status = my_thing.AVAILABLE
comparison_str = status_getter.get_status_display()
Is there a better/builtin way to directly access the string value for the field's choices given that I don't have an object of that type already instantiated? I could write a function
def get_status_on_value(self, value):
for tup in STATUSES:
if tup[0] == value:
return tup[1]
But I have a sneaking suspicion django has a built-in way to do this
Not really. Your best bet is to convert the CHOICES tuple to a dict and do a lookup:
status_dict = dict(my_thing.STATUSES)
return status_dict[value]