Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ORM, how to use values() and still work with choicefield?

I am using django v1.10.2

I am trying to create dynamic reports whereby I store fields and conditions and the main ORM model information into database.

My code for the generation of the dynamic report is

class_object = class_for_name("app.models", main_model_name)

results = (class_object.objects.filter(**conditions_dict)
                               .values(*display_columns)
                               .order_by(*sort_columns)
                               [:50])

So main_model_name can be anything.
This works great except that sometimes associated models of the main_model have choicefield.

So for one of the reports main_model is Pallet.
Pallet has many PalletMovement.
My display columns are :serial_number, created_at, pallet_movement__location

The first two columns are fields that belong to Pallet model. The last one is from PalletMovement

What happens is that PalletMovement model looks like this:

class PalletMovement(models.Model):
    pallet = models.ForeignKey(Pallet, related_name='pallet_movements',
                               verbose_name=_('Pallet'))
    WAREHOUSE_CHOICES = (
        ('AB', 'AB-Delaware'),
        ('CD', 'CD-Delaware'),
    )
    location = models.CharField(choices=WAREHOUSE_CHOICES,
                                max_length=2,
                                default='AB',
                                verbose_name=_('Warehouse Location'))

Since the queryset will return me the raw values, how can I make use of the choicefield in PalletMovement model to ensure that the pallet_movement__location gives me the display of AB-Delaware or CD-Delaware?

Bear in mind that the main_model can be anything depending on what I store in the database.

Presumably, I can store more information in the database to help me do the filtering and presentation of data even better.

like image 592
Kim Stacks Avatar asked Aug 05 '17 07:08

Kim Stacks


3 Answers

The values() method returns a dictionary of key-value pairs representing your field name and a corresponding value.

For example:

Model:

class MyModel(models.Model):
    name = models.CharField()
    surname = models.CharField()
    age = models.IntegerField()
    ...

Query:

result = MyModel.objects.filter(surname='moutafis').values('name', 'surname')

Result:

< Queryset [{'name': 'moutafis', 'surname': 'john'}] >

You can now manipulate this result as you would a normal dictionary:

if main_model_name is 'PalletMovement':
    # Make life easier
    choices = dict(PalletMovement.WAREHOUSE_CHOICES)

    for item in result:
        item.update({ 
            pallet_movement__location: verbal_choice.get(
                pallet_movement__location, pallet_movement__location)
        })

You can even make this into a function for better re-usability:

def verbalize_choices(choices_dict, queryset, search_key):
    result = queryset        

    for item in result:
        item.update({ search_key: choices_dict.get(search_key, search_key) })

    return result

verbal_result = verbalize_choices(
                    dict(PalletMovement.WAREHOUSE_CHOICES),
                    result,
                    'pallet_movement__location'
                )

I suggest the use of the update() and get() methods because they will save you from potential errors, like:

  • The search_key does not exist in the choice_dict then get() will return the value of the search_key
  • update() will try to update the given key-value pair if exists, else it will add it to the dictionary.

If the usage of the above will be in the template representation of your data, you can create a custom template filter instead:

@register.filter(name='verbalize_choice')
def choice_to_verbal(choice):
    return dict(PalletMovement.WAREHOUSE_CHOICES)[choice]

Have an extra look here: Django: How to access the display value of a ChoiceField in template given the actual value and the choices?

like image 137
John Moutafis Avatar answered Oct 05 '22 14:10

John Moutafis


You would use get_foo_display

In your template:

{{ obj.get_location_display }}

or

{{ obj.pallet_movement.get_location_display }}

[Edit:] As pointed out in the comments this will not work when calling values()

like image 28
The_Cthulhu_Kid Avatar answered Oct 05 '22 14:10

The_Cthulhu_Kid


an alternative to create a templatetag is :

{{form.choicefield.1}}

This shows the value of the initial data of the foreign key field instead the id.

like image 25
wololoooo Avatar answered Oct 05 '22 14:10

wololoooo