Подтвердить что ты не робот

Как получить экземпляры ModelChoiceField в шаблоне

У меня есть ModelForm, который содержит ModelChoiceField, используя виджет RadioSelect.

class MyAForm(forms.ModelForm):
    one_property = models.ModelChoiceField(
        widget=forms.RadioSelect,
        queryset=MyBModel.objects.filter(visible=True),
        empty_label=None)
    class Meta:
        model = MyAModel

В MyBModel есть атрибуты, которые я хочу отображать рядом с переключателем. Я бы переопределил label_from_instance в подклассе ModelChoiceField, но это не позволяет мне делать то, что я хочу, поскольку я хочу, чтобы переключатель появлялся внутри таблицы, которая имеет строку для каждого элемента выбора.

Итак, где-то в моем шаблоне я хочу что-то вроде...

{% for field in form.visible_fields %}
    {% if field.name == "one_property" %}
    <table>
        {% for choice in field.choices %}
            <tr>
                <td><input value="{{choice.id}}" type="radio" name="one_property" />{{choice.description}}</td>
                <td><img src="{{choice.img_url}}" /></td>
            </tr>
        {% endfor %}
    </table>
    {% endif %}
{% endfor %}

К сожалению, field.choices возвращает кортеж идентификатора объекта и метки, а не экземпляр из набора запросов.

Есть ли простой способ получить экземпляры выбора для ModelChoiceField для использования в шаблоне?

4b9b3361

Ответ 1

После вникания в источник django для ModelChoiceField я обнаружил, что у него есть свойство "queryset".

Я смог использовать что-то вроде...

{% for field in form.visible_fields %}
    {% if field.name == "one_property" %}
    <table>
        {% for choice in field.queryset %}
            <tr>
                <td><input value="{{choice.id}}" type="radio" name="one_property" />{{choice.description}}</td>
                <td><img src="{{choice.img_url}}" /></td>
            </tr>
        {% endfor %}
    </table>
    {% endif %}
{% endfor %}

Ответ 2

Я хотел сделать что-то почти идентичное вопросу OP (таблица и все), был так же разочарован отсутствием сотрудничества Django, и аналогичным образом вникал в источник, чтобы придумать мою собственную реализацию. То, что я придумал, немного отличается от принятого ответа, и мне это понравилось лучше, потому что я использовал простой {{ form.as_table }} в своем шаблоне и не хотел, чтобы цикл visible_fields был ненужным или жестким кодом в моем шаблоне, который просто похож на текущую реализацию Django (что может измениться). Вот что я сделал вместо этого:

RadioInput и RadioFieldRenderer

Django RadioSelect использует RadioFieldRenderer, чтобы получить generator RadioInputs, которые выполняют фактическую работу по рендерингу Радио-кнопки. RadioSelect похоже, имеет недокументированную функцию, в которой вы можете передать ей другой рендерер, чем этот, по умолчанию, поэтому вы можете подклассифицировать оба из них, чтобы получить то, что хочет OP.

from django import forms
from django.utils.safestring import mark_safe

class CustomTableRadioInput(forms.widgets.RadioInput):

    # We can override the render method to display our table rows
    def render(self, *args, **kwargs):
        # default_html will hold the normally rendered radio button
        # which we can then use somewhere in our table
        default_html = super(CustomTableRadioInput, self).render(*args, **kwargs)
        # Do whatever you want to the input, then return it, remembering to use
        # either django.utils.safestring.mark_safe or django.utils.html.format_html
        # ...
        return mark_safe(new_html)

class CustomTableFieldRenderer(forms.widget.RadioFieldRenderer):
    # Here we just need to override the two methods that yield RadioInputs
    # and make them yield our custom subclass instead
    def __iter__(self):
        for i, choice in enumerate(self.choices):
            yield CustomTableRadioInput(self.name, self.value,
                                          self.attrs.copy(), choice, i)

    def __getitem__(self, idx):
        choice = self.choices[idx] # Let the IndexError propogate
        return CustomTableRadioInput(self.name, self.value,
                                       self.attrs.copy(), choice, idx)

С этим мы просто должны сказать виджету RadioSelect, чтобы использовать наш собственный рендерер, когда мы называем его где-то в нашем коде формы:

...
radio = forms.ChoiceField(widget=forms.RadioSelect(renderer=CustomTableFieldRenderer),
                          choices=...)
...

И что это!

Обратите внимание, что для использования этого в шаблоне вы, вероятно, захотите перебрать поле, а не называть его напрямую, то есть это:

<table>
  <tbody>
  {% for tr in form.radio %}
    <tr>{{ tr }}</tr>
  {% endfor %}
  </tbody>
</table>

вместо этого:

<table>
  <tbody>{{ form.radio }}</tbody>
</table>

Если вы сделаете последнее, он попытается обернуть ваши элементы таблицы в <ul><li>...</li></ul>.

Ответ 3

Обычно вам не нужен фактический объект, но его исполнение.

Рассмотрим этот код:

class LabelledHiddenWidget(forms.HiddenInput):

    def __init__(self, get_object, *args, **kwargs):
        super(LabelledHiddenWidget, self).__init__(*args, **kwargs)
        self.get_object = get_object

    def render(self, name, value, attrs=None):
        s = super(LabelledHiddenWidget, self).render(name, value, attrs)
        if value:
            s += SafeUnicode("<span>%s</span>" % self.get_object(value))
        return s

Затем вы можете использовать его следующим образом:

class SomeForm(forms.Form):
    object = forms.ModelChoiceField(
         SomeModel.objects.all(), 
         widget=LabelledHiddenWidget(get_object=lambda id: get_object_or_404(SomeModel, id=id)))

Затем в коде шаблона {{ form.object }} выведет скрытое поле с идентификатором объекта, объединенным с некоторой меткой. Конечно, ваш SomeModel должен реализовать __unicode__ или какой-либо другой метод, который возвращает приятную, удобочитаемую человеческую метку.