Get django field "choice" from arbitrary value - django

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]

Related

Adding an annotation to django queryset to be used by django_tables2

I have a model that has (among other fields) one value for the current price of an item and one value for the usual price of an item. I'd like to include a field for the percentage saving. I've done this using the #property:
#property
def percSaving(self):
aval = self.stickerprice
bval = self.currentprice
if self.stickerprice > 0:
return "%0.2f" % ((aval-bval)/aval*100) + "%"
elif self.currentprice == 0:
return "Always Free"
else:
return "100% OFF"
This works, I can add this column to my django_table2 table with:
percSaving = tables.Column(verbose_name='% Saving')
Super easy and all good. However, I am unable to sort by this column. This is because it's not one of the columns of data from the query set. So I've been trying to annotate the query set to allow for this ordering I've based by annotation attempt on this queryset api reference page and have produced this:
annoed = products.objects.annotate(percSaving=((stickerprice)-(currentprice))/(stickerprice))
However this gives me an error of "name 'stickerprice' is not defined" which I thought might be because of not using inverted commas around the field names, but I tried that and got an error saying "unsupported operand type(s) for -: 'str' and 'str'" - basically using the inverted commas forces it to view the field names as strings.
What am I doing wrong? How can I annotate a query set to allow for ordering by a column I have defined as above!
Yes, you can. With providing some more code like Model etc. I can only make some general advise.
You should use F function to calculate in the query like:
annoed = products.objects.annotate(
percSaving=((F("stickerprice") - F("currentprice")) / F("stickerprice"))
)
Check also How to make sum query with type casting and calculation in django views? which provides also some type casting hints.

Getting a value from a different class in django

As new to both Python and Django I have encounterd what I think is a simple problem, but which does not want to go away. I have a "table" with factors obtained from
class TjgFaktor(models.Model):
typ = models.CharField(max_length=2)
factor = models.FloatField()
Next I have another class which is foreign-keyed to this:
class Moment(models.Model):
typ = models.ForeignKey(TjgFaktor,on_delete=models.SET_NULL,null=True)
Now, what I want to do is to get the factor from the first class as an attribut to Moment. I have tried
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
in the hope of getting the correct factor. However, when I do something like
person_moment = Moment.objects.all()
for e in person_moment:
print(e.factor())
what I get is "TjgFaktor matching query does not exist".
So how should I do this? I guess it is the function: it works if I replace type=self with pk=1.
You do not need to obtain the TjgFaktor through an explicit query. If you query for some_moment.typ, Django itself will perform an implcit query to fetch the TjgFaktor that corresponds to the Moment (through the foreign key), or None, if the foreign key is set to None.
We can thus query like:
def factor(self):
tjgfaktor = self.typ
if tjgfaktor:
return tjgfaktor.factor
In case there is no related TjgFaktor, then this function will return None as well.
In case you define a large amount of values, then this fetch might be inefficient: Django will fetch all columns from the database, and since we are only interested in a single one, this will thus result in some overhead.
We can avoid that by using the following query:
def factor(self):
if self.typ_id:
return (TjgFaktor.objects.values_list('factor', flat=True)
.get(pk=self.typ_id))
Assuming factor is function within Moment class, you can access factor if Moment object has related TjgFaktor object:
def factor(self):
return self.typ.factor if self.typ else None
So, the in the factor method, you need to enter the value for typ as a string value like this: A self does not satisfy the conditions of a string parameter that is required. You could do something like this -
def factor(self):
return TjgFaktor.objects.get(typ="YOUR_TYPE_IN_STRING").factor
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
You can't actually compare the object of Moment with objects in TjgFaktor. You can directly access the values of parent model or foreignkey directly by doing like this.
e.typ.factor #this will directly give you factor values of foreign key.
Or you can compare with
def factor(self):
return TjgFaktor.objects.get(typ=self.typ.id).factor

Django Postgres JSONField query

I have a class with a json field in it
class A(models.Model)
brand = JSONField()
If I post an array of JSON like [{'brand_id:1', 'name':'b1'}, {'brand_id:2', 'name':'b2'}] it is stored as an array of JSON. This works fine.
How should I query so as to check if '1' is present in the brand_id of any dictionary in that array?
Well first of all your JSON here is malformed. I presume it is meant to be:
[{'brand_id': 1, 'name': 'b1'}, {'brand_id': 2, 'name': 'b2'}]
If that's the case, to test for 1 in such a blob, something like this will tell you if 1 is to be found anywhere in the JSON as a value:
def check_for_one(json_data):
return any([1 in data.values() for data in json_data])
But you want to know specifically if 1 is a value owned by a key
brand_id anywhere in the JSON so you can also use a loop to add in some extra conditions:
def check_for_one(json_data):
match = []
for data in json_data:
for key, value in data.items():
if key == 'brand_id' and value == 1:
match.append(True)
return any(match)
You can incorporate such logic as methods on your model class like this:
class A(models.Model):
brand = JSONField()
def check_for_one_comprehension(self):
return any([1 in data.values() for data in self.brand])
def check_for_one_loop(self):
match = []
for data in self.brand:
for key, value in data.items():
if key == 'brand_id' and value == 1:
match.append(True)
return any(match)
But, if you actually want to filter instances from the database where the JSON data is an array at the top level and brand_id == 1, that needs a different approach, and this should do it:
A.objects.filter(brand__contains=[{'brand_id': 1}])
Note the additional [{}] braces! If you just call contains=['brand_id': 1] it will throw a syntax error and if you call contains={'brand_id': 1} it will not match.
This worked:
A.objects.filter(brands__contains=[{'brand_id':1}])
I didnt check it first by using the array.
Link pointed put by #Bear Brown gives sufficient info.
Its easy in django, but finding it out took time :P.

Django Query Result Is NoneType?

I have this query that looks like this:
blah = Blah.objects.filter(foo=title)
It works fine, so I can use the results in a template. BUT, I need to change one of the values in it, from an integer to a timedelta so it will display a time in the template output. Having trouble doing this, I wondered why type the result is & discovered that, type(blah), tells me that it's a NoneType, even tho it actually has usuable values. That's very puzzling, and I couldn't find anything in the Django documentation about this.
I tried using blah['length'], to get my specific integer, but that didn't work. So it appears the type isn't a dict, set, or list.
So, what type is a Django query result and why does it tell me it's a NoneType even tho it isn't?
I'm really scratching my head on this one.
Django querysets are of their own type. The idea for them is to retrieve sets of objects and for that they provide Django's public queryset API (docs). As a result of that, it's probably not a good idea to change any queryset attributes since there is no guarantee future Django releases will have the same attributes. You can however change attributes of model instances they return:
qs = FooModel.objects.all()
for model in qs:
model.attr = 'foo'
So in your case you can do something like:
# assuming you allow your integer field to be null
class FooModel(models.Model):
time = models.IntegerField(null=True)
qs = FooModel.objects.all()
for model in qs:
# handle case when time is null - model.time will be None
t = model.time or 90
model.time = timedelta(seconds=t)
If however you only need to retreive one object (model instance), then you should probably use get queryset method instead of filter:
foo = FooModel.objects.get(...)
foo.time = timedelta(seconds=foo.time)

Filter by certain pattern in all CharField in Model

I got a serach box in my project, the thing is that a user can enter any keyword and my ModelForm filters the fields I explicitly tell to filter, I use the following piece of code in my Form:
def get_matching_siniestros(self):
if self.cleaned_data['keywords'] is None:
return None
matching = []
for kw in self.cleaned_data['keywords']:
numero_ajuste = Siniestro.objects.filter(
numero_ajuste__icontains=kw
)
nombre_contratante = Siniestro.objects.filter(
poliza__contratante__nombre__icontains=kw
)
matching = chain(
numero_ajuste,
nombre_contratante,
matching
)
# verify not repeated Siniestro
non_rep_siniestros = []
for siniestro in matching:
if siniestro not in non_rep_siniestros:
non_rep_siniestros.append(siniestro)
return non_rep_siniestros
What I want to do is to programatically filter on any CharField in the Model and also if possible on any CharField of nested relations, in this example Siniestro has a FK to poliza and poliza has an FK to contratante.
You can iterate over every field and do whatever you like, e.g.:
[process(field) for field in model._meta.fields if field.__class__ == CharField]
where process can be a function, or whatever you require.
That said, I should really point out that the complexity you're trying to involve is bound to get messy. IMO, have a look at django-haystack. Indexing should be the way to go.