Dynamically populate constraints in Field.contribute_to_class

Ah, the fact that it didn’t throw an exception on the non-inherited model was due I think to the bug described above - adding another constraint into that model triggered the exception on that one.

So the error I’m dealing with is indeed that the CreateModel operation is incorrectly “seeing” a constraint (that will be handled elsewhere).

So, interestingly, explicitly added constraints are not present in the “fake” model that migrations use, but this class is passed to the contribute_to_class function, so “automatic” constraints added here still get added.

In my case, I was able to avoid adding the constraint if cls.__module__ == '__fake__', but this feels like a dirty solution - I’d be keen to find why cls._meta.constraints is empty when building migrations…

I would also like if that worked, my use case is

class PageTypeForeignKey(models.ForeignKey):
    def contribute_to_class(self, cls, name, *args, **kwargs):
        super().contribute_to_class(cls, name, *args, **kwargs)
        cls._meta.constraints.append(
            models.UniqueConstraint(
                fields=[name, "lang"],
                name=f"{cls._meta.app_label}_{cls.__name__.lower()}_{name}_and_lang",
            )
        )

and makemigrations doesn’t create the constraints

I tried the metaclass solution and it still didn’t work without defining constraints = [] in Meta

but apparently pg now works when doing it in contribute_to_class and having constraints defined in Meta (tested by a friend in pg)

I tested both in sqlite (with constraints = [] in Meta) and both work

I also find that this flexibility would be valuable to have, and taking @nessita’s above summary as a starting point, I have a proposal for a new API.
I’d set aside potential bugs around contribute_to_class() and db_check() for now. Given that those are undocumented, it feels better not to lean on them for this use case regardless.

There’s already an accepted ticket, but rather than jumping straight to a PR there, I’ve opened a new-features issue, since the proposal diverges a bit from the discussion so far.
Would love to get the community’s feedback, and continue the discussion there if there’s interest.

Here’s what I found when digging in a while back:

I think a neat example is bounds checking for the battleship board game where each new game the players choose from predefined geometries like 10x5, 16x8, or 20x10. (Or maybe or tic tac toe, connect four, pen the pig, chess puzzles, etc. Square len(rows) == len(columns) will be simpler to model and check than non-square len(rows) != len(columns). A fuller example below:

wellplated/wellplated/models.py at main · biobuddies/wellplated · GitHub

You may find people advocating that new and improved Fields begin and grow as third party installables, without burdening the Django Foundation with maintenance, and with the option to someday/maybe merge upstream.

I think many Django projects could benefit from more comprehensive SQL constraints. To scope creep a little: what about harmonized checks at every level? HTML attributes/JavaScript for the frontends like the admin interface, Python code like full clean on save, and SQL constraints as the backstop?