Isn’t there a more direct way to style rendered ModelForms (hence add CSS classes or ID’s?) – without looking up the underlying widget class for every form field?

It’s possible to override the default form fields.

Furtunately there is a Filed types table in the Django Documentation which describes the relations between every model field and its corresponding default form field.

Add CSS class in ModelForm – Meta class

Defining a widget with a class attribute in the respective form Meta class – by overriding the default form field (see above) – seems to be the common way (stackoverflow) (according to the stackoverflow votes):

### app/models.py
class Ad(models.Model):
    # ...
    adTitle = models.CharField(max_length=100)


### app/forms.py
from .models import Ad
from django.forms import DateInput, ModelForm
from django.utils.translation import gettext_lazy as _

class CreateAdForm(ModelForm):
    class Meta:
        model = Ad
        fields = ['adTitle',
                # ...
                ]
        labels = {
            'adTitle': _('Ad Title'),
            # ...
        }
        widgets = {
            'adTitle': CharField(attrs={'class': 'form-field'}),
            # ... 
        }

However this approach seems to be tedious work, if one wants to ascribe one (or few) certain classes to all form fields – for example to style all form fields equally.

This approach may seem so far, so good – not too complicated.
The slight problem is that it doesn’t work for me.
When I run the local server using py manage.py runserver I get the following error:

Watching for file changes with StatReloader
Performing system checks...

Exception in thread django-main-thread:
Traceback (most recent call last):
  File "C:\Program Files\Python39\lib\threading.py", line 973, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Python39\lib\threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\core\management\commands\runserver.py", line 124, in inner_run
    self.check(display_num_errors=True)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\core\management\base.py", line 438, in check
    all_issues = checks.run_checks(
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\core\checks\registry.py", line 77, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\core\checks\urls.py", line 13, in check_url_config
    return check_resolver(resolver)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\core\checks\urls.py", line 23, in check_resolver
    return check_method()
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\urls\resolvers.py", line 446, in check
    for pattern in self.url_patterns:
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\utils\functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\urls\resolvers.py", line 632, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\utils\functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\urls\resolvers.py", line 625, in urlconf_module
    return import_module(self.urlconf_name)
  File "C:\Program Files\Python39\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "C:\Users\Name\codingprojects\CoderMatching\codermatching\codermatching\urls.py", line 20, in <module>
    path('', include('codermatch.urls')),
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\urls\conf.py", line 34, in include
    urlconf_module = import_module(urlconf_module)
  File "C:\Program Files\Python39\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "C:\Users\Name\codingprojects\CoderMatching\codermatching\codermatch\urls.py", line 3, in <module>
    from . import views
  File "C:\Users\Name\codingprojects\CoderMatching\codermatching\codermatch\views.py", line 9, in <module>
    from .forms import CreateAdForm
  File "C:\Users\Name\codingprojects\CoderMatching\codermatching\codermatch\forms.py", line 5, in <module>
    class CreateAdForm(ModelForm):
  File "C:\Users\Name\codingprojects\CoderMatching\codermatching\codermatch\forms.py", line 6, in CreateAdForm
    class Meta:
  File "C:\Users\Name\codingprojects\CoderMatching\codermatching\codermatch\forms.py", line 27, in Meta
    'adTitle': CharField(attrs={'class': 'form-field'}),
  File "C:\Users\Name\codingprojects\CoderMatching\venv\lib\site-packages\django\forms\fields.py", line 216, in __init__
    super().__init__(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'attrs'

Initial Thoughts about potential Solutions

Instead of the above suggested solution (if it worked…), I thought it would be nice to have a less redundant and eventually more intutive way to add CSS classes to ModelForm’s in Django.

Here are my first few thoughts how adding of CSS classes in ModelForm’s could be made more intuitive and less redundant:

  1. Some kind of notation (a specifier/keyword?) which automatically uses the correct widget class for the respective model field could be a solution.

Does this make sense?

  1. An even more holistic(?) solution could be an approach to specify certain classes for all fields which are used in the respective ModelForm – at once (with a single expression, instead of still defining all classes for every field one by one).

Does this make sense?

Actual Solution(s)

Besides there is a discussion within the accepted stackoverflow solution that the above described approach (Add CSS class in ModelForm – Meta class) is not good practice, because:

  1. Backend (functionality) and frontend (styling) should be separated …especially in teams where the work is clearly separated between backend and frontend developers
  2. This needs a lot of complicated code for a small change

According to mutliple people in the discussion on stackoverflow, a cleaner approach would be to use a custom filter instead.
They refer to the second most upvoted answer as a ‚cleaner‘ solution.

The ridiculous part about this recommendation is, that adding a custom filter is even way more code compared to the amendment of the field’s attributes.

However amending the attribute of the field involves referring to the ancestor classes of the respective ModelForm (using inheritance with the super() class).

And according to Zed Shaw’s Learn Python 3 the Hard Way and according to John Sonmez’s explanation in some Simple Programmer Podcast episode, using composition should (almost) always be preferred over inheritance.
That’s because inheritance gets complicated very quickly – as we see in the accepted stackoverflow solution above.

Suggestions and Opinions

Any further suggestions and thoughts?
I’m looking forward to your feedback and suggestions.🤗