How to handle BigAutoField when override form_valid

I’m trying to make some form using ModelForm and CreateView.
So, I tried this.
model.py

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


# Create your models here.
class Schedule(models.Model):
    srl = models.BigAutoField(
        primary_key=True,
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.DO_NOTHING,
    )
    branch = models.ForeignKey(
        "branches.Branch",
        on_delete=models.DO_NOTHING,
    )
    start_datetime = models.DateTimeField()
    end_datetime = models.DateTimeField()

    class Meta:
        ordering = [
            "branch",
            "start_datetime",
        ]

forms.py

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

from branches.models import Branch
from schedules.models import Schedule
from users.models import User


class ScheduleForm(ModelForm):
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user")
        super(ScheduleForm, self).__init__(*args, **kwargs)
        if self.user.superuser:
            self.fields["branch"] = ModelChoiceField(
                queryset=Branch.objects.all(),
                required=True,
                label="Branch",
                widget=forms.Select(
                    attrs={
                        "class": "form-select",
                    }
                ),
            )
            self.fields["user"] = ModelChoiceField(
                queryset=User.objects.all(),
                required=True,
                label="User",
                widget=forms.TextInput(
                    attrs={
                        "class": "form-control",
                        "list": "user-list",
                    }
                ),
            )
        else:
            self.fields["branch"] = ModelChoiceField(
                queryset=Branch.objects.filter(branch=self.user.branch),
                required=True,
                label="Branch",
                widget=forms.Select(
                    attrs={
                        "class": "form-select",
                    }
                ),
            )
            self.fields["user"] = ModelChoiceField(
                queryset=User.objects.filter(branch=self.user.branch),
                required=True,
                label="User",
                widget=forms.TextInput(
                    attrs={
                        "class": "form-control",
                        "list": "user-list",
                    }
                ),
            )

    class Meta:
        model = Schedule
        fields = (
            "start_datetime",
            "end_datetime",
        )
        widgets = {
            "start_datetime": forms.DateTimeInput(
                attrs={
                    "type": "datetime-local",
                    "class": "form-control",
                },
            ),
            "end_datetime": forms.DateTimeInput(
                attrs={
                    "type": "datetime-local",
                    "class": "form-control",
                },
            ),
        }

views.py

class ScheduleCreateView(CreateView):
    model = Schedule
    form_class = ScheduleForm
    success_url = reverse_lazy("schedules:list")

    def get_form_kwargs(self):
        kwargs = super(ScheduleCreateView, self).get_form_kwargs()
        kwargs.update({"user": self.request.user})
        return kwargs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = "Create Schedule"
        if self.request.user.superuser:
            context["user_list"] = User.objects.all()
        else:
            context["user_list"] = User.objects.filter(branch=self.request.user.branch)

        return context

But I got this.

So refer to the answer of my previous question about NOT NULL Constraint, I changed views.py into this

class ScheduleCreateView(CreateView):
    model = Schedule
    form_class = ScheduleForm
    success_url = reverse_lazy("schedules:list")

    def form_valid(self, form):
        if form.is_valid():
            form.instance.branch_id = form.cleaned_data["branch"]
            form.instance.user_id = form.cleaned_data["user"]
            form.instance.start_datetime = form.cleaned_data["start_datetime"]
            form.instance.end_datetime = form.cleaned_data["end_datetime"]

            return super(ScheduleCreateView, self).form_valid(form)

    def get_form_kwargs(self):
        kwargs = super(ScheduleCreateView, self).get_form_kwargs()
        kwargs.update({"user": self.request.user})
        return kwargs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = "Create Schedule"
        if self.request.user.superuser:
            context["user_list"] = User.objects.all()
        else:
            context["user_list"] = User.objects.filter(branch=self.request.user.branch)

        return context

Now, I got this.

So my question is these.

  1. How can I solve this?
  2. Why NOT NULL Constraint error occured even I used same field name in the model and form?
  3. Why ForeignKey column’s name always have suffix ‘_id’ and how to avoid this?

This needs null = True I think:

add it:

    srl = models.BigAutoField(
        primary_key=True,
        null = True
    )

I also add “null = True” to all my foreign key models, you have not done this here. I’m not sure if you need null on BigAutoField, but add “null = True” to your foreign key models.

save script, then remake migrations:

PS            python manage.py makemigrations
PS            python manage.py migrate

How primary key can be null?
Is it possible in Django?

I’m not familiar with BigAutoField or any AutoField model, because I make a primary key with a UUIDField:

    id = models.UUIDField( 
        primary_key=True,
        default=uuid4,
        editable=False,
        max_length=36,)

