Skip to content

Discussion: Addressing reasonable model.full_clean() behavior. #35

@brendan-morin

Description

@brendan-morin

Original ninja issue: vitalik/django-ninja#443

Overview

Basically there are two parts to this issue:

  1. model.full_clean() is not called by default (known Django decision for backwards compatibility)
  2. model_full_clean() is not async compatible, so it would be possible to call this in an unsafe manner when left to users to decide how or when to call.

Background

In a nutshell, model.full_clean() performs model-level validation checks. Django has historically disabled this by default, but recommends manually calling it on save(), which has been a fairly contentious decision and infamous footgun, as not doing so can lead to data corruption.

Discussion

First off, I think that the Django decision to not call model.full_clean() by default is somewhat defensible for Django core, where legacy apps may have existed for many years. However, ninja and shinobi are new frameworks (and generally new use cases) on top of Django and it would be perfectly reasonable to call model.full_clean() by default and instead allow people to opt out.

The reason I recommend enabling by default is Django and ninja's "just works no surprises" approach, which is widely loved. However, this is at odds with being able to specify validations at the model level that you reasonably could assume would be enforced but default to not enforcing them. IMO, it is more surprising that you would define validation at the model level and then choose to ignore it, especially given we already have the ability to use Schema-level validation for more bespoke validation use cases that would not be applied to all interactions with a particular model.

Possible Solutions

While enabling by default addresses reasonable expectations, given the async focus on Django Ninja, it is possible this could lead to issues with the model.full_clean() behavior not being async compatible. One idea, from the original ninja issue:

One option Is there possibly some way forward using a flag as described above + one the async adapter functions in conjunction with an asyncio.get_running_loop() test to see if the code is running async? E.g. do nothing in sync context, and in async call the adapter to ensure it's a safe/sync operation?

Would love to hear more thoughts on this - thank you!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions