Home > Enterprise >  Django ModelChoiceField in ModelForm passes None
Django ModelChoiceField in ModelForm passes None

Time:08-10

I'm very new to Django.
I'm making form to add timetable to specific branch.
So I have 'Branch' model and 'Timetable' model.
'Branch' model is in 'branches' app and 'Timetable' model is in 'schedules' app.
What I'm expecting is when users fill in the form to add timetable, they can choose which branch to add it from select box.

Here is my codes
branches/models.py

from django.core.validators import RegexValidator
from django.db import models
from django.urls import reverse

# Create your models here.
phone_validator = RegexValidator(
    regex="\d{2,4}-?\d{3,4}(-?\d{4})?",
    message="It's not correct phone number format.",
)


class Branch(models.Model):
    srl = models.BigAutoField(primary_key=True, verbose_name="Serial")
    name = models.TextField(verbose_name="Branch name")
    postcode = models.CharField(max_length=5, verbose_name="Postcode")
    address1 = models.TextField(verbose_name="Road address")
    address2 = models.TextField(verbose_name="Detail address", blank=True)
    phone1 = models.CharField(max_length=13, validators=[phone_validator], verbose_name="Phone 1")
    phone2 = models.CharField(max_length=13, validators=[phone_validator], verbose_name="Phone 2", blank=True)
    is_opened = models.BooleanField(default=True, verbose_name="Is opened?")

    class Meta:
        verbose_name = "Branch"
        verbose_name_plural = "Branches"
        ordering = ["srl"]

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("branches:detail", kwargs={"srl": self.srl})

schedules/models.py

import datetime

from django.conf import settings
from django.db import models


class Timetable(models.Model):
    srl = models.BigAutoField(primary_key=True, verbose_name="Serial")
    branch_srl = models.ForeignKey(
        "branches.Branch",
        on_delete=models.CASCADE,
        verbose_name="Branch Serial",
    )
    period = models.DecimalField(
        max_digits=2,
        decimal_places=0,
        verbose_name="Period",
    )
    period_length = models.DurationField(
        default=datetime.timedelta(minutes=50),
        verbose_name="Period Length",
    )
    is_holiday = models.BooleanField(default=False, verbose_name="Is holiday?")
    start_time = models.TimeField(default=datetime.time(10, 00), verbose_name="Start time")
    end_time = models.TimeField(default=datetime.time(23, 00), verbose_name="End time")

schedules/forms.py

from django import forms
from django.forms import ModelChoiceField, ModelForm

from branches.models import Branch
from schedules.models import Schedule, Timetable


class TimetableForm(ModelForm):
    branch_choices = ModelChoiceField(
        queryset=Branch.objects.all(),
        required=True,
        label="Branch",
        widget=forms.Select(attrs={"class": "form-select"}),
    )

    class Meta:
        model = Timetable
        fields = (
            "branch_choices",
            "period",
            "period_length",
            "is_holiday",
            "start_time",
            "end_time",
        )
        labels = {
            "period": "Period",
            "period_length": "Period Length",
            "is_holiday": "Is Holiday?",
            "start_time": "Start Time",
            "end_time": "End Time",
        }
        widgets = {
            "period": forms.NumberInput(attrs={"class": "form-control"}),
            "period_length": forms.TimeInput(
                format="%H:%M:%S",
                attrs={
                    "class": "form-control",
                },
            ),
            "is_holiday": forms.RadioSelect(
                choices=[(True, "Yes"), (False, "No")],
                attrs={
                    "class": "form-check-input",
                },
            ),
            "start_time": forms.TimeInput(
                attrs={
                    "type": "time",
                    "class": "form-control",
                },
            ),
            "end_time": forms.TimeInput(
                attrs={
                    "type": "time",
                    "class": "form-control",
                },
            ),
        }

Error message is this.
Error message from Django debug mode

--- Edit 0 ---
I forgot to attach schedules/views.py
schedules/views.py

from django.apps import apps
from django.urls import reverse_lazy
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView
from django.views.generic.list import ListView

from schedules.forms import TimetableForm
from schedules.models import Schedule, Timetable


# Create your views here.
class AddTimetableView(CreateView):
    model = Timetable
    form_class = TimetableForm
    # This should be changed. This is temporary
    success_url = reverse_lazy("schedules:index")

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = "Add timetable"

        return context

--- Edit 1 ---
Here is my template
schedules/templates/scheduels/timetable_form.html
For your information, I'm using Bootstrap 5 and related CDN link is in main/base.html

{% extends "main/base.html" %}
{% load static %}
{% block content %}
    <form action="" method="POST" id="timetable-form" >
        {% csrf_token %}
        {{ form.non_field_errors }}
        <div >
            <label  for="{{ form.branch_choices.id_for_label }}">{{ form.branch_choices.label }}</label>
            <div >{{ form.branch_choices }}</div>
            {{ form.branch_choices.errors }}
        </div>

        <div >
            <label  for="{{ form.period.id_for_label }}">{{ form.period.label }}</label>
            <div >{{ form.period }}</div>
            {{ form.period.errors }}
        </div>
        
        <div >
            <label  for="{{ form.period_length.id_for_label }}">{{ form.period_length.label }}</label>
            <div >{{ form.period_length }}</div>
            {{ form.period_length.errors }}
        </div>
        
        <div >
            <label  for="{{ form.is_holiday.id_for_label }}">{{ form.is_holiday.label }}</label>
            <div >
                {% for choice in form.is_holiday %}
                    <div >
                        {{ choice.tag }}
                        <label  for="{{ choice.id_for_label }}">{{ choice.choice_label }}</label>
                    </div>
                {% endfor %}
            </div>
            {{ form.is_holiday.errors }}
        </div>
        
        <div >
            <label  for="{{ form.start_time.id_for_label }}">{{ form.start_time.label }}</label>
            <div >{{ form.start_time }}</div>
            {{ form.start_time.errors }}
        </div>
        
        <div >
            <label  for="{{ form.end_time.id_for_label }}">{{ form.end_time.label }}</label>
            <div >{{ form.end_time }}</div>
            {{ form.end_time.errors }}
        </div>
        
        <div >
            <div >
                <button  type="submit">저장</button>
            </div>
        </div>
    </form>
{% endblock %}

CodePudding user response:

An easier way of setting queryset-related choices on a model form is like by overriding the __init__() method of the form, rather than manually creating a form field.

class TimetableForm(ModelForm):
    ...

    def __init__(self, *args, **kwargs):
        self.branches = Branch.objects.all()
        super(TimetableForm, self).__init__(*args, **kwargs)
        self.fields['branch_srl'].queryset = self.branches

Furthermore, because you are using a ModelForm a lot of the labels and field definition should be automatic based on what you have defined on the model. Try to avoid repeating those declarations if you can avoid it.

CodePudding user response:

Thanks to CodenameTim from Django Forum, I found the solution
So I copied the solution below for who experiencing the same issue.

The error is saying that the field branch_srl is null when it’s being inserted so it violates the NOT NULL CONSTRAINT. If you check your form, the form doesn’t use that field, but instead it uses branch_choices. You need to map the selected value for that field to branch_srl somewhere. It can be in TimetableForm.save(), AddTimetableView.form_valid() or you could rename the field branch_choices to branch_srl so it saves through to the database.

  • Related