Created
July 19, 2022 12:39
-
-
Save sin-ack/41c01b1d7f75d7cfd64702d0502341f0 to your computer and use it in GitHub Desktop.
How to make mypy sweat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| _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