|
@@ -58,6 +58,7 @@ class BaseModel(models.Model):
|
|
|
This class provides some important overrides to Django's default functionality, such as
|
|
This class provides some important overrides to Django's default functionality, such as
|
|
|
- Overriding the default manager to use RestrictedQuerySet
|
|
- Overriding the default manager to use RestrictedQuerySet
|
|
|
- Extending `clean()` to validate GenericForeignKey fields
|
|
- Extending `clean()` to validate GenericForeignKey fields
|
|
|
|
|
+ - Extending `clean()` and `save()` to coerce empty strings to None on unique nullable CharFields
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
objects = RestrictedQuerySet.as_manager()
|
|
objects = RestrictedQuerySet.as_manager()
|
|
@@ -65,11 +66,26 @@ class BaseModel(models.Model):
|
|
|
class Meta:
|
|
class Meta:
|
|
|
abstract = True
|
|
abstract = True
|
|
|
|
|
|
|
|
|
|
+ def _coerce_nullable_unique_chars(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Coerce empty strings to None on unique nullable CharFields to avoid spurious
|
|
|
|
|
+ uniqueness violations (PostgreSQL treats two empty strings as duplicates).
|
|
|
|
|
+ """
|
|
|
|
|
+ for field in self._meta.concrete_fields:
|
|
|
|
|
+ if (
|
|
|
|
|
+ isinstance(field, models.CharField)
|
|
|
|
|
+ and field.null
|
|
|
|
|
+ and field.unique
|
|
|
|
|
+ and getattr(self, field.attname, None) == ''
|
|
|
|
|
+ ):
|
|
|
|
|
+ setattr(self, field.attname, None)
|
|
|
|
|
+
|
|
|
def clean(self):
|
|
def clean(self):
|
|
|
"""
|
|
"""
|
|
|
Validate the model for GenericForeignKey fields to ensure that the content type and object ID exist.
|
|
Validate the model for GenericForeignKey fields to ensure that the content type and object ID exist.
|
|
|
"""
|
|
"""
|
|
|
super().clean()
|
|
super().clean()
|
|
|
|
|
+ self._coerce_nullable_unique_chars()
|
|
|
|
|
|
|
|
for field in self._meta.get_fields():
|
|
for field in self._meta.get_fields():
|
|
|
if isinstance(field, GenericForeignKey):
|
|
if isinstance(field, GenericForeignKey):
|
|
@@ -97,6 +113,10 @@ class BaseModel(models.Model):
|
|
|
# update the GFK field value
|
|
# update the GFK field value
|
|
|
setattr(self, field.name, obj)
|
|
setattr(self, field.name, obj)
|
|
|
|
|
|
|
|
|
|
+ def save(self, *args, **kwargs):
|
|
|
|
|
+ self._coerce_nullable_unique_chars()
|
|
|
|
|
+ super().save(*args, **kwargs)
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, EventRulesMixin, BaseModel):
|
|
class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, EventRulesMixin, BaseModel):
|
|
|
"""
|
|
"""
|