Apply html class according to "input" type.

I have forms that have input type like text, number. I also have select and textarea. What is the best way to apply a class according to input type and the like. I come with this as initial idea, but there must be something better.

class ForecastadjustmentForm(forms.ModelForm):
    require_input_type = ("number", "text")

    class Meta:
        model = ForecastAdjustment
        fields = ["costcenter", "fund", "amount", "note"]

    def __init__(self, *args, **kwargs):
        super(ForecastadjustmentForm, self).__init__(*args, **kwargs)

        for name, field in self.fields.items():
            try:
                if field.widget.input_type in self.require_input_type:
                    field.widget.attrs.update({"class": "input"})
            except (AttributeError):
                pass  # Because textarea sucks

Can you explain more what your use-case is? I’m not understanding what you’re trying to do here.

What I can see is that you’re creating a form. (Specifically, a model form) This form is (typically) going to be used to create html.

You already have the facilities to specify a widget and css attributes on each individual field. You also have the ability to override default widgets for fields.

So I’m not seeing what benefit you’re looking to achieve beyond what Django already provides.

@KenWhitesell
Yep, let see if that can help a bit.
The template:

{% extends 'base.html' %}

{% block content %}
<main class="block block--centered">
    <section class="form">
        <form method="POST">
            {% csrf_token %}
            {{ form.management_form }}
            {{ form.errors }}
            {{ form.non_field_errors }}
            {{ form.non_form_errors }}
            <header class='form__header'>Forecast Adjustment</header>
        <div class='form__fields'>
            <label for="costcenter" class="label">Cost Element</label>
            <div class="select"> {{form.costcenter}} </div>

            <label class="label" for="fund">Fund</label>
            <div class="select"> {{form.fund}} </div>

            <label class="label" for="amount">Amount</label>{{form.amount}}
            
            <label class="label" for="note">Note</label>{{form.note}} 
        </div>
            <div class="block block--spaced">
                <button class="btn">Save</button>
                <button class="btn btn-back">Back</button>
            </div>
        </form>
        <footer class='form__footer'>Created on {{form.instance.created}}. Last edited by {{form.instance.owner}} on  {{form.instance.updated}}</footer>
    </section>
</main>
</body>
</html>

{% endblock content %}

And the model:

class ForecastAdjustment(models.Model):
    costcenter = models.ForeignKey(Costcenters, on_delete=models.CASCADE, null=True)
    fund = models.ForeignKey(Funds, on_delete=models.CASCADE, null=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    note = models.TextField(null=True, blank=True)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.RESTRICT)
    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True, null=True)

    def __str__(self):
        return f"{self.costcenter} - {self.fund} - {self.amount}"

The only other option I can see is that I set the styling directly in the say textarea in the css file, but I would rather avoid that. Hence if if in my form class (ForecastAdjustmentForm) I have a text area, I want to appy say a .textarea styling class in html.

Still not clear, but I think we’re closer.

Are you saying, that for all widgets that are of type “textarea”, you want the css class “textarea” applied to that widget?

Yes, this is exactly what I am trying to achieve.

Something like this, but it does not work with textarea given it is not an input. testarea does not have an attribute input_type

    def __init__(self, *args, **kwargs):
        super(SearchSiteForm, self).__init__(*args, **kwargs)

        for name, field in self.fields.items():
            if field.widget.input_type == "checkbox":
                field.widget.attrs.update({"class": "check"})
            else:
                field.widget.attrs.update({"class": "input"})
            field.required = False

This isn’t how you want to do this.

You’ve actually got at least three options, depending upon the “scope of effect” that you want for this. (In other words, the answer may depend upon whether you want this to apply only on this form, for all forms in this app, or universally across your project.)

  • Override the template used by the textarea widget to always include that css class.

  • Create a new widget subclass for Textarea, and add that css class as a default attribute. Configure your form to use your modified widget.

    • As a variant of this, create a different field class to select your Textarea widget as a default with that parameter applied.
  • Add that attribute to the individual fields in the form definition.

Ideally it should be across the project. At least until I hit an exceptional case.
Thanks, I will go back to the docs I guess.

I guess the option to override the template of textarea would do that if I interpret your statement correctly

Correct. Implementing that option would probably be the easiest way of doing that.

Can you tell me if that make sense? It works, but I would like to know if it is appropriate.

  1. I added django.forms in INSTALLED_APPS
  2. Added FORM_RENDERER = “django.forms.renderers.TemplatesSetting” in setting.py although I did not see this in the docs
  3. Created django/forms/widgets directory in my project templates directory.
  4. Created my own textarea.html template heavily inspired from stock template as shown below with required class.
<textarea class='input' name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
    {% if widget.value %}{{ widget.value }}{% endif %}</textarea>

That seem kind of neat given it is now project wide and I can globally adjust this widget as necessary.

Mentioned in the second paragraph at Overriding built-in widget templates.

Yep, you got it!

Thanks.
I was expecting something more explicit in the paragraph you refer to. something like the statement made higher in the page DjangoDivformRenderer

Anyway, this makes my day.