Django: Pass arguments from other form to submit button

Viewed 923

I'm building a simple Django app that lets users track stuff for specific days:

form

It records entries with a name and a date using the upper form.

<form action="" method="post" style="margin-bottom: 1cm;">
        {% csrf_token %}

        <div class="form-group">
            {{ form.entry_name.label_tag }}
            <div class="input-group">
                <input type="text" class="form-control" id="{{ form.entry_name.id_for_label }}" name="{{ form.entry_name.html_name }}" aria-label="new entry field">
                {{ form.entry_date }}

                <div class="input-group-append">
                    <button type="submit" class="btn btn-primary">Add</button>
                </div>
            </div>

            <small id="{{ form.entry_name.id_for_label }}Help" class="form-text text-muted">This can be anything you want to track: An activity, food, how you slept, stress level, etc.</small>
        </div>
    </form>

Below the form, there are quick add buttons that let users quickly add a new entry with a specific name. In addition, I'd like to use the date selected in the form above. I.e., if a user sets a date in the upper form but then clicks one of the suggested buttons, it should still use the selected date for adding the new entry.

This is what the code for the suggested buttons currently looks like:

{% if entry_counts and entry_dict|length > 0 %}
    <div class="card" style="margin-bottom: 1cm;">
        <div class="card-body">
            <div class="card-title">Suggested entries</div>
            {% for name, count in entry_counts.items %}
                <form method="post" action="{% url 'app:add_entry_with_date' name form.entry_date.value %}" style="display: inline-block;">
                    {% csrf_token %}
                    <button type="submit" class="btn btn-secondary" name="{{ name }}" style="margin-bottom: 5px;">{{ name }}</button>
                </form>
            {% endfor %}
        </div>
    </div>
    {% endif %}

I'm trying to access the selected date and pass it to the corresponding view: action="{% url 'app:add_entry_with_date' name form.entry_date.value %}", but it still adds the entry at the default date (today) not on the selected date. My guess, is that the problem is with <button type="submit" class="btn btn-secondary" name="{{ name }}" style="margin-bottom: 5px;">{{ name }}</button>. Does this just pass name but not the date when submitting?

Here are the relevant URL patterns:

class DateConverter:
    regex = '\d{4}-\d{2}-\d{2}'

    def to_python(self, value):
        return datetime.datetime.strptime(value, '%Y-%m-%d')

    def to_url(self, value):
        return value


register_converter(DateConverter, 'yyyymmdd')


urlpatterns = [
    path('', views.index, name='index'),
    path('add/<entry_name>/', views.add_entry, name='add'),
    path('add/<entry_name>/<yyyymmdd:entry_date>/', views.add_entry, name='add_entry_with_date'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

So whenever adding a new entry (with or without specific date), my add_entry view is called:

@login_required
def add_entry(request, entry_name, entry_date=datetime.date.today()):
    # only works for post
    # if request.method == 'POST':
    entry_name = entry_name.strip().lower()
    entry = Entry.objects.create(name=entry_name, date=entry_date, owner=request.user)
    return HttpResponseRedirect(reverse('app:index'))
1 Answers

You're trying to pass the date value as part of the URL,

{% url 'app:add_entry_with_date' name form.entry_date.value %}

however, form.entry_date.value won't have a defined value unless your form is bound before it's passed to the template for rendering. As a result, probably, your add_entry view is being called via the URL pattern add, not add_entry_with_date.

Another challenge with your current code is that you want to have the same date-type input element ({{ form.entry_date }}) serve as the source for different, separate HTML forms (you have the first form for adding entries, and then you have one form for each suggested entry). Changing the value of that input when the page is already rendered in the browser won't update the action URLs for the suggested entry forms—unless you use JavaScript.

I think the quickest way to make your existing code work is to write some JavaScript to manipulate the action attribute for the suggested-entry forms whenever the date input value changes.

Manipulating action attributes looks strange though, and also I believe your view, which should work only for POST requests, should use only POST data and not rely on URL parameters. Therefore I recommend that you use hidden inputs, e.g.

<input type="hidden" name="variable-name" value="temporary-date-value-here">

and then have the JavaScript manipulate these input elements' values instead of the form action attribute. Of course you have to update the view too.

Update: sample JS for synchronizing inputs across forms

HTML:

<html>

    <head>
        <title>Sample synchronization of inputs across forms</title>
    </head>

    <body>
        <h1>Sample synchronization of inputs across forms</h1>

        <h2>Form 1</h2>
        <form>
            <input class="synchronized-inputs" type="date" name="input_date">
        </form>

        <h2>Form 2</h2>
        <form>
            <input class="synchronized-inputs" type="date" name="input_date">
        </form>

        <script src="sync-inputs-across-forms.js" type="text/javascript"></script>

    </body>
    
</html>

JavaScript (sync-inputs-across-forms.js):

var syncedInputs = document.getElementsByClassName('synchronized-inputs');

Array.from(syncedInputs).forEach((source) => {
    source.addEventListener('change', () => {
        Array.from(syncedInputs).forEach((target) => {
            target.value = source.value;
        });
    });
});

Note that:

  • Without the JS, selecting a date in one form won't update the other form's value
  • As indicated in the original answer, you'd want to use hidden inputs for the suggested-entry forms. To do that, just change type="date" to type="hidden" for the other form. Synchronization will still work as the value is tracked in the (invisible parts of the) DOM.
Related