diff --git a/README.md b/README.md index e25b559..ab55db0 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,8 @@ Refactoring old functionality and getting rid of unnecessary * [x] Testing * [x] Abstract model with publishing flag * [x] Abstract model with the functionality of publishing in a certain period - * [ ] Universal manager worked with all abstract models * i18n - * [ ] Field translation simplification functionality when using [django-modeltranslation](https://django-modeltranslation.readthedocs.io/en/latest/) + * [x] Field translation simplification functionality when using [django-modeltranslation](https://django-modeltranslation.readthedocs.io/en/latest/) * [x] Localization of all texts in the library * utils * [x] Method and template tag for getting date range @@ -85,6 +84,7 @@ Refactoring old functionality and getting rid of unnecessary Adding new functionality. Can change. +* Universal manager worked with all abstract models * Tests of all functionality * Methods for encrypting and decrypting text content (To create various secrets, such as a link to change your password or activate your profile). * Functionality for obtaining absolute links to resources presented on the front, located on another domain (When working through api) (utils methods, template tags and filters). diff --git a/docs/src/usage/core.md b/docs/src/usage/core.md index e45f673..39b2d91 100644 --- a/docs/src/usage/core.md +++ b/docs/src/usage/core.md @@ -113,6 +113,19 @@ An abstract model that adds a sortable field, as well as a manager with sorting show_source: false +## Translations + +If you use [djano-modeltranslation](https://django-modeltranslation.readthedocs.io/en/latest/), then when connecting `meringue.core`, you can register fields for translations by setting the list of fields in the `m_translate_fields` field in the meta of the corresponding model: + +```py +class FooModel(models.Model): + name = models.CharField(max_length=32) + + class Meta: + m_translate_fields = ["name", ] +``` + + ## Views diff --git a/meringue/core/__init__.py b/meringue/core/__init__.py index 83d0de8..3f09443 100644 --- a/meringue/core/__init__.py +++ b/meringue/core/__init__.py @@ -1,5 +1,7 @@ import django +from meringue.core import options # nowa: F401 + if django.VERSION < (3, 2): default_app_config = "meringue.core.apps.Config" diff --git a/meringue/core/options.py b/meringue/core/options.py index 5a80691..2c6eb3e 100644 --- a/meringue/core/options.py +++ b/meringue/core/options.py @@ -3,5 +3,5 @@ options.DEFAULT_NAMES = ( *options.DEFAULT_NAMES, - "translate_fields", + "m_translate_fields", ) diff --git a/meringue/core/tests/test_teapots.py b/meringue/core/tests/test_teapots.py index b754de7..2417642 100644 --- a/meringue/core/tests/test_teapots.py +++ b/meringue/core/tests/test_teapots.py @@ -4,7 +4,7 @@ def test_check_teapot(): """ - Nominal mixin check + Im a teapot """ client = Client() response = client.get(reverse("make_coffee")) diff --git a/meringue/core/tests/test_translation.py b/meringue/core/tests/test_translation.py new file mode 100644 index 0000000..c03ab7a --- /dev/null +++ b/meringue/core/tests/test_translation.py @@ -0,0 +1,12 @@ +from test_project.models import TranslatedModel + + +def test_translate_fields(): + """ + Checking the registration of fields for translation + """ + + instance = TranslatedModel() + assert hasattr(instance, "name") + assert hasattr(instance, "name_ru") + assert hasattr(instance, "name_en") diff --git a/meringue/core/translation.py b/meringue/core/translation.py index 69b7f19..2adf5e0 100644 --- a/meringue/core/translation.py +++ b/meringue/core/translation.py @@ -1,51 +1,18 @@ -from importlib import import_module - from django.apps import apps -from django.conf import settings from modeltranslation.translator import TranslationOptions from modeltranslation.translator import translator -try: - from polymorphic.utils import get_base_polymorphic_model -except ImportError: - pass -else: - for app in settings.INSTALLED_APPS: - try: - models = import_module("%s.models" % app) - except ImportError: - bits = app.split(".") - app_name = import_module(bits[0]) - - for bit in bits[1:]: - app_name = getattr(app_name, bit) - - if hasattr(app_name, "name"): - try: - models = import_module("%s.models" % app_name.name) - except ImportError: - continue - else: - continue - - except AttributeError: - continue - - for model in apps.get_models(): - fields = getattr(model._meta, "translate_fields", []) - force = False - - if not fields and get_base_polymorphic_model and get_base_polymorphic_model(model): - force = True +for model in apps.get_models(): + fields = getattr(model._meta, "m_translate_fields", []) - if (fields or force) and model not in translator.get_registered_models(): - translator.register( - model, - type( - str(model.__name__ + "Translation"), - (TranslationOptions,), - {"fields": fields}, - ), - ) + if fields and model not in translator.get_registered_models(): + translator.register( + model, + type( + str(model.__name__ + "Translation"), + (TranslationOptions,), + {"fields": fields}, + ), + ) diff --git a/pyproject.toml b/pyproject.toml index bb15a9c..0cf7e19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,7 +125,7 @@ description = "Lint environment" detached = true python = "3.11" dependencies = [ - "ruff==0.0.272", + "ruff==0.0.275", "black==23.3.0", ] [tool.hatch.envs.lint.scripts] @@ -149,6 +149,7 @@ dependencies = [ "Faker==18.11.1", "django==4.2", "pytz==2023.3", + "django-modeltranslation==0.18.10", ] [tool.hatch.envs.test.env-vars] DJANGO_SETTINGS_MODULE = 'test_project.settings' @@ -169,6 +170,7 @@ dependencies = [ [tool.hatch.envs.mtest.overrides] matrix.django.dependencies = [ { value = "django~={matrix:django}" }, + { value = "django-modeltranslation=={matrix:modeltranslation}" }, ] [tool.hatch.envs.mtest.env-vars] DJANGO_SETTINGS_MODULE = 'test_project.settings' @@ -177,7 +179,12 @@ PYTHONPATH = '.' check = "pytest {args:-q}" [[tool.hatch.envs.mtest.matrix]] python = ["3.8", "3.9", "3.10", "3.11"] -django = ["2.0", "3.0", "4.0"] +django = ["2.0"] +modeltranslation = ["0.17.7"] +[[tool.hatch.envs.mtest.matrix]] +python = ["3.8", "3.9", "3.10", "3.11"] +django = ["3.0", "4.0"] +modeltranslation = ["0.18.10"] [tool.hatch.envs.docs] description = "Docs environment" @@ -186,7 +193,7 @@ python = "3.11" dependencies = [ "mkdocs[i18n]==1.4.3", "mkdocs-literate-nav==0.6.0", - "mkdocs-material==9.1.15", + "mkdocs-material==9.1.17", "mkdocs-git-revision-date-localized-plugin==1.2.0", "mkdocs-git-authors-plugin==0.7.2", "mkdocstrings[python]==0.22.0", @@ -238,14 +245,15 @@ select = [ "YTT", ] ignore = [ + "RUF001", # Allow similar characters like latin `c` and cyrillic `с` "RUF002", # Allow russian at docstrings "RUF003", # Allow russian at comment + "FBT001", # Allow boolean args "FBT002", # Allow boolean positional argument define + "FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)` "PLR0913", # Allow any number of function arguments # # Allow non-abstract empty methods in abstract base classes # "B027", - # # Allow boolean positional values in function calls, like `dict.get(... True)` - # "FBT003", # # Ignore checks for possible passwords # "S105", "S106", "S107", # # Ignore complexity @@ -259,28 +267,23 @@ ignore = [ # "F401", # ] [tool.ruff.extend-per-file-ignores] -"__init__.py" = ["F403"] +"__init__.py" = ["F401", "F403"] "test_*.py" = ["S101", "PLR2004"] -"*/migrations/*" = ["I", "E", "Q"] - +"*/migrations/*" = ["I", "E", "Q", "RUF"] +"test_project/*models.py" = ["RUF012"] [tool.ruff.flake8-import-conventions] - [tool.ruff.flake8-import-conventions.extend-aliases] "datetime" = "dt" - # [tool.ruff.flake8-quotes] # inline-quotes = "single" - [tool.ruff.flake8-unused-arguments] ignore-variadic-names = true - [tool.ruff.isort] force-single-line = true known-first-party = ["meringue"] lines-after-imports = 2 no-lines-before = ["local-folder"] section-order = ["future", "standard-library", "django", "third-party", "first-party", "local-folder"] - [tool.ruff.isort.sections] django = ["django"] diff --git a/test_project/migrations/0001_initial.py b/test_project/migrations/0001_initial.py index 363c60a..0b304ca 100644 --- a/test_project/migrations/0001_initial.py +++ b/test_project/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2023-06-24 14:41 +# Generated by Django 4.2 on 2023-06-26 00:26 from django.db import migrations, models @@ -17,7 +17,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('ctime', models.DateTimeField(auto_now_add=True, help_text='Date and time of creation.')), ('mtime', models.DateTimeField(auto_now=True, help_text='Date and time of editing.')), - ('title', models.CharField(max_length=10)), + ('title', models.CharField(max_length=32)), ], options={ 'abstract': False, @@ -29,7 +29,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date_from', models.DateTimeField(blank=True, help_text='Date and time of publication (inclusive).', null=True, verbose_name='Date from')), ('date_to', models.DateTimeField(blank=True, help_text='Date and time when to hide.', null=True, verbose_name='Date to')), - ('title', models.CharField(max_length=10)), + ('title', models.CharField(max_length=32)), ], options={ 'abstract': False, @@ -40,7 +40,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('is_published', models.BooleanField(db_index=True, default=True, help_text='Show/Hide', verbose_name='Publication')), - ('title', models.CharField(max_length=10)), + ('title', models.CharField(max_length=32)), ], options={ 'abstract': False, @@ -51,11 +51,23 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('sorting', models.SmallIntegerField(default=0, help_text='Sorting order.', verbose_name='Sorting')), - ('title', models.CharField(max_length=10)), + ('title', models.CharField(max_length=32)), ], options={ 'ordering': ['sorting'], 'abstract': False, }, ), + migrations.CreateModel( + name='TranslatedModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32)), + ('name_ru', models.CharField(max_length=32, null=True)), + ('name_en', models.CharField(max_length=32, null=True)), + ], + options={ + 'm_translate_fields': ['name'], + }, + ), ] diff --git a/test_project/models.py b/test_project/models.py index 9b80a56..a411676 100644 --- a/test_project/models.py +++ b/test_project/models.py @@ -7,16 +7,25 @@ class CMTimeModel(CMTimeMixin): - title = models.CharField(max_length=10) + title = models.CharField(max_length=32) class SortingModel(SortingMixin): - title = models.CharField(max_length=10) + title = models.CharField(max_length=32) class PublicationModel(PublicationMixin): - title = models.CharField(max_length=10) + title = models.CharField(max_length=32) class PublicationDatesModel(PublicationDatesMixin): - title = models.CharField(max_length=10) + title = models.CharField(max_length=32) + + +class TranslatedModel(models.Model): + name = models.CharField(max_length=32) + + class Meta: + m_translate_fields = [ + "name", + ] diff --git a/test_project/settings.py b/test_project/settings.py index de76be6..7781310 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -13,6 +13,7 @@ # 'django.contrib.sessions', # 'django.contrib.messages', # 'django.contrib.staticfiles', + "modeltranslation", "test_project", "meringue.core", ] @@ -82,7 +83,6 @@ # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ -# LANGUAGE_CODE = 'en-us' TIME_ZONE = "UTC" @@ -102,4 +102,14 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +LANGUAGE_CODE = "ru" + + +gettext = lambda s: s # noqa:E731 +LANGUAGES = ( + ("ru", gettext("Russian")), + ("en", gettext("English")), +) + + MERINGUE = {}