Creating a model with "GeneratedField" results in a DoesNotExist error.

class BaseModel(models.Model):

created_at = models.DateTimeField(_("created at"), auto_now_add=True, editable=False)
updated_at = models.DateTimeField(_("updated at"), auto_now=True, editable=False)
created_by = models.ForeignKey(
    settings.AUTH_USER_MODEL,
    verbose_name=_("created by"),
    on_delete=models.PROTECT,
    blank=True,
    null=True,
    editable=False,
    related_name="created_%(class)s",
    db_column="created_by",
)
updated_by = models.ForeignKey(
    settings.AUTH_USER_MODEL,
    verbose_name=_("updated by"),
    on_delete=models.PROTECT,
    blank=True,
    null=True,
    editable=False,
    related_name="updated_%(class)s",
    db_column="updated_by",
)

class Meta:
    abstract = True

class LeadingZeroIDModel(models.Model):

class Meta:
    abstract = True

_id_length = 5 

def save(self, *args, **kwargs):
    with transaction.atomic():
        if not self.id and self._state.adding:
            id_length = kwargs.pop("id_length", self._id_length)
            self.id = self.generate_new_id(id_length)
        super().save(*args, **kwargs)

def generate_new_id(self, id_length):
    last_id = self.__class__.objects.all().values_list("id", flat=True).order_by("-id").first() or "0"
    new_id = str(int(last_id) + 1).zfill(id_length)
    return new_id

class Company(BaseModel, LeadingZeroIDModel):

id = models.CharField(_("id"), primary_key=True, max_length=5, unique=True, editable=False)
name = models.CharField(_("name"), max_length=20, unique=True)
COUNTRY_CHOICES = {
    "JP": _("Japan"),
    "US": _("USA"),
}
country = models.CharField(_("country"), max_length=2, choices=COUNTRY_CHOICES, default="JP")
staff_first_name = models.CharField(_("staff first name"), max_length=35)
staff_last_name = models.CharField(_("staff last name"), max_length=35)

staff_full_name = models.GeneratedField(
    expression=Case(
        When(country="JP", then=Concat(F("staff_last_name"), F("staff_first_name"))),
        When(country="US", then=Concat(F("staff_first_name"), Value(" "), F("staff_last_name"))),
    ),
    output_field=CharField(max_length=70),
    db_persist=True,
)

_id_length = 5

def __str__(self):
    return self.name

class Meta:
    db_table = "company"
    verbose_name = _("company")

When I try to create an object of this model, I get an error
DoesNotExist at company/new/
Company matching query does not exist.

CompanyCreateView is

class CompanyCreateView(CreateView):
model = Company
template_name = “company/company_create.html”
form_class = CompanyCreateForm

def get_success_url(self):
    return reverse_lazy("company-detail", kwargs={"pk": self.object.pk})

def form_valid(self, form):
    form.instance.created_by = form.instance.updated_by = self.request.user
    return super().form_valid(form)

and CompanyCreateForm is

class CompanyCreateForm(forms.ModelForm):

class Meta:
    model = Company
    fields = ["name", "staff_first_name", "staff_last_name"]

If I remove “staff_full_name”, which is a “GeneratedField” from the model, and then migrate it, everything works fine. Why am I getting an error? I am using SQLite in the development phase.

You may be hitting one of the SQLite restrictions on generated columns.

I’m thinking #7 is a possibility, but you’d have to check your migrations to see if that’s the issue.

Side note: As a general rule, you typically want to specify mixins before the base classes. In most cases, the intent of a mixin is to supersede the base class. (I’m not certain this is an issue here, but given that you’re overriding the model’s save method in LeadingZeroDModel, I would think that you do want it first in the Company class definition.)

Thanks for the comment. KenWhitesell.
Looking at the part

It is not possible to ALTER TABLE ADD COLUMN a STORED column. One can add a VIRTUAL column, however.

I tried changing “db_persist” in GeneratedField to False, but I still get the same error. OK. I guess this doesn’t work with SQLite.

But for something like filtering objects by “staff_full_name”, that field is absolutely necessary (There may be other ways, but none come to mind, at least not for me.).

If I change the database to PostgreSQL or MySQL, will what I want work? Or will I have to try it out to find out?

Addendum: I changed the Company class to class Company(LeadingZeroIDModel, BaseModel) as per your advice. Thanks for your kind reply!

What version of Django do you have?
There have been some patch releases for 5.0 around GeneratedFields, just want to check you have the latest patch.

Also minor thing, max_length should be 71 if you do first and last name with a space :+1:

Actually, I made a mistake. I missed seeing that both parent classes are abstract. It doesn’t really matter in this case.

I suspect it will. I don’t see anything wrong with the expression that would cause it to fail, provided you’re using the most recent version of Django 5 as suggested above.
(I’d have to try it myself to see.)

I don’t know about MySQL. It won’t in Django unless you’re running 5.1 or apply the patch as described in this thread: Using GeneratedField with Postgres - #2 by felixxm

I’m using django version 5.0.2.

That’s right, I was missing this. Thank you :blush:

I’ll try it with the most recent version of Django 5 and PostgreSQL, thanks for the advice!