Repository: wagtail/wagtail
File: wagtail/images/models.py (Python)

Code before:
            )

            # We only include fields to check if they are a UniqueConstraint, and
            # they have no conditions on them.
            unique_constraint_fields = [
                constraint.fields
                for constraint in cls._meta.constraints
                if (
                    isinstance(constraint, UniqueConstraint)
                    and constraint.condition is None
                )
            ]
            in_unique_constraint = any(
                set(constraint) == {"image", "filter_spec", "focal_point_key"}
                for constraint in unique_constraint_fields
            )

            # We check for some kind of unique constraint, whether in unique together or
            # as part of the constraints.
            if not in_unique_together and not in_unique_constraint:
                errors.append(
                    checks.Error(
                        "Custom rendition model %r has an invalid unique_together setting"
                        % cls,
                        hint="Custom rendition models must include a unique constraint "
                        "on the 'image', 'filter_spec', and 'focal_point_key' fields.",
                        obj=cls,
                        id="wagtailimages.E001",
                    )
                )

        return errors

    @staticmethod
    def construct_cache_key(image, filter_cache_key, filter_spec):
        return "wagtail-rendition-" + "-".join(
            [str(image.id), image.file_hash, filter_cache_key, filter_spec]
        )

    @classproperty
    def cache_backend(cls) -> BaseCache:
        try:
            return caches["renditions"]
        except InvalidCacheBackendError:
            return caches[DEFAULT_CACHE_ALIAS]

    def get_cache_key(self):
        return self.construct_cache_key(
            self.image, self.focal_point_key, self.filter_spec
        )


Review comment (suggestion):
I think this should be the new error message itself (right now the error message still uses `unique_together`), and the `hint` should be something the developer can use, e.g. suggesting

```py
                        "Add models.UniqueConstraint(fields={'image', 'filter_spec', 'focal_point_key'}, "
                        "name='unique_rendition') to %s.Meta.constraints."
                        % (cls.__name__,)
```

Code after:
            in_unique_together = any(
                set(constraint) == unique_constraint_fields
                for constraint in cls._meta.unique_together
            )
            in_unique_constraint = any(
                isinstance(constraint, models.UniqueConstraint)
                and constraint.condition is None
                and set(constraint.fields) == unique_constraint_fields
                for constraint in cls._meta.constraints
            )

            if not in_unique_together and not in_unique_constraint:
                errors.append(
                    checks.Error(
                        "Custom rendition model '%s' must include a unique constraint "
                        "on the 'image', 'filter_spec', and 'focal_point_key' fields."
                        % cls._meta.label,
                        hint=(
                            "Add models.UniqueConstraint(fields={"
                            '"image", "filter_spec", "focal_point_key"}, '
                            'name="unique_rendition") to %s.Meta.constraints.'
                            % (cls.__name__,)
                        ),
                        obj=cls,
                        id="wagtailimages.E001",
                    )
                )

        return errors

    @staticmethod
    def construct_cache_key(image, filter_cache_key, filter_spec):
        return "wagtail-rendition-" + "-".join(
            [str(image.id), image.file_hash, filter_cache_key, filter_spec]
        )

    @classproperty
    def cache_backend(cls) -> BaseCache:
        try:
            return caches["renditions"]
        except InvalidCacheBackendError:
            return caches[DEFAULT_CACHE_ALIAS]

    def get_cache_key(self):
        return self.construct_cache_key(
            self.image, self.focal_point_key, self.filter_spec
        )

    def purge_from_cache(self):
        self.cache_backend.delete(self.get_cache_key())