Get key value from display value from choices in Django - django

I have a model field with choices
CATEGORY_CHOICES = (
(0, 'A'),
(1, 'B'),
(2, 'C'),
)
I'm inserting rows in my database table where the category values could be A, B, or C.
Instead of using
if category:
if category == "A":
category = 0
elif category == "B":
category = 1
elif category == "C":
category = 2
before inserting my rows, can I somehow use my CATEGORY_CHOICES to translate display values to key values?

Build a dict of the display_value: value pairs and get the value from it:
CATEGORIES_D = {v: k for k, v in CATEGORY_CHOICES}
category = CATEGORIES_D[category]

How about this:
choices_dict = {}
for choice, value in CATEGORY_CHOICES:
choices_dict[value] = choice
category = choices_dict[category]

Related

Django filter using Q and multiple fields with different values

I am trying to generate a result that satisfies with the filter query below:
indicators = request.GET.getlist('indicators[]')
fmrprofiles = FMRPriority.objects.all()
q_objects = Q()
obj_filters = []
for indicator in indicators:
split_i = indicator.split('_')
if len(split_i) == 5:
if not any(d['indicator'] == split_i[1] for d in obj_filters):
obj_filters.append({
'indicator': split_i[1],
'scores': []
})
for o in obj_filters:
if split_i[1] == o['indicator']:
o['scores'].append(int(split_i[4]))
for obj in obj_filters:
print (obj['scores'])
q_objects.add(Q(pcindicator__id = int(obj['indicator'])) & Q(score__in=obj['scores']), Q.AND)
print (q_objects)
fmrprofiles = fmrprofiles.values('fmr__id','fmr__road_name').filter(q_objects).order_by('-fmr__date_validated')
print (fmrprofiles.query)
Basically, indicators is a list e.g. ['indicator_1_scoring_1_5', 'indicator_1_scoring_1_4', 'indicator_2_scoring_2_5']
I wanted to filter FMRPriority with these following fields:
pcindicator
score
e.g. pcindicator is equal 1 and scores selected are 5,4..another selection pcindicator is equal to 2 and scores selected are 3.
The query q_objects.add(Q(pcindicator__id = int(obj['indicator'])) & Q(score__in=obj['scores']), Q.AND) returns empty set..i have tried also the raw sql, same result.
Model:
class FMRPriority(models.Model):
fmr = models.ForeignKey(FMRProfile, verbose_name=_("FMR Project"), on_delete=models.CASCADE)
pcindicator = models.ForeignKey(PCIndicator, verbose_name=_("Priority Indicator"), on_delete=models.PROTECT)
score = models.FloatField(_("Score"))
I solve this by using OR and count the occurrence of id then exclude those are not equal to the length of filters:
for obj in obj_filters:
print (obj['scores'])
q_objects.add(
(Q(fmrpriority__pcindicator__id = int(obj['indicator'])) & Q(fmrpriority__score__in=obj['scores'])), Q.OR
)
fmrprofiles = fmrprofiles.values(*vals_to_display).filter(q_objects).annotate(
num_ins=Count('id'),
...
)).exclude(
~Q(num_ins = len(obj_filters))
).order_by('rank','road_name')

Django update rows base on order in queryset

I have a simple django model
class Item(Model):
name = CharField()
rank = PositiveIntegerField()
created_at = DateField(auto_now_add=True)
I want to update the object rank based on their order when sorted by a field (name or created_at)
e.g. when ordering by name
[("Pen", 0, "2021-05-04"), ("Ball", 0, "2021-05-04")] => [("Pen", 1, "2021-05-04"), (Ball, 0, "2021-05-04")]
I already know I can do this using bulk_update but it means I have to fetch the objects in memory
items = Items.objects.order_by("name")
for i, item in enumerate(items):
item.rank = i
Item.objects.bulk_update(items, ["rank"])
I was wondering if there is a way to do it with 1 query directly in the database, without having to fetch the data
CREATE TABLE items (
id serial PRIMARY KEY,
name VARCHAR ( 50 ) UNIQUE NOT NULL,
rank smallint
);
insert into items(id, name, rank) values (1, 'A', 0);
insert into items(id, name, rank) values (2, 'B', 0);
insert into items(id, name, rank) values (3, 'C', 0);
select * from items;
id
name
rank
1
A
0
2
B
0
3
C
0
UPDATE items
SET rank=calculated.calc_rank
FROM
(SELECT id AS calc_id,
(ROW_NUMBER() OVER (
ORDER BY name ASC)) AS calc_rank
FROM items) AS calculated
WHERE items.id = calculated.calc_id;
select * from items;
id
name
rank
1
A
1
2
B
2
3
C
3
And perform raw SQL for Django:
sql = '''
UPDATE items
SET rank=calculated.calc_rank
FROM
(SELECT id AS calc_id,
(ROW_NUMBER() OVER (
ORDER BY name ASC)) AS calc_rank
FROM items) AS calculated
WHERE items.id = calculated.calc_id
'''
with connection.cursor() as cursor:
cursor.execute(sql)

Django, get objects by multiple values

There is a model
class Fabric(models.Model):
vendor_code = models.CharField(max_length=50)
color = models.CharField(max_length=50)
lot = models.CharField(max_length=50)
I've a list of objects
values = [
{'vendor_code': '123', 'color': 'aodfe', 'lot': 'some lot 1'},
{'vendor_code': '456', 'color': 'adfae', 'lot': 'some lot 2'},
{'vendor_code': '789', 'color': 'dvade', 'lot': 'some lot 3'},
]
There are no ids in dict objects. How can get objects checking for list of field values(for all 3 values per object at same time)?
I know that I can query one by one in loop as:
for item in values:
fabric = Fabric.objects.filter(vendor_code=item['vendor_code'], color=item['color'], lot=item['lot'])
but amount of objects in list can be large. Is there any proper way to get objects at once, if they exists? Or at least to get them with min amount of db hit.
Thanks in advance!
You can use the in (__in) filter like so:
fabrics = Fabric.objects.filter(
vendor_code__in=[value['vendor_code'] for value in values],
color__in=[value['color'] for value in values],
lot__in=[value['lot'] for value in values],
)
This will however iterate the values list 3 times, to only iterate it once use something like this:
vendor_codes = []
colors = []
lots = []
for value in values:
vendor_codes.append(value['vendor_code'])
colors.append(value['color'])
lots.append(value['lot'])
fabrics = Fabric.objects.filter(
vendor_code__in=vendor_codes,
color__in=colors,
lot__in=lots,
)
To filter according to all three values at the same time you will have to use Q objects like this:
q_objects = []
for value in values:
q_objects.append(Q(
vendor_code=value['vendor_code'],
color=value['color'],
lot=value['lot']
)
)
final_q_object = Q()
for q_object in q_objects:
final_q_object.add(q_object, Q.OR)
fabrics = Fabric.objects.filter(final_q_object)
The gist of it is to get this query:
Q(Q(a=i, b=j, c=k)) | Q(Q(a=l, b=m, c=n) | ...)
Final answer after a bit of optimization:
final_query = Q()
for item in values:
final_query.add(
Q(
vendor_code=value['vendor_code'],
color=value['color'],
lot=value['lot']
),
Q.OR
)
fabrics = Fabric.objects.filter(final_query)
You could use the field lookup "in".
# get all the vendor codes in a list
vendor_code_list = []
for v in values:
vendor_code_list.append(v['vendor_code'])
# query all the fabrics
fabrics = Fabric.objects.filter(vendor_code__in=vendor_code_list)
If you want to match exact values and you are sure that your item keys are valid field names you can just:
for item in values:
fabric = Fabric.objects.filter(**item)
otherwise if you want check if your items are contained inside existing items you can:
for item in values:
item_in = {'{}__in'.format(key):val for key, val in item.items()}
fabric = Fabric.objects.filter(**item_in)

Django: get choices key from display value

