1

In Django I have two functions contained in views.py. One is called def city_detail and the other is called def DemoView. Both return return render(request, 'app1/home.html', context).

In urlpatterns of url.py i would like to use both functions, but it seems I can only use 1 at a time. If I use only one of the two, then it works (only one of the two works). If use both functions i get error:

 path('home/', views.city_detail, views.DemoView, name='home'),
 path('home/', views.DemoView, name='home'),

I get the error:

     raiseTypeError(
TypeError: kwargs argument must be a dict, but got function.

How can I use both functions in home?

UPDATE

I update the question by writing the whole code

I believed there was no need for the code as I believed the solution was simpler. Update to improve the question. I added the code that I use in the project.

home.html is the page I go to after logging in

app1/urls.py

from django.urls import path, include
from . import views
from app1.views import index 

urlpatterns = [
    path('', index, name='index'),
    path('home/', views.city_detail,  views.DemoView, name='home'),
    path('home/', views.DemoView, name='home'),

 ]

app1/views.py

from django.shortcuts import render, redirect

#Simple Combobox
def DemoView(request):
  color_choices = ("Red","Blue","Black","Orange")
  message =''
  if request.method =="POST":
    picked = request.POST.get('color')
    if picked == 'Red':
      message = "<<< You chose red"
      print(message)
    elif picked == 'Blue':
        message = "<<< You chose blue"
    elif picked == 'Black':
        message = "<<< You chose black"
    else:
        message = "<<< Oh no, you chose orange"
  context = {'message':message,'color_choices':color_choices}
  return render(request, 'app1/home.html', context)

#Combobox from Database
def city_detail(request):
    form = SelectCityForm()
    object = None
    if request.method == "POST":
        form = SelectCityForm(request.POST)
        if form.is_valid():
            city_id = form.cleaned_data['city']
            object = City.objects.get(id=city_id)


    context = {'form': form, 'object': object}
    return render(request, 'app1/home.html', context)

app1/models.py (for combobox from database)

from django.db import models

class City(models.Model): 
    name = models.CharField(max_length=30) 

    def __str__(self):
        # here you want to return the name
        # print function on an instance of this model then returns the name
        return self.name

app1/forms.py

from django import forms

#Combobox Semplice
class SimpleCombobox(forms.Form):
    Col1 = 'Red'
    Col2 = 'Blue'
    Col3 = 'Black'
    Col4 = 'Orange'
      
    COLOR_CHOICES = (
        (Col1, u"Red"),
        (Col2, u"Blue"),
        (Col3, u"Black"),
        (Col4, u"Orange"),
    )
    cities = forms.ChoiceField(choices=COLOR_CHOICES)


class SimpleTextbox(forms.Form):
    coverletter = forms.CharField(required=False,
              widget=forms.Textarea(
                # rows and colums of the textarea
                attrs={'rows': 4, 'cols': 40}))

from .models import City

class SelectCityForm(forms.Form):
    city = forms.ModelChoiceField(queryset=City.objects.all())

    class Meta:
        model = City
        fields = ('name')

app1/home.html

  <div class="b-example-divider b-example-vr"></div>

  <div class="app">

    <form action="" method="post">
      {% csrf_token %}
      {{ form }}
      <input type="submit" value="Submit">
  </form>
  {% if object %}
  Here you can embed data of your city such as the ID: {{ object.id }} or the Name: {{ object.name }}. Since you defined the __str__ method you can also
  just put {{ object }} and it will give the name.
  {% endif %}

  </div>
      
    <div class="container p-5">
      <div class="row mx-auto">
        <div class="col-6">
          <form action="" method="POST" novalidate class="form-group">
            {% csrf_token %}
            <select name="color" class="form-select" >
              {% for i in color_choices %}
              <option value="{{i}}">{{i}}</option>
              {% endfor %}
            </select>
            <textarea class="form-control" name="msg" cols="30" rows="10">{{message}}</textarea>
            <button class="btn btn-primary mt-3" type="submit">Submit</button>
          </form>
        </div>
      </div>
    </div>
Horiatiki
  • 398
  • 1
  • 10
  • The capitalisation of `DemoView` suggests it might a class-based view? (compared to `def city_detail` being a function view). If it's a class view you have to route it like `view.DemoView.as_view()` https://docs.djangoproject.com/en/4.2/topics/class-based-views/intro/#using-class-based-views – Anentropic Jul 13 '23 at 09:26
  • Oh, no, I see that is not what you are asking... it is not possible to route one url path to two view functions. What do you even expect the behaviour to be in that case? You need to have a unique url for each view. – Anentropic Jul 13 '23 at 09:28
  • how would you use two views? A view converts a request into a response, imagine you use two, then which of the two views should be picked? – Willem Van Onsem Jul 13 '23 at 09:28
  • Are you perhaps conflating the url path `home/` with the template path `app1/home.html`? You can have multiple views render the same template, but it makes no sense to want the same url to route to multiple views. – Anentropic Jul 13 '23 at 09:30
  • @Anentropic I'm just starting out with Django. I'm trying to understand. Sorry for the trivial question. The two functions are linked to two comboboxes. So I expect both comboboxes to work. – Horiatiki Jul 13 '23 at 09:30
  • A view corresponds to a 'page' or 'screen' in your website. The individual components of the page need to be in the template rendered by a single view. – Anentropic Jul 13 '23 at 09:32
  • @Willem VO supports mod strike One function is tied to one combobox, while the other function is tied to another combobox. I would like to run both comboboxes. I need both comboboxes to work in the app1/home.html page – Horiatiki Jul 13 '23 at 09:32
  • If you want to tie multiple input components together then Django has an abstraction for that: Forms https://docs.djangoproject.com/en/4.2/topics/forms/ – Anentropic Jul 13 '23 at 09:33
  • Can you help me please? How can I solve the problem? I thought it was possible to use multiple functions and return their results in the same html page. So should I put all the code in one function? – Horiatiki Jul 13 '23 at 09:34
  • the view is a single function, within the view function you can call multiple functions – Anentropic Jul 13 '23 at 09:35
  • If you (both) wait a few minutes, please update the question and write my code. Thank you – Horiatiki Jul 13 '23 at 09:35
  • I've updated the question. I hope you can help me. Thank you – Horiatiki Jul 13 '23 at 09:46
  • You want one Form. A form is a collection of fields. You have currently defined one form for each field... put them all together in one form and then put that one form in one view. i.e. the django `Form` class corresponds to all the fields submitted together in the HTML `
    `
    – Anentropic Jul 13 '23 at 09:59
  • @Anentropic Could you show me the code please? Are you referring to forms as the other kind user also said? He said "you normally do n't render comboboxes manually, you simply add these to the form as extra form field." I will be glad to accept your question in case of any help. – Horiatiki Jul 13 '23 at 10:19

1 Answers1

3

How can I use both functions in home?

You don't. It makes no sense. A view is a function that takes as parameter the request (and optionally some url parameters), and turns that into a response.

If there are two views, then hwo should it compose a response? Both views then produce a response, so what response should be picked?

A view is thus a function that determines how to map a request to a response, and you "fire" a view by making a request to a certain path.

People often think that a path is somehow related to a template, but that is not the case: a view can render zero, one, or multiple templates. Rendering templates is just a utility most web servers offer to make generating responses easier, but it is not necessary at all to render a template, or only render one.

You can make utilty methods, define context processors, etc. that make it more convenient to pass data to the rendering process, but these are not views, there are just helper functions.

So in this case you combine these with:

from django.shortcuts import redirect, render


def city_detail(request):
    form = SelectCityForm()
    object = None
    color_choices = ('Red', 'Blue', 'Black', 'Orange')
    message = ''
    if request.method == 'POST':
        picked = request.POST.get('color')
        if picked == 'Red':
            message = '<<< You chose red'
            print(message)
        elif picked == 'Blue':
            message = '<<< You chose blue'
        elif picked == 'Black':
            message = '<<< You chose black'
        else:
            message = '<<< Oh no, you chose orange'
            form = SelectCityForm(request.POST)
            if form.is_valid():
                city_id = form.cleaned_data['city']
                object = City.objects.get(id=city_id)

        context = {
            'form': form,
            'object': object,
            'message': message,
            'color_choices': color_choices,
        }
        return render(request, 'app1/home.html', context)

It is however quite inconvenient to render form items in the template manually, that would introduce a lot of code repitition, and while HTML form elements look easy to understand, in fact these also have some special cases (for example a checkbox sends 'on' if the checkbox is checked, and nothing if not).

Therefore one typically combines fields in a form, or you use different forms:

from .models import City


class HomeForm(forms.Form):
    Col1 = 'Red'
    Col2 = 'Blue'
    Col3 = 'Black'
    Col4 = 'Orange'

    COLOR_CHOICES = (
        (Col1, u"Red"),
        (Col2, u"Blue"),
        (Col3, u"Black"),
        (Col4, u"Orange"),
    )
    color = forms.ChoiceField(choices=COLOR_CHOICES)
    city = forms.ModelChoiceField(queryset=City.objects.all())

In the view, we can then render the form and process the results:

from django.shortcuts import redirect, render

MESSAGES = {
    'Red': '<<< You chose red',
    'Blue': '<<< You chose blue',
    'Black': '<<< You chose black',
    'Orange': '<<< Oh no, you chose orange',
}


def city_detail(request):
    object = None
    message = ''
    if request.method == 'POST':
        form = HomeForm(request.POST, request.POST)
        if form.is_valid():
            object = form.cleaned_data['city']
            message = MESSAGES.get(form.cleaned_data['color'], '')
    else:
        form = HomeForm()
    context = {
        'form': form,
        'object': object,
        'message': message,
    }
    return render(request, 'app1/home.html', context)

and in the template:

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

you can, if the two forms are independent, work with two forms you create and handle. But usually it makes more sense to then have separate views that handle the forms, since it is possible that then both forms are valid, when only one was actually filled with data.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • VO supporta mod strike I've updated the question. I hope you can help me. Thank you – Horiatiki Jul 13 '23 at 09:46
  • The code is a bit confusing. I see you have wrapped both functions into one. In this case it's 2 comboboxes in the same function, ok, but the code is a bit confusing. Is this the only solution? I say this because soon I will have to insert about 10-12 comboboxes: it will be a mess if I have to enclose them all in the same function, because there will be so much confusion in the code. Can you make your code a little tidier please (perhaps dividing the two comboboxes with comments and spaces), so at least it's a little clearer for me. Thank you – Horiatiki Jul 13 '23 at 10:01
  • @Folidexman: you normally don't render comboboxes manually, you simply add these to the form as extra form field. – Willem Van Onsem Jul 13 '23 at 10:02
  • So my approach (which was suggested to me in an answer here on stackoverlow) is completely wrong? In my case do I manually render the comboboxes? The neatest solution then would be to add these to the form as extra form field? – Horiatiki Jul 13 '23 at 10:05
  • @Folidexman: well I'm not entirely following, your form already has a field with `Red`, `Blue`, `Black` and `Orange`. So then there are two color fields? – Willem Van Onsem Jul 13 '23 at 10:36
  • One combobox consists of the colors Blue, Black, Orange and Red, while the other combobox pulls city names from a database. It's a project where I'm trying to learn Django – Horiatiki Jul 13 '23 at 10:48
  • I think the indentation is wrong in `city_detail` in first code block (at `form.is_valid()`). The revised version in later code block looks good – Anentropic Jul 13 '23 at 11:07
  • I'd suggest renaming `object` -> `city` as it's clearer, also avoids shadowing the `object` builtin – Anentropic Jul 13 '23 at 11:10
  • it might make sense to name the view function as `home` instead of `city_detail` since the url is `home/` and it renders the `home.html` template – Anentropic Jul 13 '23 at 11:11
  • 1
    @Folidexman this answer is good. If you absolutely need the fields split into two forms that are submitted separately then that introduces some complication, see options here https://stackoverflow.com/a/1395866/202168 – Anentropic Jul 13 '23 at 11:20
  • @Anentropic I'm a bit confused as to which solution is more correct. Are you talking about a solution like the one provided by the other user, or are you talking about add these to the form as extra form field? Could you write an answer with the solution of the code I use in the question please? – Horiatiki Jul 13 '23 at 11:30
  • @Willem VO supports mod strike Why is HomeForm built in a class, and city_detail is normally in a function? Why a class for HomeForm? For a third and fourth combobox, should I also use a class? Thank you – Horiatiki Jul 13 '23 at 11:59
  • @Folidexman: because we subclass from `Form` this implements the handling of the form. You can also make class-based views, and use mixins to even easily mix in logic from other classes. The view is just a function mapping requests to responses. We here construct a form so it will run `__init__` which will then perform logic w.r.t. the fields in the form, etc. – Willem Van Onsem Jul 13 '23 at 12:02
  • @Willem VO supports mod strike I'm not convinced "else". My goal was to print depending on whether I selected just one combobox or both together. So with the possibility of combinations of comboboxes. I would like to be able to select the two comboboxes at the same time or not, because I will insert other comboboxes and therefore I should select 2 or more of them together (as combinations between comboboxes). To give a trivial example, I'll have to do this if I select Red and London (city_detail) then print "Ok". With your "else" can I only select one of the two comboboxes? – Horiatiki Jul 13 '23 at 12:38
  • @Folidexman: if you combine the two fields in one form, you can select the two fields, if you work with two separate forms, you can independently validate, and thus show data for each form that is valid. Typically however two forms are send to two views, especially since forms usually have side effects (make modifications to the database), so if a form would be valid "by accident", it could mean you create/edit/remove a record. – Willem Van Onsem Jul 13 '23 at 12:47
  • @Willem VO supports mod strike I'm confused for a moment. In your answer you combined two fields in one form, right? In my case (combinations of two or more comboboxes) which approach should I use? (combine fields in one form or use different forms?). I specify that my forms must not make changes to the database, but only read from the database – Horiatiki Jul 13 '23 at 12:55
  • @Folidexman: well that depends on *what* you want to do. But since you only seem to want to read what the user fills in, a single form likely is sufficient here. – Willem Van Onsem Jul 13 '23 at 12:57
  • @Willem VO supports mod strike I understand. I needed to have two separate forms (in relation to how the project should be implemented in the future). For a completeness of the your answer (also for future readers because the discussion is becoming interesting), could you add a short alternative answer on how to use two separate forms as well please? I will be super happy to accept your answer (already upvoted you). Sorry for the long comments, but I'm new to Django and trying to figure this out. Thank you – Horiatiki Jul 13 '23 at 13:10
  • @Folidexman I didn't write my own answer because I think this one already answers it best. It might be better to address the multiple forms thing in a separate question which details the underlying motivations. Multi forms to me suggests a UI using "Ajax" i.e. JS to submit forms and handle responses without page refresh. In that scenario it makes a lot of sense having them POST to separate views. Or maybe multi forms is unnecessary. It all needs context to give good answers. – Anentropic Jul 13 '23 at 13:21
  • @Anentropic Of course yes, I understand – Horiatiki Jul 14 '23 at 14:06
  • @Willem VO supports mod strike Voted (yesterday) and today accepted your answer. Sorry for too many comments yesterday. I was trying to understand. Thanks for the answer :) – Horiatiki Jul 14 '23 at 14:07