简体   繁体   中英

Django: Accessing model fields from form widget in template

I have a ModelForm which uses a CheckboxSelectMultiple widget to represent data from a ManyToManyField. By default this will render a list of checkboxes with the label being set to the return value of the __str__ method of the related model. However, I wish to display data from other fields in the related model in addition to the choice_label provided by the widget. For example consider the implementation below:

models.py

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Item(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)

    def __str__(self):
        return self.name

class Order(models.Model):
    items = models.ManyToManyField('Item')

forms.py

from django import forms
from .models import Order

class OrderForm(forms.ModelForm):
    class Meta:
        model = Order
        fields = ['items']
        widgets = {'items': forms.widgets.CheckboxSelectMultiple}

template.html

{% for item in form.items %}
    <tr>
        <td>{{ item.tag }}</td>
        <td>{{ item.choice_label }}</td>
        <td>{{ item.category }}</td>
    </tr>
{% endfor %}

The problem here is the {{ item.category }} part of the template.html. When looping through all item checkboxes with {% for item in form.items %} , item is of type BoundWidget , not Item . This is what provides the tag and choice_label attributes for rendering the checkbox for the form. My problem is that BoundWidget doesn't appear to have any reference back to the Item instance that the checkbox represents that would allow me to access any of the additional model data such as the category the item is in. Is there any way around this?

EDIT:

The only way I can think to do this is the following:

templatetags/app_tags.py

from django import template
from models import Item

register = template.Library()

@register.inclusion_tag('app/order_item_table.html')
def show_item_table(items):
    item_objs = []
    for item in items:
        id = item.data['value']
        obj = Item.objects.get(id=id)
        item_objs.append((item, obj))
    return {'items': item_objs}

order_item_template.html

{% for item, obj in items %}
    <tr>
        <td>{{ item.tag }}</td>
        <td>{{ item.choice_label }}</td>
        <td>{{ obj.category }}</td>
    </tr>
{% endfor %}

template.html

{% load app_tags %}

{% show_item_table form.items %}

But this seems incredibly hacky, I would really like to know if there is a better way to do this?

I find a sliglty diffrent solution (I had to reimplement a widget for my own use)

I've created a class for a new widget (i've to change the rending as well, in your case the get_context ovverridign may be enough)

class MyWidget(forms.widgets.CheckboxSelectMultiple):
    template_name = 'web_admin/partial/checkbox.html'
    qs = None

    def get_context(self, name, value, attrs):
        ctx = super().get_context(name, value, attrs)
        ctx.update(dict(qs=self.qs))
        return ctx

note how the get_context update the context with the self.qs

Now, in the form

class MyForm(ModelForm):
    class Meta:
        model = MyModel
        fields = ['my_field']
        widgets = {
            'my_field': MyWidget,

        }

    def __init__(self, qs, *args, **kwargs):
        # this is set in get_form_kwargs of the view
        super().__init__(*args, **kwargs)
        self.fields['my_field'].queryset = qs
        self.fields['data_controller'].widget.qs = qs

the self.fields['my_field'].queryset = qs I need it to filter the possivble options, what you need is self.fields['data_controller'].widget.qs = qs

in the view (I use class based view)

class ConsentCreationDC(CreateView):
    template_name = "web_admin/consent_creation_new.html"
    form_class = MyForm

    def get_form_kwargs(self):
        ctx = super().get_form_kwargs()
        ctx.update({'qs': THE_QUERY_SET})
        return ctx

now inside the widget template (the html file) you have the possibility to access the qs object

and I did {% with obj_item=qs|index:widget.index %} where the filter is

@register.filter def index(List, i): return List[int(i)]

in your case it should be {% with obj_item=qs|index:item.index %} (not sure if item has index

Hope its help

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM