Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Questions on how to organize properly django forms #166

Closed
EmanueleSarr opened this issue Jan 31, 2025 · 7 comments
Closed

Questions on how to organize properly django forms #166

EmanueleSarr opened this issue Jan 31, 2025 · 7 comments

Comments

@EmanueleSarr
Copy link

Since the guide does not go into the detail of the forms, I wonder what would be the best practices to create them. Personally, I do not like the fact that they mix the logic of validation and presentation, what would be the optimal way to manage it?
For example, if I want to insert placeholders on a form in an automatic way (without making each field individually in the model) I must necessarily put the logic or in the form or in the view, and frankly the cleaner solution seems to me to put it in the form, However, as I said above, it does not seem to me the greatest of solutions, I look for suggestions.
Thank you in advance.

@RadoRado
Copy link
Member

@EmanueleSarr Hello 👋

When it comes to forms, we usually look at them as serializers:

  1. They turn something (can be a model, can be a plain object) into an HTML form.
  2. They handle validation
  3. And the final result of the forms is cleaned_data - https://docs.djangoproject.com/en/5.1/ref/forms/validation/ - which you can then take & use as an input for your service layer.

On a high-level, that's pretty much it.

Now, forms are baked-in the more generic Django Views. That's why you may want to use something more simple, such as TemplateView.

Let me know if this is enough 👍

@EmanueleSarr
Copy link
Author

Hello, thank you for the answer, however, I still do not know if I should put the presentation logic on the form, or not. Some things but I think you can not do menop that you do not put part of the presentation logic on the form, For example if I have to create placeholders from a dictionary (where the keys are field names and the values are placeholders to apply) that is only possible on forms, and not on templates, however this would lead to having the presentation logic both on the form, is on the template.

@RadoRado
Copy link
Member

RadoRado commented Feb 1, 2025

@EmanueleSarr I think I get what you are asking, but if I see a code example, it'll be really helpful 🙏

Nevertheless, this can be considered a problem with Django forms, because some of the presentation logic can be expressed in the abstraction itself, on Python level.

And some of the presentation logic can be expressed in the HTML / Django templates.

Again, this can be considered a problem, but also, it can be considered "by design" - it's up to you what to use.

My general approach in such scenarios is the following:

Can I do everything in one place? If yes - I'd stick to that, no matter the place, until I reach an example that no-longer fits my assumptions and framework.

The Django Forms are quite the powerful abstraction and I'll also look there first, especially if I'm not rendering the forms field by field, but rather simply as:

<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit">
</form>

or if you are using other 3rd party packages that deals with that.

If you are rendering the form, piece by piece, where most of the control lies on the frontend / tempalting side of things - then - I'd think about moving some of the presentation logic there.

But again, if you give me few examples, I'll have a better idea and give you a better answer.

Cheers!

@EmanueleSarr
Copy link
Author

EmanueleSarr commented Feb 1, 2025

Thank you for your answer, I understand.
I leave the case below. I would like to understand if there was a clean way to be able to automatically manage the placeholders inside the template, without having to insert it in the logic of the form. Besides that, I was thinking of using django-crispy-forms to be able to handle everything from the form class, to keep the template clean, but I don’t know how flexible that library is.
I also tried to use it, but using tailwind (through nodejs to be able to customize it in a clean way via configuration files) the pattern seemed not to take python files correctly.
I'm also using a custom field.html to put my custom classes.

form.html:

    <form class="w-full max-w-[500px] flex flex-col p-[24px] gap-[24px] bg-secondary border border-gray-700 rounded-[8px]" hx-post="{% url 'beta_subscription:beta_subscription' %}">
        {% csrf_token %}

        
        {% for hidden_field in form.hidden_fields %}
            {{ hidden_field }}
        {% endfor %}

        {% if form.non_field_errors %}
            <div class="">
                {% for error in form.non_field_errors %}
                <p class="text-danger-500">{{error}}</p>
                {% endfor %}
            </div>
        {% endif %}

        {% for field in form.visible_fields %}
            {{field.as_field_group}}
        {% endfor %}

        <button class="btn-brand-md w-full text-white text-center" type="submit">Submit</button>
    </form>

forms.py:

class BetaSubscriptionForm(ModelForm):
    class Meta:
        model = BetaSubscription
        exclude = ["id"]
        
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        placeholders = {
            "name": "James",
            "surname": "Doe",
            "email": "[email protected]"
        }
        
        set_placeholder(self, placeholders)

field.html:

{% load widget_tweaks %}

{% if field.field.widget.input_type == "checkbox" %}
    <div class="w-full flex flex-row gap-[8px]">
{% else %}
    <div class="w-full flex flex-col gap-[8px]">
{% endif %}

    {% if field.use_fieldset %}
        <fieldset 
            {% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %}
                aria-describedby="{{ field.auto_id }}_helptext"
            {% endif %}
        >
            {% if field.label %}
            {{ field.legend }}
            {% endif %}
    {% else %}
        {% if field.label %}
            <label for="{{ field.id_for_label }}" class="text-white">
                {{ field.label }}
            </label>
        {% endif %}
    {% endif %}

    {% if field.help_text %}
        <div class="helptext" 
            {% if field.auto_id %}
                id="{{ field.auto_id }}_helptext"
            {% endif %}
        >
            {{ field.help_text|safe }}
        </div>
    {% endif %}

    {% if field.field.widget.input_type == "checkbox" %}
        {{ field|add_class:"" }}
    {% else %}
        {{ field|add_class:"text-form-field" }}
    {% endif %}

    <!-- {% if form.is_bound %}
        {% if field.errors %}
            {% render_field field class="form-control is-invalid" %}
                {% for error in field.errors %}
                    <div class="invalid-feedback">
                        <p class="text-danger-500">{{error}}</p>
                    </div>
                {% endfor %}
        {% else %}
            {% render_field field class="form-control is-valid" %}
        {% endif %}
    {% else %}
        {% render_field field class="form-control" %}
    {% endif %} -->

    {% if field.errors %}
    {% for error in field.errors %}
        <p class="text-danger-500">{{error}}</p>
    {% endfor %}
    {% endif %}

    {% if field.use_fieldset %}
        </fieldset>
    {% endif %}
</div>

@RadoRado
Copy link
Member

RadoRado commented Feb 5, 2025

@EmanueleSarr thank you for the extensive example.

From what I see - you require a lot of flexibility, so rendering the form, piece by piece, in the HTML, looks like the right way to do it.

If you want to move that flexibility to the Python side, you'll effectively have to implement your own small version of something that resembles django-crispy-form, so you'll have the declarative power of Python and render exactly how you want to render.

My suggestion would be to try to do a POC and see how hard it's going to be to achieve what you want.

You can always read the implementation of django-crispy-forms for ideas 👍

If not - the approach that you have - it's ok. As long as you separate your forms into small & includable HTML files, I think you'll be able to manage.

In fact, if you go this route, I'd suggest looking at something like https://mitchel.me/slippers/docs/introduction/ (and the other similar options) - so you can have a more component-based approach with the templates.

Cheers!

@EmanueleSarr
Copy link
Author

All clear, thank you. I think I’ll go for django-crispy-forms, going to create a custom template, so that I can be flexible, and at the same time keep all the presentation logic in one place, in this case the django forms (python side)

@RadoRado
Copy link
Member

@EmanueleSarr Sounds like a good approach 👍

Best of luck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants