PageView

Subscribe to our YouTube Channel!
[Jul 12, 2021] New Video: How to Use Django Rest Framework Permissions (DRF Tutorial - Part 7)


Ask Vitor #1: Getting form data to appear in URL and for use in the next view

Ask Vitor #1: Getting form data to appear in URL and for use in the next view

Devon Moore asks:

I want to have the user specify a date in a custom form. This date will append the current URL with the date value path/YYYY-MM-DD/ I then need to capture the date and use it to filter data from the database to display that date’s data.
I’m using class based views and for the report I’m using generic view since this view is a custom report I’m building using multiple db models.


Answer

Here is what Devon wants to achieve:

Result

There are a couple of ways to achieve the desired result. Using class-based views, we could perhaps do something like this:

forms.py

class ReportForm(forms.Form):
    date = forms.DateField()

urls.py

from django.conf.urls import url
from core import views  # import your views using the correct app name

urlpatterns = [
    url(r'report/$', views.Report.as_view(), name='report'),
    url(r'report/(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})/$',
        views.ReportDetails.as_view(), name='report_details'),
]

Example of valid URL matching the report_details pattern: /report/2017-03-17/. If you want to change the URL to use slashes instead of dashes (/report/2017/03/17/), change the URL pattern to this:

    url(r'report/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$',
        views.ReportDetails.as_view(), name='report_details'),

views.py

from django.contrib.auth.models import User
from django.views import View
from django.views.generic.edit import FormView
from django.utils.dateformat import format

class Report(FormView):
    template_name = 'report.html'
    form_class = ReportForm

    def form_valid(self, form):
        date = form.cleaned_data.get('date')
        year = date.year
        month = format(date, 'm')
        day = format(date, 'd')
        return redirect('report_details', year, month, day)

class ReportDetails(View):
    def get(self, request, year, month, day):
        users = User.objects.filter(date_joined__year__gte=int(year))
        # do your thing here, filter the data etc
        return render(request, 'report_details.html', {'users': users})

The view Report is responsible just for validating the user input and redirecting the user to the view that will actually process the report, which is ReportDetails.

The form is posted to Report. Report validates the input and if it is valid, it fires a redirect towards the ReportDetails. ReportDetails grab the date information from the URL, process the querysets and finally returns to the user, rendering the template.

report.html

{% extends 'base.html' %}

{% block content %}
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Generate report</button>
  </form>
{% endblock %}

report_details.html

{% extends 'base.html' %}

{% block content %}
  <ul>
  {% for user in users %}
    <li>{{ user.username }}</li>
  {% endfor %}
  </ul>
  <a href="{% url 'report' %}">New report</a>
{% endblock %}

The final result would be something like this:

Report View

Filtering with the proper URL:

Report Details View


Caveats

This implementation will only work if you only need the date to filter the report.

If you need to pass extra information to do the filtering of the querysets, I would recommend sending the form data directly to the Report view. And perhaps even using a GET request, because you are not modifying the data.

Something like this:

urls.py

urlpatterns = [
    url(r'report/$', views.Report.as_view(), name='report'),
]

views.py

class Report(View):
    def get(self, request):
        if 'date' in request.GET:  # only try to filter if `date` is in the querystring
            form = ReportForm(request.GET)
            if form.is_valid():
                # process the report
                date = form.cleaned_data.get('date')
                invoices = Invoices.objects.filter(date__year=date.year)
                return render(request, 'report_details.html', {'invoices': invoices})
        else:
            form = ReportForm()
        return render(request, 'report.html', {'form': form})

Then you would end up having a URL like this: /report/?date=2017-03-17. And if you had more information in the form, like the status, it would append in the URL: /report/?date=2017-03-17&status=pending.

The URL would still be friendly, and the implementation would be simpler.


Get the Code

The code is available on GitHub: github.com/sibtc/askvitor.