Hi,
I have two models Source and Input, where Input has an FK to Source like this:
class Source(db_models.Model):
scheme = db_models.CharField(...)
path = db_models.CharField(...)
display_as = db_models.CharField(...)
class Input(db_models.Model):
name = db_models.CharField(...)
description = db_models.CharField(...)
type = db_models.CharField(...)
source = db_models.ForeignKey(Source, on_delete=db_models.CASCADE)
reuse = db_models.CharField(...)
value = db_models.CharField(...)
The instances of Source are statically created, and number 20 or so in total. The instances of Input number in the millions, and may be loaded from external files/network endpoints. When Input instances are created, we perform normal Django validation processing, involving form.is_valid()/form.errors. As we know, Django 5.x does something like:
- Form field validation using Form.clean_xxx().
- Form cross-field validation using Form.clean().
- Model validation, including validating the FK.
like this:
...
File "/.../django/forms/forms.py", line 197, in is_valid
return self.is_bound and not self.errors
File "/.../django/forms/forms.py", line 192, in errors
self.full_clean()
File "/.../django/forms/forms.py", line 329, in full_clean
self._post_clean()
File "/.../django/forms/models.py", line 496, in _post_clean
self.instance.full_clean(exclude=exclude, validate_unique=False)
File "/.../django/db/models/base.py", line 1520, in full_clean
self.clean_fields(exclude=exclude)
File "/.../django/db/models/base.py", line 1572, in clean_fields
setattr(self, f.attname, f.clean(raw_value, self))
File "/.../django/db/models/fields/__init__.py", line 830, in clean
self.validate(value, model_instance)
File "/.../django/db/models/fields/related.py", line 1093, in validate
if not qs.exists():
...
In one experiment, I observe that this results in 143k queries, taking a total of 43s. Is there a way to short circuit this Model-level validation? Since I know the ~20 instances of Source, even a cache of Source.objects.all() would be a very cheap, but I cannot see any way to inject that into the code for line 1093:
def validate(self, value, model_instance):
if self.remote_field.parent_link:
return
super().validate(value, model_instance)
if value is None:
return
using = router.db_for_read(self.remote_field.model, instance=model_instance)
qs = self.remote_field.model._base_manager.using(using).filter( <<<< queryset created here
**{self.remote_field.field_name: value}
)
qs = qs.complex_filter(self.get_limit_choices_to())
if not qs.exists(): <<<< queryset evaluated here, line 1093
raise exceptions.ValidationError(...)
We actually have several versions of this problem so any ideas are welcome.
Thanks, Shaheed