Let's say I have the following Django model:
class Person(models.Model):
SHIRT_SIZES = (
(0, 'Small'),
(1, 'Medium'),
(2, 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.IntegerField(choices=SHIRT_SIZES)
I can create create a Person instance and get the shirt_size display value very easily:
john = Person(name="John", shirt_size=2)
john.shirt_size # 2
john.get_shirt_size_display() # 'Medium'
How can I do this the other way? That is, given a shirt size of Medium, how can I get the integer value? I there a method for that or should I write my own method on the Person object like so:
class Person(models.Model):
...
#staticmethod
def get_shirt_size_key_from_display_value(display_value):
for (key, value) in Person.SHIRT_SIZES:
if value == display_value:
return key
raise ValueError(f"No product type with display value {display_value}")
The docs recommend the following:
class Person(models.Model):
SMALL = 0
MEDIUM = 1
LARGE = 2
SHIRT_SIZES = (
(SMALL, 'Small'),
(MEDIUM, 'Medium'),
(LARGE, 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.IntegerField(choices=SHIRT_SIZES)
Now the name MEDIUM is attached to your model and model instances:
>>> john = Person(name="John", shirt_size=2)
>>> john.shirt_size
2
>>> john.MEDIUM
2
If given a string, you can use getattr:
def get_shirt_size(instance, shirt_size):
return getattr(instance, shirt_size.upper())
choices_dict = {y: x for x, y in shirt_size.choices}
will give you a dictionary with all the ints as values and sizes as keys.
So you can write a function that returns the int of whatever shirt size you put in, or make choices_dict part of your Person object.

Pseudo-random ordering in django queryset

Suppose I have a QuerySet that returns 10 objects, 3 of which will be displayed in the following positions:
[ display 1 position ] [ display 2 position ] [ display 3 position ]
The model representing it is as follows:
class FeaturedContent(models.Model):
image = models.URLField()
position = models.PositiveSmallIntegerField(blank=True, null=True)
where position can be either 1, 2, 3, or unspecified (Null).
I want to be able to order the QuerySet randomly EXCEPT FOR the objects with a specified position. However, I can't order it by doing:
featured_content = FeaturedContent.objects.order_by('-position', '?')
Because if I had one item that had position = 2, and all the other items were Null, then the item would appear in position 1 instead of position 2.
How would I do this ordering?
Thinking about this, perhaps it would be best to have the data as a dict instead of a list, something like:
`{'1': item or null, '2': item or null, '3': item or null, '?': [list of other items]}`
If you use a db backend that does random ordering efficiently you could do it like this:
# This will hold the result
featured_dict = {}
featured_pos = FeaturedContent.objects.filter(position__isnull=False).order_by('-position')
featured_rand = FeaturedContent.objects.filter(position__isnull=True).order_by('?')
pos_index = 0
rand_index = 0
for pos in range(1, 4):
content = None
if pos_index < len(featured_pos) and featured_pos[pos_index].position == pos:
content = featured_pos[pos_index]
pos_index += 1
elif rand_index < len(featured_rand):
content = featured_rand[rand_index]
rand_index += 1
featured_dict[str(pos)] = content
# I'm not sure if you have to check for a valid index first before slicing
featured_dict['?'] = featured_rand[rand_index:]
If you just want to iterate over the queryset you can have two querysets, order them and chain them.
import itertools
qs1 = FeaturedContent.objects.filter(position__isnull=False).order_by('-position')
qs2 = FeaturedContent.objects.filter(position__isnull=True).order_by('?')
featured_content = itertools.chain(qs1, qs2)
for item in featured_content:
#do something with qs item
print item
Upadate:
Since you are asking to make sure position determines the order and the "blank" spaces are replaced randomly by elements with null positions. If the featured list you want to obtain is not too large, 20 in this case
featured = []
rands = []
for i in xrange(1, 20):
try:
x = FeaturedContent.objects.get(position=i) # assuming position is unique
except FeaturedContentDoesNotExist:
if not rands:
rands = list(FeaturedContent.objects.filter(position__isnull=True).order_by('?')[:20]
x = rands[0]
rands = rands[1:]
featured.append(x)
I would post process it, doing a merge sort between the ordered and unordered records.
EDIT:
The beginnings of a generator for this:
def posgen(posseq, arbseq, posattr='position', startpos=1):
posel = next(posseq)
for cur in itertools.count(startpos):
if getattr(posel, posattr) == cur:
yield posel
posel = next(posseq)
else:
yield next(arbseq)
Note that there are lots of error conditions possible in this code (hint: StopIteration).