Skip to content

Instantly share code, notes, and snippets.

@sin-ack
Created July 19, 2022 12:39
Show Gist options
  • Select an option

  • Save sin-ack/41c01b1d7f75d7cfd64702d0502341f0 to your computer and use it in GitHub Desktop.

Select an option

Save sin-ack/41c01b1d7f75d7cfd64702d0502341f0 to your computer and use it in GitHub Desktop.
How to make mypy sweat
_TranslatedM = TypeVar("_TranslatedM", bound="TranslatedModel", covariant=True)
class TranslationModelBase(Generic[_TranslatedM], Model):
"""Base class for translation models."""
language = models.ForeignKey["Language", "Language"](
to="Language", on_delete=models.CASCADE, verbose_name=_("Language")
)
parent: "models.ForeignKey[_TranslatedM, _TranslatedM]"
class Meta:
abstract = True
unique_together = ("language", "parent")
def __str__(self):
base_representation = f"{self.language.name} translation for {self.parent}"
if hasattr(self, "name"):
return f"{self.name} ({base_representation})" # type: ignore
return base_representation
class TranslatedModelMeta(ModelBase):
"""Metaclass for TranslatedModel objects.
This class adds the `Translation` property to its created models which is an abstract model that has an automatic
foreign key to the newly-created model. This can then be used to create the translations for the associated models.
"""
def __new__(cls, name: str, bases: Tuple[type], attrs: Dict[str, Any]) -> type:
new_model: Type[Model] = super().__new__(cls, name, bases, attrs) # type: ignore
# Create a new TranslationModelBase subclass that points to this newly created model.
Translation = type(
f"{name}TranslationBase",
(TranslationModelBase,),
{
"__module__": new_model.__module__,
"parent": models.ForeignKey(
to=new_model,
on_delete=models.CASCADE,
verbose_name=new_model._meta.verbose_name,
related_name="translations",
),
"Meta": type("Meta", (), {"abstract": True}),
},
)
# FIXME: django-stubs needs ModelBase.add_to_class.
new_model.add_to_class("Translation", Translation) # type: ignore
return new_model
class TranslatedModel(Model, metaclass=TranslatedModelMeta):
"""A model that will have translated fields related to it.
Intended use is as follows:
class Foo(TranslatedModel):
# Required because Python can't figure out circular types.
translation: "FooTranslation"
translations: "RelatedManager[FooTranslation]"
class FooTranslation(Foo.Translation):
name = models.CharField[str, str](...)
description = models.TextField[str, str](...)
"""
Translation: "Type[TranslationModelBase[Self]]"
translations: "RelatedManager[TranslationModelBase]"
@property
def translation(self) -> "TranslationModelBase":
return self.translations.get(language__code="en") # FIXME: No hablo ingles
class Meta:
abstract = True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment