Problem with migrating a fresh db - jango.db.utils.ProgrammingError: column "structure_depth" specified more than once

41 views
Skip to first unread message

Marius Räsener

unread,
Jul 30, 2019, 5:53:00 PM7/30/19
to Django users
Hey everybody,

I hope I can somehow give enough informations but also keep it as little as possible.

situation is:
- existing migrations deleted to reset migrations (existing migrations seem are broken aswell, for a fresh db)
- new migrations with makemigrations
- migrate fails with:

 File "/Users/user/.pyenv/versions/systori-python/lib/python3.6/site-packages/django/db/backends/utils.py", line 83, in _execute
    return self.cursor.execute(sql)
django.db.utils.ProgrammingError: column "structure_depth" specified more than once

Maybe related, this doesn't happen when I run the tests.

And indeed, if I print the sql statement in above mentioned line, I see:

CREATE TABLE "project_project" 

  ("id" serial NOT NULL PRIMARY KEY,

   "name" varchar(512) NOT NULL,

   "description" text NULL,

   "is_template" boolean NOT NULL,

   "structure" varchar(64) NOT NULL,

   "phase" varchar(50) NOT NULL,

   "state" varchar(50) NOT NULL,

   "structure_depth" integer NOT NULL CHECK ("structure_depth" >= 0),

   "structure_depth" integer NOT NULL CHECK ("structure_depth" >= 0)

)


if I print the same when running tests the "structure_depth" isn't present twice.


while surfing around with PyCharms nice Debugger I figured that at some point there is an object model, which represents the right model, but model._meta.fields already has the duplicate of "structure_depth". other parts I can find, f.e. something like a ProjectState doesn't have the duplicate "structure_depth". This is all way beyond my current understanding of Django.


the generated migrations.CreateModel looks correct, "structure_depth" appears only once.


the model is, shortened:


class Project(models.Model):

    structure = GAEBStructureField(

        _("Numbering Structure"), default="01.01.001"

    )  # GAEBStructureField adds structure_depth


the custom Field is defined as follows:


class GAEBStructureField(models.Field):

    description = _("Pattern for the GAEB hierarchy code structure.")


    def __init__(self, *args, **kwargs):

        kwargs["max_length"] = 64

        super().__init__(*args, **kwargs)


    def deconstruct(self):

        name, path, args, kwargs = super().deconstruct()

        del kwargs["max_length"]

        return name, path, args, kwargs


    def contribute_to_class(self, cls, name, **kwargs):

        super().contribute_to_class(cls, name)


        setattr(cls, name, GAEBStructureProperty(name))


        depth_name = name + "_depth"

        depth_field = models.PositiveIntegerField(editable=False, db_index=True)

        cls.add_to_class(depth_name, depth_field)

        setattr(

            cls,

            depth_name,

            property(

                fget=lambda obj: obj.__dict__[depth_name], fset=lambda obj, val: None

            ),

        )


so, I'm kind of proud what I've figured but I'm lost in non-linear land - at least it feels like it. I can't find the point where model._meta.fields actually gets created.


so, hopefully maybe someone can point me in the right direction, give some hints or just comment it - all appreciated.


this project is open source, so I could aswell just make a branch and upload current state. FYI github.com/systori/systori


thx in advance,

Marius

Marius Räsener

unread,
Aug 1, 2019, 3:43:03 PM8/1/19
to Django users
Hey again,

so to solve this case, a guy from our local user group had the solution, which involved of course our custom Field.

changing contribute_to_class to the following changes it. If you have an understanding if the two setattr() are needed, please reply :)

    def contribute_to_class(self, cls, name, **kwargs):
        super().contribute_to_class(cls, name)
        depth_name = name + "_depth"

        setattr(cls, name, GAEBStructureProperty(name))  # maybe not needed?

        if self.dept_field and not cls._meta.abstract:
            depth_field = models.PositiveIntegerField(editable=False, db_index=True)
            cls.add_to_class(depth_name, depth_field)

        setattr(
            cls,
            depth_name,
            property(
                fget=lambda obj: obj.__dict__[depth_name], fset=lambda obj, val: None
            ),
        )  # maybe not needed?

Cheers,
Marius
Reply all
Reply to author
Forward
0 new messages