Set attribute based on another attribute

Viewed 107

How to set hostel choices based on gender?

Global variables

BOYS_HOSTEL_CHOICES = (...)

GIRLS_HOSTEL_CHOICES = (...)

class Student(models.Model):

    MALE = 'M'
    FEMALE = 'F'
    
    #...
    GENDER_CHOICES = (
        (MALE, 'Male'),
        (FEMALE, 'Female')
    )

    gender = models.CharField(
        max_length = 10,
        choices = GENDER_CHOICES,
        verbose_name="gender",
    )


    hostel = models.CharField(
        max_length=40,
        choices = BOYS_HOSTEL_CHOICES if gender == 'M' # <---
                            else GIRLS_HOSTEL_CHOICES,
        default = 'some hostel'
    )

2 Answers

You cannot do this on the model level, unless you want to implement this as a database constraint. But it sounds like you just want to limit the choice on the form.

Allow both choices in the model:

hostel = models.CharField(
    max_length=40,
    # Combine choices
    choices=BOYS_HOSTEL_CHOICES + GIRLS_HOSTEL_CHOICES,
    default='some hostel'
)

You can also use Django's model validation to check the constraint on save in Python.

If you need to restrict the choices in a rendered form, the form needs to know the value of the gender field and can then restrict the choices on the hostel field.

from django import forms

class StudentForm(forms.ModelForm):
# ...
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['hostel'].choices = BOYS_HOSTEL_CHOICES

If you do not know superset of hostel choices then you should not set choices attribute in model, because django model validate the field choices.

Make hostel field as a CharField without choices attribute

hostel = models.CharField(max_length=40)

In this case, you can create a form or modelform where you can set dynamic choice.

from django import forms

class StudentForm(forms.ModelForm):
    
    default_choices = [your default choices if you want]

    hostel = forms.CharField(max_length=40, choices = default_choices)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['hostel'].choices = self.get_dynamic_choice()


    def get_dynamic_choice(self):
        ''' you can set choices BOYS_HOSTEL_CHOICES or GIRLS_HOSTEL_CHOICES'''
        return choices

but if you want dependent dropdown means on selection of gender field you change the value hostel choice then you can implement this with the help of javascript or Ajax call

Edit:

If you don't want write javascript or ajax code then you can use django-autocomplete-light package.

you forward selected value of gender in hostel field and access in the view where you can set dynamic choices for hostel form field. You can also search in hostel field value. This package also supports queryset.

In forms.py,

from django import forms

class StudentForm(forms.ModelForm):
    default_choices = [your default choices if you want]

    hostel = forms.ChoiceField(max_length=40,
                             choices = default_choices,
                             widget=autocomplete.ListSelect2(url='hostel_autocomplete', forward=['gender'],))

In urls.py,

urlpatterns = [
    path('hostel_autocomplete/', HostelAutocompleteView.as_view(), name='hostel_autocomplete'),
]

In views.py,

from dal import autocomplete

class HostelAutocompleteView(autocomplete.Select2ListView):

    def get_list(self):
        if not self.request.user.is_authenticated:
            return []

        # you get select gender value
        gender = self.forwarded.get('gender', None)

        if gender == 'M':
            # your BOYS_HOSTEL_CHOICE_LIST
            choice_list = ['A hostel','B hostel']
        elif gender == 'F':
            # your GIRL_HOSTEL_CHOICE_LIST
            choice_list = ['C hostel', 'D hostel']
        else:
            choice_list = []

        return choice_list

forward value docs link- https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#filtering-results-based-on-the-value-of-other-fields-in-the-form

Related