Простые вопросы иногда имеют сложные ответы в Django. Я потратил много времени, чтобы заставить это работать хорошо. Объединяя обзор Джарретта с важной запиской от jnns о render_option
и некоторой помощью #django по freenode, у меня есть хорошо работающее полное примерное решение:
Во-первых, в этом примере предполагается, что в модели, которую я называю Правило, обычно определен тип выбора CharField. Я подкласс своего TimeStampedModel
, но вы можете использовать models.Model
:
class Rule(TimeStampedModel):
...
# Rule Type
SHORT_TERM_RULE = 'ST'
MAX_SIGHTINGS_PER_PERIOD_RULE = "MA"
WHITE_LIST_RULE = "WL"
BLACK_LIST_RULE = "BL"
RULE_CHOICES = (
(SHORT_TERM_RULE, 'Short Term Rule'),
(MAX_SIGHTINGS_PER_PERIOD_RULE, 'Max Sightings Per Period Rule'),
(WHITE_LIST_RULE, 'White List Rule'),
(BLACK_LIST_RULE, 'Black List Rule'),
)
rule_type = models.CharField(
max_length=2,
choices=RULE_CHOICES,
default=SHORT_TERM_RULE,
)
...
В файле forms.py определите подкласс виджета Select
, который принимает disabled_choices
. У него есть пользовательский render_option()
, который добавляет disabled
к выводу html тегов параметров, если их выбор включен в список переданных в disabled_choices
. Обратите внимание, я оставил большую часть кода render_option()
из Select
как есть:
class MySelect(Select):
def __init__(self, attrs=None, choices=(), disabled_choices=()):
super(MySelect, self).__init__(attrs, choices=choices)
self.disabled_choices = disabled_choices
def render_option(self, selected_choices, option_value, option_label):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
selected_choices.remove(option_value)
else:
selected_html = ''
for key, value in self.disabled_choices:
if option_value in key:
return format_html('<option disabled value="{}"{}>{}</option>', option_value, selected_html,
force_text(option_label))
return format_html('<option value="{}"{}>{}</option>', option_value, selected_html, force_text(option_label))
Затем, при определении подкласса формы ModelForm
, проверьте переданный список disabled_choices
и инициализируйте поле соответствующим образом. В этом примере я также выбираю вариант по умолчанию.
class RuleChoiceForm(ModelForm):
class Meta:
model = Rule
fields = ['rule_type']
# Add a default choice to the list defined in the Rule model
default_choice = ('DF', 'Choose a Rule Type...')
choices = list(Rule.RULE_CHOICES)
choices.insert(0, default_choice)
choices = tuple(choices)
rule_type = forms.ChoiceField(widget=MySelect(attrs={'class': 'form-control'}, disabled_choices=[]),
choices=choices)
def __init__(self, *args, disabled_choices=None, **kwargs):
super(RuleChoiceForm, self).__init__(*args, **kwargs)
if disabled_choices:
self.fields['rule_type'].widget.disabled_choices = disabled_choices
Затем, по вашему мнению, определите disabled_choices
как список, добавив варианты из вашего _CHOICES
var в вашу модель и передайте его в экземпляр формы. В моей логике я использую список RULE_CHOICES
, чтобы получить кортеж выбора, который я хочу отключить. Хотя может быть более простой способ, пожалуйста, не стесняйтесь размещать предложения, чтобы упростить или улучшить этот ответ.
disabled_choices = []
# Logic disabling choices
if Rule.objects.filter(route=route, rule_type=Rule.SHORT_TERM_RULE).exists():
disabled_choices.append([item for item in Rule.RULE_CHOICES if Rule.SHORT_TERM_RULE in item][0])
disabled_choices.append([item for item in Rule.RULE_CHOICES if Rule.MAX_SIGHTINGS_PER_PERIOD_RULE in item][0])
rule_choice_form = RuleChoiceForm(disabled_choices=disabled_choices)