applegrew / django-select2

This is a Django integration for Select2
MIT License
710 stars 316 forks source link

Interdependent selects for fields of one model #598

Closed thilasx closed 4 years ago

thilasx commented 4 years ago

Hey there! Thanks for providing such a nice integration!

Sadly, I've got a problem working with it. I want to realize the following feature:

I've got the model Rim with the fields diameter, thickness and some foreign keys like manufacturer etc. However, diameter and thickness aren't foreign keys.

The user should be allowed to filter the rim objects based on the fields. e.g. if he selects diameter 16" in the first select the second select should only show thicknesses that are available for rims with diameter 16".

I know that it can be done easily with foreign keys like the documentation shows for the city-country relation. However, the diameter and thickness are normal fields belonging to "rim".

I can't seem to find an answer, but I'm also quite new to Django, so I might don't see the obvious solution.

Greetings!

codingjoe commented 4 years ago

Hi @thilasx excellent question! I think all you are looking for is the search_fields attribute. In your example you can create a form as follows:

from django import forms
from django_select2.forms import Select2Widget

from . import models

class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.MyModel
        fields = [
            'rim',
            # others
        ]
        widgets = {
            'rim': Select2Widget(search_fields=['diameter', 'tickness']),
        }

The search_fields attribute takes a list of field lookup names. Like my_field__icontains or my_other_field__startswith. You can also overwrite the attribute on a subclass or go as far as to override filter_queryset on a widget subclass to fully customize the results show for a given search term.

I hope this answers your question and helps you solve your problem.

Let me know if you need any help.

Best Joe

thilasx commented 4 years ago

Hey Joe, thanks for the fast response. Based on your city-country example I tried to already write something like: MODEL:

from django.db import models

class Rim(models.Model):
    name = models.CharField(max_length=20)
    diameter = models.CharField(max_length=6)
    thickness = models.CharField(max_length=6)
    def __str__(self):
        return self.name

FORM:

from django import forms
from django.utils.encoding import force_text
from django_select2.forms import (
     ModelSelect2Widget,
    Select2Widget
)
from .models import Rim

class RimsChainedSelect2WidgetForm(forms.Form):
    rimdia = forms.ModelChoiceField(
        queryset=Rim.objects.all(),
        label='RimDia',
        widget=ModelSelect2Widget(
            search_fields=['diameter'], # <---still showing the name
            max_results=500,
            #dependent_fields={'thickness': 'thickness'}, <---what to write for fields of the same model?
            attrs={'data-minimum-input-length': 0},
        )
    )

    rimthickness = forms.ModelChoiceField(
        queryset=Rim.objects.all(),
        label='RimThickness',
        widget=ModelSelect2Widget(
            search_fields=['thickness'], # <---still showing the name
            #dependent_fields={'diameter': 'diameter'}, <---what to write for fields of the same model?
            max_results=500,
            attrs={'data-minimum-input-length': 0},
        )
    ) 

I know, that a Charfield isn't the best choice for the fields. I'm just testing. After your answer I tried to set the search_fields to the expected fields, but I'm still getting the name of the rim in both selects instead of the diameter and the thickness. Sadly, I'm also unsure how to use the dependent_fields in this case. Thanks again for the time you put into your integration and the support!

codingjoe commented 4 years ago

Hi @thilasx,

Would you mind sharing your model structure with me? It seems strange, that you have multiple foreign keys to the same model, yet want it to display different things.

Anyhow, if you want to change the label per widget, you can do that by overriding the label_form_instance method on each widget. For that I'd recommend creating your own subclass. Something along the lines of:

from django_select2 import ModelSelect2Widget

from . import models

class ThicknessWidget(ModelSelect2Widget):
    model = models.Rim
    search_fields = ['thickness']

    def label_from_instance(self, obj):
        return str(self.obj.thickness)

I hope that helps you solve your problem, yet something feels off about that. I'd be really curious to see your model structure.

Best -Joe