1

I have created a label_form_instance for modelchoicefield but the values in html are showing primary key values. To get rid of that, i use to_field_names but i can't provide more than one column name in it.

class firearmChoiceField(forms.ModelChoiceField):

    def label_from_instance(self, obj):
        return '%s%s, %s'%(obj.make,obj.firearm_model,obj.serial_no)


self.fields['firearm'] = firearmChoiceField(queryset = firearm_db.objects.all(),to_field_name="make,firearm_model,serial_no",required=False,empty_label='Select Firearm', widget = forms.Select(attrs={'label': ' ','class': 'form-control',}))
addminuse
  • 23
  • 4

1 Answers1

0

You can patch the prepare_value [GitHub] and to_python [GitHub] functions for that, for example:

from django.core.exceptions import ValidationError

class firearmChoiceField(forms.ModelChoiceField):

    def prepare_value(self, value):
        if hasattr(value, '_meta'):
            return '{}:{}:{}'.format(value.make,value.firearm_model,value.serial_no)
        return super().prepare_value(value)

    def to_python(self, value):
        if value in self.empty_values:
            return None
        try:
            make, firmod, serial = value.split(':')
            return firearm_db.objects.get(
                make=make,
                firearm_model=firmod,
                serial_no=serial
            )
        except (ValueError, TypeError, firearm_db.DoesNotExist):
            raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')

You thus should not specify the field_name here. In fact if we look at the original implemention, we see how this field_name is used:

class ModelChoiceField(ChoiceField):

    # ...

    def prepare_value(self, value):
        if hasattr(value, '_meta'):
            if self.to_field_name:
                return value.serializable_value(self.to_field_name)
            else:
                return value.pk
        return super().prepare_value(value)

    def to_python(self, value):
        if value in self.empty_values:
            return None
        try:
            key = self.to_field_name or 'pk'
            value = self.queryset.get(**{key: value})
        except (ValueError, TypeError, self.queryset.model.DoesNotExist):
            raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
        return value

In the prepare_value, we thus convert an object (here a firearm_db object) into a string that holds the value used in the <option value="...">s. The to_python function on the other hand performs the transformation back to a firearm object (or None in case the selection is empty).

You will have to ensure that the two functions are each other inverse: each mapping with prepare_value should result in the same object when we perform a to_python on it. If for example here the make contains a colon (:), then this will fail, so it might require some extra finetuning.

That being said, I am not sure why you want to use a more complicated value, and not use a primary key, a slug, or some hashed value for this.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • I thought there might be an easy way to solve this problem. what if i combine make,firearm_model and serial_no into one column rather than using separate ones? this should solve the problem. – addminuse May 11 '19 at 21:15
  • @addminuse: this sounds like "*data duplication*", which is typically not very good, since that means you need to keep the original columns and the "combined" column in sync, etc. which likely in the end turns out to be a harder problem. – Willem Van Onsem May 11 '19 at 21:19
  • So what do you suggest? – addminuse May 11 '19 at 21:21
  • @addminuse: what exactly is the problem with using a primary key? slug? or create some "hashed" identifier as a "twin" primary key? – Willem Van Onsem May 11 '19 at 21:22
  • I will try that and then i will get back to you – addminuse May 12 '19 at 05:58
  • I use the prepare_value and to_python function. Everything works fine now but in db it is storing values as db objects. Example : firearm_db object (11) – addminuse May 12 '19 at 06:48
  • @addminuse: well the idea is indeed that in the database a `FOREIGN KEY` is used, this will gurantee *referential integrity*, and furthermore due to indexing, etc. it will make queries faster. A form is also only a way to communicate with the *user*. You can alter model fields to communicate with the *database*, but using primary key values, is one of the ways to [*normalize* a database](https://en.wikipedia.org/wiki/Boyce%E2%80%93Codd_normal_form) – Willem Van Onsem May 12 '19 at 06:51
  • Understood, so how can i convert that value to human readable format. apologies for my ignorance but i am very new to django framework. – addminuse May 12 '19 at 07:03
  • @addminuse: a human readable format *where*? In a template? In a form? In a the Django shell? – Willem Van Onsem May 12 '19 at 07:14
  • Sorry, in a template and also in a shell. – addminuse May 12 '19 at 07:16
  • @addminuse: you can implement the `__str__` of your model (here in the `firearm_db` class), see [here](https://docs.djangoproject.com/en/2.2/ref/models/instances/#str). – Willem Van Onsem May 12 '19 at 07:21
  • Thank you sir! you are a genius. Thank you very much, can you also recommend some books on django to understand it more? – addminuse May 12 '19 at 07:50