How can I call multiple views in one url address in Django?

Question:

I’m trying to show forms defined by new_measurement on index.html, but I only manage to get IndexView() to work. I tried various combinations between IndexView() and new_measurement(), but those didn’t work out at all. I know that IndexView() doesn’t pass anything related to new_measurement(), and new_measurement() isn’t called, which is the core of my problem. I’d really appreciate if someone more experienced with Django could tell me what I could, or should do. Thank you.

Here’s my views.py:

from django.shortcuts import render
from django.utils import timezone
from .models import Measurement
from .forms import MeasurementForm
from django.views import generic


class IndexView(generic.ListView):
    model = Measurement
    context_object_name = 'measurement_list'
    template_name = 'index.html'
    queryset = Measurement.objects.all()


def new_measurement(request):
    if request.method == "POST":
        form = MeasurementForm(request.POST)
        if form.is_valid():
            measurement = form.save(commit=False)
            measurement.measurement_date = timezone.now()
            measurement.save()
    else:
        form = MeasurementForm()

    return render(request, 'index.html', {'form': form})

urls.py:

from django.urls import path
from . import views


urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]

forms.py:

class MeasurementForm(forms.ModelForm):
    class Meta:
        model = Measurement
        fields = ('measurement_value', 'measurement_unit')

index.html:

{% extends "base.html" %}

{% block content %}
    <h1>Climate Measurement Tool</h1>

    <h2>Add a new measurement</h2>
    <form method="POST" class="post-form">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="save">Add</button>
    </form>

    <h2>Measurements</h2>
    {% if measurement_list %}
    <ul>
        {% for measurement in measurement_list %}
        <li>
            <p>{{ measurement }}</p>
        </li>
        {% endfor %}
    </ul>
    {% else %}
        <p>No measurements yet</p>
    {% endif %}    
{% endblock %}
Asked By: nameisxi

||

Answers:

You can’t call 2 views for one url. basically each url has to be linked to one view and that’s something you can’t really change.

But if you want your code to be cleaner and have multiple functions, you can call them in your view, basically what you can do is to make a view and call it when a url or even more than one url has been used and in that view decide which function to use

Example:

def god_view(request):
    if request.method == "POST"
        return post_func(request)
    return get_func(request)

This is a very simple example but you can do so many other things.

Answered By: Navid Zarepak

You can’t map multiple views in one url but you can do mutiple works in one view.
update your views.py as you can see that I am sending (querylist and form) both in that view

views.py

def new_measurement(request):
    if request.method == "POST":
        form = MeasurementForm(request.POST)
        if form.is_valid():
            measurement = form.save(commit=False)
            measurement.measurement_date = timezone.now()
            measurement.save()
    else:
        form = MeasurementForm()

    qs = Measurement.objects.all()
    context = {'form': form, 'measurement_list': qs}
    return render(request, 'index.html', context)

update urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('', views.new_measurement, name='index'),
]
Answered By: Sagar

It is not possible to have more views in one url, but you can simulate it. I did it like a view and in the template of this view was javascript which loaded the second view with the response of AJAX and filled the belonging element with the second view’s content. The second view was not whole template but it started with some div tags which were placed into the first template. I’ll try to give you an example

views

def first_view(request):
    return render(
        request,
        'first_template.html',
        {
            'first_content': 'Some heavy content'
        })


def second_view(request):
    return render(
        request, 
        'second_template.html', 
        {
            'second_content': 'Some heavier content!'
        })

first_template.html

...
<body>

    <div id="1">
        {{ first_content }}
    </div>
    <div>
        ... loading ...
    </div>

    <script>

    window.onload = function() {
        $.ajax({
            url: {% url 'to_second_view' %},
            method: 'GET',
            success: function(response) {
                $('#2').html(response);
            }
        })
    }

    </script>

</body>
...

second_template.html

<div>
    {{ second_content }}
</div>
Answered By: Kryštof Řeháček

If you’re using cbv you can override the get_template_names method for any view that inherits TemplateResponseMixin and return a list of string which are searched in order until one matches or ImporperlyConfigured is raised. For example:

class SomeView(TemplateResponseMixin):
   ...
   def get_template_names(self):
      if self.request.method == "POST":
         return ['post_template.html']
      else:
         return ['template.html']
Answered By: Nnaobi

Instead of generic.ListView you can try with rest_framework.views.APIView

from rest_framework.views import APIView

class IndexView(APIView):
    
    def post(self, request: Request):
        form = MeasurementForm(request.POST)
        if form.is_valid():
            measurement = form.save(commit=False)
            measurement.measurement_date = timezone.now()
            measurement.save()
            return render(request, 'index.html', {'form': form})

    def get(self, request: Request):
        form = MeasurementForm()
        return render(request, 'index.html', {'form': form})

This gives you more control on the APIs you call. Also you can raise/return error when you call your API using incorrect methods (PUT, PATCH)

Answered By: bhave7