Django get choice display on using .values - django

For a Model with field type, is there any way to get the choice display value on using Model.objects.values() ? I tried Model.objects.values('get_type_display') but it doesn't work.

You can't do that. values is a built in django queryset method which is used to get dictionaries of data instead of model instances you can read more about it here.
The conventional (and proper) way of attaching choices with model for a field is using static variable like this.
class MyModel(models.Model):
TYPE_CHOICES = (
# (<DB VALUE>, <DISPLAY_VALUE>)
('a', 'Choice A'),
('b', 'Choice B'),
)
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
You can access choices for type field outside model like this.
MyModel.TYPE_CHOICES

Where I do call .values of choice fields into my queryset I deal with this in the following way:
Assume the following model
from enum import Enum
class TypeChoice(Enum):
a = 'class A'
b = 'class B'
class MyModel(models.Model):
type = models.CharField(max_length=1, choices=[(tag.name,tag.value) for tag in TypeChoice])
Using the query my_qset = MyModel.objects.values('type') the display values are available as:
for item in my_qset:
print(TypeChoice[item].value)
To deal with this in my templates I write a custom template filter, say type_display:
from django import template
import TypeChoice
register = template.Library()
#register.filter
def type_display(var):
return TypeChoice[var].value

I had a similar need and unfortunately you can't do so with only values; however, you can do something similar with some crafty annotations and django's custom enums (IntegerChoices/TextChoices) classes.
I created this from another SO question but I can't remember where I got the inspiration from. Basically, you can pass in the corresponding model or the choices you'd like to map back to labels via annotations on the queryset.
class ChoicesLabelCase(Case):
def __init__(self, field: str, model: Model = None, choices: list = None, *args, **kwargs) -> None:
if choices is None and model is None:
raise ValueError("Either a model or a choices parameter must be provided.")
elif choices is None:
choices = model._meta.get_field(field).flatchoices
cases_list = [When(**{field: val, "then": Value(label)}) for (val, label) in choices]
super(ChoicesLabelCase, self).__init__(*cases_list, output_field=CharField(), *args, **kwargs)
As an example take this model:
class FruitType(models.IntegerChoices):
APPLE = 1, 'Apple'
BANANA = 2, 'Banana'
ORANGE = 3, "Orange"
class Fruit(models.Model):
type = models.IntegerField(choices=FruitType.choices)
You can annotate the labels like so:
>>> Fruit.objects.all().annotate(
type_label=ChoicesLabelCase('type', FruitType)
).values("type", "type_label")
[
{'type': 1, 'type_label': 'Apple'},
{'type': 2, 'type_label': 'Banana'},
...
]

Related

Django-filter get all records when a specific value for a filter_field is passed

I am using django-filter to filter my Queryset on the basis of url params.
class WorklistViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = MyModel.objects.all()
filter_backends = [DjangoFilterBackend, ]
filterset_fields = ['class', ]
# possible values of *class* which is allowed to be passed in the url params are ['first', 'second', 'ALL'].
class MyModel(BaseModel):
CLASS_CHOICES = (
(FIRST_CLASS, 'first'),
(SECOND_CLASS, 'second'),
)
class = models.CharField(choices=CLASS_CHOICES, max_length=3, )
URLs http://127.0.0.1:8000?class=first and http://127.0.0.1:8000?class=first are giving the expected results.
I want that when http://127.0.0.1:8000?class=ALL is called, all the records in my table should be listed i.e without filtering.
How can i do this while using django-filter ?
You may want to use Filter.method, as explained in the docs.
In your case, I would do as follows:
class F(django_filters.FilterSet):
klass = CharFilter(method='my_custom_filter')
class Meta:
model = MyModel
fields = ['klass']
def my_custom_filter(self, queryset, name, value):
if value == 'ALL':
return queryset
return queryset.filter(**{
name: value,
})
Be also reminded that class is a reserved word in Python and cannot be used as a variable name. I've used klass instead, although that's used as something else in many Python books and may be confusing.

Hov can get the translated values in select?

I get select with values Without and With. How can I get already translated values Без and С in django.po in a select?
models.py
CONFIRMATION_WITHOUT = 'without'
CONFIRMATION_OTHER = 'other'
CONFIRMATION_WITH = 'with'
CONFIRMATION_CHOICES = (
(CONFIRMATION_WITHOUT, _('Without')), #Без
(CONFIRMATION_OTHER, _('Other')), #Другое
(CONFIRMATION_WITH, _('With')), #С
)
income_proof = models.CharField(_('proof'), max_length=255, choices=CONFIRMATION_CHOICES, default=CONFIRMATION_WITHOUT)
#[u'without', u'with']
forms.py
income_proof = forms.ModelChoiceField(queryset=CreditPayment.objects.values_list('income_proof', flat=True).distinct(), widget=forms.Select(attrs={'class': 'selectpicker form-control', 'title':_("Income proof")}))
html
{{ form.income_proof }}
It is possible to make in the form, for example?
<select>
<option value = "CONFIRMATION_WITHOUT">Без</option>
</select>
For the form, you should not use a ModelChoiceField [Django-doc]. Indeed, you here do not select a model object, but a value. You thus should use a ChoiceField [Django-doc] instead.
As for the options, I think you want to use CONFIRMATION_CHOICES, since by using a queryset, you query the database, and you thus are only able to pick income_proofs that are already picked by other records.
from app.models import CONFIRMATION_CHOICES
from django import forms
class MyForm(forms.ModelForm):
income_proof = forms.ChoiceField(
choices=CONFIRMATION_CHOICES,
widget=forms.Select(
attrs={'class': 'selectpicker form-control', 'title':_('Income proof')}
)
)
or if you only want the values that were selected, you can use:
from app.models import CONFIRMATION_CHOICES
from django import forms
class MyForm(forms.ModelForm):
income_proof = forms.ChoiceField(
choices=[],
widget=forms.Select(
attrs={'class': 'selectpicker form-control', 'title':_('Income proof')}
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
render = dict(CONFIRMATION_CHOICES)
self.fields['income_proof'].choices = [
(k, render.get(k, k))
for k in CreditPayment.objects.values_list('income_proof', flat=True).distinct()
]
Here the __init__ is called when we construct the form. We first let the super constructor do the work to create the fields, then make a dictionary of the CONFIRMATION_CHOICES.
Next, we perform a query (the same one you used) to get the database values for income_proof, and we use the dictionary to map these to the corresponding translations. We thus yield a list of 2-tuples as choices for that form field.
We here thus use the choices= parameter [Django-doc] which should contain:
choices
Either an iterable of 2-tuples to use as choices for this field, or a
callable that returns such an iterable. This argument accepts the same
formats as the choices argument to a model field. See the model
field reference documentation on choices for more details. If the
argument is a callable, it is evaluated each time the field's form is
initialized. Defaults to an empty list.

Object of type QuerySet is not JSON serializable

I have these list objects that are callable by name. They have number values that users can input. how would I turn each of those lists to form that chart js can render? I tried with this: Django Queryset to dict for use in json but could not get it working. Getting "Object of type QuerySet is not JSON serializable". Chart js would need to have own line for each of those list and show the values in those lines. This is how far I got with the link in views:
First I get all of users lists:
user_lists = List.objects.filter(user=user)
Then I get number values for each list
list_data = {}
for list in user_lists:
list_data[list.name] = DataItem.objects.filter(list=list)
Here is where I get stuck when I should convert these lists to something that chart.js can understand..
list_data_json = json.dumps(list_data, cls=DjangoJSONEncoder)
btw am I in the right tracks to put this conversion to views, right? or does it belong somewhere else?
Dont know if these are needed but here are models for these lists and the data items in them:
class List(models.Model):
name = models.CharField(max_length=100, default="")
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='lists')
def __str__(self):
return self.name
class Meta:
unique_together = ['name', 'user']
class DataItem(models.Model):
data = models.IntegerField(default=0)
list = models.ForeignKey(List, on_delete=models.CASCADE, related_name='data_items')
EDIT
output query set looks like this (this is what json.dumps is trying to read i quess):
<QuerySet [{'name': 'squat', 'data_items__data': 1}, {'name': 'deadlift', 'data_items__data': 55}, {'name': 'Chest', 'data_items__data': None}, {'name': 'asd', 'data_items__data': 444}, {'name': 'asd', 'data_items__data': 32342}, {'name': 'asd', 'data_items__data': 42342}]>
And for me that looks good, there is a list of lists and list has a name "squats" and then values. But getting this error again "'QuerySet' object is not callable"
If you know what fields you want to pass to chart.js, you can do a specific values() query to get a dictionary, which you can easily serialize with json.dumps, more or less like this:
user_lists = (List.objects
.filter(user=user)
.select_related('user')
.prefetch_related('data_items')
.values('user__username', 'data_items__data') # all fields you need
)
list_data_json = json.dumps(list(user_lists))
despite what one could expect, DjangoJSONEncoder doesn't handle querysets nor models instances (see here for the types DjangoJSONEncoder deals with) - this part is actually handled by the serializer itself, but a serializer expects a Queryset and not a dict of querysets.
IOW, you will have to write your own encoder (based on DjangoJSONEncoder) to handle querysets and models (hint: someobj.__dict__ returns the object's attributes as a dict, which you can filter out to remove irrelevant django stuff like _state)

Filter on a field with choices

I have this field:
operation = models.CharField(max_length=10, choices=OPERATIONS)
Having this filter works:
class OperationFilter(django_filters.Filter):
def filter(self, qs, value):
try:
qs = qs.filter(operation=value.upper())
except:
pass
return qs
With url:
/api/v1/operation/?operation=CREATE
But having the default filter (without an extra OperationFilter) fails with:
{
"operation": [
"Select a valid choice. %(value)s is not one of the available choices."
]
}
Why is a filter on a field with choices failing?
For other, non-choice fields, default filters are working fine:
/api/v1/operation/?recipient=recipient-19
EDIT
The OPERATIONS:
from enum import Enum
def enum_as_choices(enum_class):
"""From an enum class, generate choices for a django field"""
return ((entry, entry.value) for entry in enum_class)
class OperationType(Enum):
CREATE = 'CREATE'
STATUS = 'STATUS'
EXPAND = 'EXPAND'
DELETE = 'DELETE'
OPERATIONS = enum_as_choices(OperationType)
You are using django_filters package, I suggest reading docs, since you already have support for this
https://django-filter.readthedocs.io/en/master/ref/filters.html#choicefilter
Just point out your choices to the value suggested by the other answers (or check the example in docs)
The choices you written would be converted to this pythonic representation:
(
('OperationType.CREATE', 'CREATE'),
('OperationType.STATUS', 'STATUS'),
('OperationType.EXPAND', 'EXPAND'),
('OperationType.DELETE', 'DELETE')
)
As you can see the actual values stored in your operation field (in DB) are 'OperationType.CREATE', etc.
So you should change your choices to normal constant choices or you should filter by something like 'OperationType.CREATE' which is not a good option IMO.
also you can change your enum_as_choices method like this:
def enum_as_choices(enum_class):
"""From an enum class, generate choices for a django field"""
return ((entry.name, entry.value) for entry in enum_class)
You haven't defined a blank/default choice in your OPERATIONS. To do so, add something like this:
OPERATIONS = (
('', 'NONE'),
# the rest of your choices here...
)
But you would also need to update your model to be:
operation = models.CharField(max_length=10, choices=OPERATIONS, default='NONE')

Django: Building a QuerySet Mixin for a model and a related model

My question is about creating a QuerySet Mixin which provides identical QuerySet methods for both a model and a related model. Here is example code, and the first class ByPositionMixin is what I am focused on:
from django.db import models
from django.db.models.query import QuerySet
from django.core.exceptions import FieldError
class ByPositionMixin(object):
def batters(self):
try:
return self.exclude(positions=1)
except FieldError:
return self.exclude(position=1)
class PlayerQuerySet(QuerySet, ByPositionMixin):
pass
class PlayerPositionQuerySet(QuerySet, ByPositionMixin):
pass
class PlayerManager(models.Manager):
def get_query_set(self):
return PlayerQuerySet(self.model, using=self._db)
class PlayerPositionManager(models.Manager):
def get_query_set(self):
return PlayerPositionQuerySet(self.model, using=self._db)
class Position(models.Model):
# pos_list in order ('P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF')
# pos id / pk correspond to index value of pos_list(pos)
pos = models.CharField(max_length=2)
class Player(models.Model):
name = models.CharField(max_length=100)
positions = models.ManyToManyField(Position, through='PlayerPosition')
objects = PlayerManager()
class PlayerPosition(models.Model):
player = models.ForeignKey(Player)
position = models.ForeignKey(Position)
primary = models.BooleanField()
objects = PlayerPositionManager()
Inside ByPositionMixin, I try exclude(positions=1) which queries against PlayerQuerySet and if that generates a FieldError, I try exclude(position=1) which queries against PlayerPositionQuerySet. The difference in field names is precise, a Player() has positions, but a PlayerPosition() has only one position. So the difference it the exclude() query is 'positions' / 'position'. Since I will have many custom queries (e.g. batters(), pitchers(), by_position() etc.), do I have to write out try / except code for each one?
Or is there a different approach which would let me write custom queries without having to try against one model and then against the other one?
UPDATE: basically, I have decided to write a kwarg helper function, which provides the correct kwargs for both Player and PlayerPosition. It's a little elaborate (and perhaps completely unnecessary), but should be able to be made to simplify the code for several custom queries.
class ByPositionMixin(object):
def pkw(self, **kwargs):
# returns appropriate kwargs, at the moment, only handles one kwarg
key = kwargs.keys()[0] # e.g. 'positions__in'
value = kwargs[key]
key_args = key.split('__')
if self.model.__name__ == 'Player':
first_arg = 'positions'
elif self.model.__name__ == 'PlayerPosition':
first_arg = 'position'
else:
first_arg = key_args[0]
key = '__'.join([first_arg] + key_args[1:])
return {key: value}
def batters(self): # shows how pkw() is used
return self.exclude(**self.pkw(positions=1))