Maybe leave this BigAutoField as it is, try add “null = True” to here:

branch = models.ForeignKey(
        "branches.Branch",
         null = True,
        on_delete=models.DO_NOTHING,
    )

I don’t know well about Django ORM, but what I know is Primary Key can’t be null.
I can’t make branch to NULL because it’s required to be associated with branch model that I made.
It’ required when the schedule is which branch’s.

Some general notes:

This means that all the model fields you want to set in this form should be defined in the fields attribute of the Meta class.

This is all unnecessary - you’re replicating what the form already does. (You definitely don’t need to check is_valid in the form_valid method. The CBV has already checked it to call form_valid.)

You don’t avoid this. The database column has the _id suffix because what the column contains is the id of the related object, not the object itself. In your code, this means you can refer to either other_model as a reference to the related object or other_model_id to access the foreign key itself.

I’d also refactor that form to move the common definitions for the fields you’re defining in __init__ to the appropriate settings in Meta, and reduce the alterations being performed in __init__ to only those elements needing to be changed there.

I’m confused by “branches.Branch” - do you have a Branch model defined somewhere else? can you post this model? Perhaps the issue could lie in the Branch model?

This is the code contains Branch model
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="Not valid phone number format",
)


class Branch(models.Model):
    srl = models.BigAutoField(
        primary_key=True,
        verbose_name="Serial",
    )
    name = models.TextField(
        verbose_name="Branch name",
        unique=True,
    )
    equipment_count = models.DecimalField(
        max_digits=2,
        decimal_places=0,
        verbose_name="Number of equipment",
        default=5,
    )
    postcode = models.CharField(
        max_length=5,
        verbose_name="Postcode",
    )
    address1 = models.TextField(
        verbose_name="Address 1",
    )
    address2 = models.TextField(
        verbose_name="Address 2",
        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,
    )
    closure = models.BooleanField(
        default=False,
        verbose_name="Closure T/F",
    )

    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})

Oh, thank you every time.
I though the code self.fields["branch"] under def __init__(self, *args, **kwargs) in forms.py will act same like class Meta: fields = ("branch")

A ModelForm is a Form - with the additional functionality that elements defined in the Meta class define an association between that form and its associated model.

When you define fields outside of Meta, they act just like any other fields in a non-Model form - there’s no implicit association between those fields and a model. Instead, they just act like additional fields on the form.

So no, there’s a very real and substantial difference between what you define in Meta for a ModelForm and what you define at the class layer like a regular form.

1 Like

Thanks.
I will keep in mind.

I don’t know if I have the experience to really help with the issue, but what I do know is that django kept giving me problems when I didn’t include null = True on TextField models (while I could leave CharField models without the null). I also left all other field types without the null. It even says in the documentation to avoid using null = True on string-based fields, but the database wouldn’t migrate.
Is the integrity error gone?

My problem is solved with Ken Whitesell’s solution.
I should include the field in the Meta class’ field argument, not just declare field in def __init__
I think the problem you mentioned can be solved with blank=True on the TextField.
The link below might be help you.
https://books.agiliq.com/projects/django-orm-cookbook/en/latest/null_vs_blank.html

1 Like

It’s just that I wanted to keep some text fields required, blank would mean the text field would not require validation. When django documentation tells me I shouldn’t use null = True on string objects, and then I never get the database to migrate, this makes me take an extreme dislike to the official documentation.

A migration involves existing data. If you can’t do a migration, it’s because your existing data has a problem, not the migration - or the documentation.

If you’re adding a field to an existing table, you must either allow for null=True or provide a default value. (The makemigration step will even point that out for you when you make the migration.)

I was unable to make a default value (although it was some time ago, so I don’t remember the case too clearly). I have mainly taken an aversion to the documentation due to the fact that many things are overly abstracted. I would describe it as an API written for professionals - not so much a guide for beginners. But I respect that it may simply not have been written for beginners, and I know as well there are a lot of good courses on django.

And there are different types of “beginners” as well. People come to Django from all sorts of backgrounds.

The Django docs do assume a good bit of familiarity with Python and the HTTP protocol. If you’re not solid with Python, there are going to be chunks of the docs that aren’t going to make a lot of sense. (Modules, Classes and the MRO are the areas I see most new-to-Python people tend to struggle with.)

1 Like

I myself am new to python (I came from Java), at first I was cocky thinking I can handle this documentation - but I’ll admit I was struggling, which led me to seek courses. My feeling towards it is just subjective by the way (from what people have said, the official documentation is written very well).

1 Like