2

I want a model with 5 choices, but I cannot enforce them and display the display value in template. I am using CharField(choice=..) instead of ChoiceField or TypeChoiceField as in the docs. I tried the solutions here but they don't work for me (see below).

model.py:

class Language(models.Model):
    language = models.CharField(max_length=20,blank=False)
    ILR_scale = (
        (5, 'Native'),
        (4, 'Full professional proficiency'),
        (3, 'Professional working proficiency'),
        (2, 'Limited professional proficiency'),
        (1, 'Elementary professional proficiency')
        )
    level = models.CharField(help_text='Choice between 1 and 5', default=5, max_length=25, choices=ILR_scale)
    def level_verbose(self):
        return dict(Language.ILR_scale)[self.level]
    class Meta:
        ordering = ['level','id']
    def __unicode__(self):
        return ''.join([self.language, '-', self.level])

view.py

..
def index(request):
language = Language.objects.all()
..

mytemplate.html

<div class="subheading strong-underlined mb-3 my-3">
      Languages
      </div>
      {% regroup language|dictsortreversed:"level" by level as level_list %}
      <ul>
        {% for lan_list in level_list %}
        <li>
          {% for lan in lan_list.list %}
          <strong>{{ lan.language }}</strong>: {{ lan.level_verbose }}{%if not forloop.last%},{%endif%}
          {% endfor %}
        </li>
        {% endfor %}
      </ul>

From shell:

python3 manage.py shell
from resume.models import Language
l1=Language.objects.create(language='English',level=4)
l1.save()
l1.get_level_display()   #This is good
Out[20]: 'Full professional proficiency'

As soon as I create a Language instance from shell I cannot load the site. It fails at line 0 of the template with Exception Type: KeyError, Exception Value: '4', Exception Location: /models.py in level_verbose, line 175 (which is the return line of the level_verbose method).

Also, I was expecting a validation error here from shell:

l1.level='asdasd'
l1.save()     #Why can I save this instance with this level?

And I can also save a shown above when using ChoiceField, meaning that I do not understand what that field is used for.

How to force instances to take field values within choices, and display the display value in templates?

aless80
  • 3,122
  • 3
  • 34
  • 53
  • it is looking for a string '4' not 4 in the dict. try - try . - `return dict(Language.ILR_scale)[int(self.leve)]` – Nihal Sharma Mar 04 '18 at 04:24
  • You are mixing up strings and integers. If you wantto use integer keys you should probably use an `IntegerField` instead of a `CharField`. This will ensure that values are properly cast as integers before being saved. – solarissmoke Mar 04 '18 at 04:25
  • yes, that fixes it. and I think ChoiceField is for forms, not for models – aless80 Mar 04 '18 at 12:16

2 Answers2

4

Well this is the common issue even when I started with django. So first let's look at django's feature that you can do it like below (Note: your choice case's value are going to be store as integer so you should use models.IntegerField instead of models.CharField):

As you can see in documentation FOO is the field name of your model. in your case it is level so when you want to access corresponding choice value in shell or view you can call method with model instance as below as you have already mentioned:

`l1.get_level_display()`

but when you want to access it in template file you need to write it like below:

{{ l1.get_level_display }}
  • Now let's look at your method level_verbose() if you see quite again your model is a class and level_verbose() is the method you have created you can access self.ILR_scale directly just as you have used self.level

the main catch in you create dictionary of ILR_scale it's keys are Integer values (i.e. 1, 2, 3, 4, 5) but you have used CharField() to store the level values which returns you string values (i.e. '1', '2', '3', '4' or '5') and in python dictionary key 1 and '1' are both different one is integer and other is string. So you may change your model field to models.IntegerField() or you can access the keys like

dict(self.ILR_scal)[int(self.level)]
Gahan
  • 4,075
  • 4
  • 24
  • 44
  • Of course! IntegerField works. I still find it strange that I can save l1.level=555, but then the site crashes. For this reason I think it would be safer to use validators. Now let me try get_FOO_display.. – aless80 Mar 04 '18 at 12:06
  • you are using choices to store values in model that means django is validating your result when you run your program in template; if you assign statically this way it won't properly handle any exception. – Gahan Mar 04 '18 at 12:08
  • yes, {{ lan.get_level_display }} works too. It wasn't working for me because of the wrong CharField. Thank you – aless80 Mar 04 '18 at 12:17
1

You can also use models.CharField but you have to set field option choices to your tuples.

For exapmle:

FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
LEVELS = (
    (FRESHMAN, 'Freshman'),
    (SOPHOMORE, 'Sophomore'),
    (JUNIOR, 'Junior'),
    (SENIOR, 'Senior'),
)
level = models.CharField(
    max_length=2,
    choices=LEVELS,
    default=FRESHMAN,
)

Then in your template you can use get_FOO_display() for example: {{l1.get_level_display}}

See more in docs

ProgShiled
  • 154
  • 1
  • 1
  • 12