English 中文(简体)
Get type of Django form widget from within template
原标题:

I m iterating through the fields of a form and for certain fields I want a slightly different layout, requiring altered HTML.

To do this accurately, I just need to know the widget type. Its class name or something similar. In standard python, this is easy! field.field.widget.__class__.__name__

Unfortunately, you re not allowed access to underscore variables in templates. Great!

You can test field.field.widget.input_type but this only works for text/password <input ../> types. I need more resolution that that.

To me, however difficult it might look, it makes most sense to do this at template level. I ve outsourced the bit of code that handles HTML for fields to a separate template that gets included in the field-loop. This means it is consistent across ModelForms and standard Forms (something that wouldn t be true if I wrote an intermediary Form class).

If you can see a universal approach that doesn t require me to edit 20-odd forms, let me know too!

最佳回答

As of Django 1.11, you can just use widget.input_type. Example:

{% for field in form.visible_fields %}
    <input type="{{ field.field.widget.input_type }}"
           id="{{ field.id_for_label }}"
           name="{{ field.html_name }}"
           placeholder="{{ field.label }}"
           maxlength="{{ field.field.max_length }}" />
{% endfor %}
问题回答

Making a template tag might work? Something like field.field.widget|widget_type

Edit from Oli: Good point! I just wrote a filter:

from django import template
register = template.Library()

@register.filter( klass )
def klass(ob):
    return ob.__class__.__name__

And now {{ object|klass }} renders correctly. Now I ve just got to figure out how to use that inside a template s if statement.

Edit from Oli #2: I needed to use the result of that in an if statetement in-template, so I just shifted all that logic into the templatetag. Magic. Thanks for poking me in the right direction.

Following up on the accepted answer - the enhanced if tag in Django 1.2 allows you to use filters in if tag comparisons. So you could now do your custom html/logic in the template like so:

<ul>
{% for field in form.fields %}
  <li>
    {% if field.field.widget|klass == "Textarea" %}
    <!-- do something special for Textarea -->
    <h2>Text Areas are Special </h2>
    {% else %}      
      {{ field.errors }}
      {{ field.label_tag }}
      {{ field }}
    {% endif %}

  </li>
{% endfor %}
</ul>

Following the answer from Oli and rinti: I used this one and I think it is a bit simpler:

template code: {{ field|fieldtype }}

filter code:

from django import template
register = template.Library()

@register.filter( fieldtype )
def fieldtype(field):
    return field.field.widget.__class__.__name__

Perhaps worth pointing out to contemporary readers that django-widget-tweaks provides field_type and widget_type template filters for this purpose, returning the respective class names in lowercase. In the example below I also show the output of the input_type property on the field widget (since Django 1.11), which may also be useful.

forms.py:

class ContactForm(forms.Form):
    name = forms.CharField(
        max_length=150,
        required=True,
        label= Your name 
    )

template.html:

{% load widget_tweaks %}

{% for field in form.visible_fields %}
{{ field.label }}
{{ field.field.widget.input_type }}
{{ field|field_type }}
{{ field|widget_type }})
{% endfor %}

Result:

Your name
text
charfield
textinput

Between these various options you should be able to find the right property to target for just about any use-case. If you need to capture the output of one of these filters for use in if statements, you can use the with template tag.

You can make every view that manages forms inherit from a custom generic view where you load into the context the metadata that you need in the templates. The generic form view should include something like this:

class CustomUpdateView(UpdateView):
    ...
    def get_context_data(self, **kwargs):
       context = super().get_context_data(**kwargs)
       ...
       for f, value in context["form"].fields.items():
          context["form"].fields[f].type = self.model._meta.get_field(f).get_internal_type()
       ...
       return context

In the template you can access these custom properties through field.field:

{% if field.field.type ==  BooleanField  %}
     <div class="custom-control custom-checkbox">
     ...
     </div>
{% endif %}

By using the debugger of PyCharm or Visual Studio Code you can see all the available metadata, if you need something else besides the field type.

For anyone here whose purpose is to customise widget style according to its type, based on @oli answer great idea, I decided to set the mapping directly in the template filter, and return the corresponding classes directly. This avoids messing with {% if %} statements in the template.

from django import template

register = template.Library()

BASIC_INPUT = "border border-indigo-300 px-2.5 py-1.5 rounded-md focus:outline-none"

mapping = {
    "Select": BASIC_INPUT,
    "TextInput": BASIC_INPUT,
    "EmailInput": BASIC_INPUT,
    "RegionalPhoneNumberWidget": BASIC_INPUT,
    "ModelSelect2": "",  # let the default markup
}


@register.filter("get_field_classes")
def get_field_classes(field):
    widget_class_name = field.field.widget.__class__.__name__
    try:
        return mapping[widget_class_name]
    except KeyError:
        raise ValueError(f"Classes related to {widget_class_name} are not defined yet")

Then in your default form template (e.g: django/forms/default.html), when browsing fields, with the help of django-widget-tweaks package:

{% load widget_tweaks %}
{% load get_field_classes %}

{% for field in form.visible_fields %}

  {# .... #}

  <div>
    {% with field_classes=field|get_field_classes %}
        {{ field|add_class:field_classes }}
    {% endwith %}
  </div>
{% endfor %}




相关问题
How to get two random records with Django

How do I get two distinct random records using Django? I ve seen questions about how to get one but I need to get two random records and they must differ.

Moving (very old) Zope/Plone Site to Django

I am ask to move data from a (now offline) site driven by Plone to a new Django site. These are the version informations I have: Zope Version (unreleased version, python 2.1.3 ) Python Version 2.1....

Can Django models use MySQL functions?

Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like ...

Flexible pagination in Django

I d like to implement pagination such that I can allow the user to choose the number of records per page such as 10, 25, 50 etc. How should I go about this? Is there an app I can add onto my project ...

is it convenient to urlencode all next parameters? - django

While writing code, it is pretty common to request a page with an appended "next" query string argument. For instance, in the following template code next points back to the page the user is on: &...

Pragmatically adding give-aways/freebies to an online store

Our business currently has an online store and recently we ve been offering free specials to our customers. Right now, we simply display the special and give the buyer a notice stating we will add the ...

热门标签