- commit
- fa2cbdfe24adc6b5f4e5e42c042663904ff25d96
- parent
- 6ea79efa7b3a6b8f6a0f1d71693356ca012e71ca
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2025-08-06 13:49
init
Diffstat
| A | .github/workflows/main.yml | 25 | +++++++++++++++++++++++++ |
| A | README.md | 80 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | parlor/__init__.py | 0 | |
| A | parlor/admin.py | 15 | +++++++++++++++ |
| A | parlor/models.py | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | pyproject.toml | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 208 insertions, 0 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
@@ -0,0 +1,25 @@ -1 1 on: [push] -1 2 jobs: -1 3 lint: -1 4 runs-on: ubuntu-latest -1 5 steps: -1 6 - uses: actions/checkout@v4 -1 7 - uses: actions/setup-python@v5 -1 8 - run: pip install ruff -1 9 - name: linters -1 10 run: | -1 11 ruff check parlor -1 12 publish: -1 13 needs: [lint] -1 14 if: startsWith(github.ref, 'refs/tags') -1 15 runs-on: ubuntu-latest -1 16 permissions: -1 17 id-token: write -1 18 steps: -1 19 - uses: actions/checkout@v4 -1 20 - uses: actions/setup-python@v5 -1 21 - run: pip install build -1 22 - name: build -1 23 run: python3 -m build -1 24 - name: publish -1 25 uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/README.md b/README.md
@@ -0,0 +1,80 @@
-1 1 # django-parlor
-1 2
-1 3 Django model translations with even less nasty hacks.
-1 4
-1 5 ## Installation
-1 6
-1 7 ```
-1 8 pip install django-parlor
-1 9 ```
-1 10
-1 11 ## Usage
-1 12
-1 13 ```python
-1 14 from django.contrib import admin
-1 15 from django.db import models
-1 16
-1 17 from parlor.admin import TranslatableAdmin
-1 18 from parlor.models import TranslatableModel
-1 19
-1 20
-1 21 class MyModel(TranslatableModel):
-1 22 ...
-1 23
-1 24
-1 25 class MyModelTranslation(model.Model):
-1 26 parent = MyModel.get_parent_field()
-1 27 language_code = MyModel.get_lang_field()
-1 28 ...
-1 29
-1 30 class Meta:
-1 31 unique_together = [('parent', 'language_code')]
-1 32
-1 33
-1 34 admin.site.register(MyModel, TranslatableAdmin)
-1 35 ```
-1 36
-1 37 ## Status
-1 38
-1 39 Right now this is more of a proof-of-concept. If people are interested in this
-1 40 library it could certainly be expanded.
-1 41
-1 42 ## Relation to django-parler
-1 43
-1 44 ### Standing on the Shoulders of Giants
-1 45
-1 46 There are two popular libraries for model translation in Django:
-1 47 [django-modeltranslation](https://github.com/deschler/django-modeltranslation)
-1 48 and [django-parler](https://github.com/django-parler/django-parler). The former
-1 49 adds additional columns to the same table while the latter adds a new table for
-1 50 each translatable model. Both of these libraries use a lot of magic which makes
-1 51 them easy to use, but also leads to some hard-to-debug edge cases.
-1 52
-1 53 There is also
-1 54 [django-translated-fields](https://github.com/matthiask/django-translated-fields),
-1 55 which uses the same basic approach as django-modeltranslation, but with much
-1 56 less magic (and much less lines of code).
-1 57
-1 58 Here I try to do something similar for django-parler, which has been
-1 59 unmaintained for some time now.
-1 60
-1 61 ### Migration
-1 62
-1 63 If you want to migrate from django-parler to django-parlor, you need to replace
-1 64 all instances of `parler.TranslatedFields` by explicit translation models as
-1 65 described above. Note that the `parent` field is called `master` in
-1 66 django-parler.
-1 67
-1 68 To keep using the same data, you will also need to set `Meta.db_table` to the
-1 69 name generated by django-parler (typically `myapp_mymodel_translation`).
-1 70
-1 71 Finally you should run `manage.py makemigrations`. The generated migrations
-1 72 will only contain minor changes though.
-1 73
-1 74 ### Conceptual differences
-1 75
-1 76 - With django-parlor, the translation model must be created explicitly.
-1 77 - In the admin UI, django-parlor uses a stock inline instead of a custom
-1 78 tabbed interface.
-1 79 - django-parlor only provides basic attribute access while django-parler
-1 80 provides a lot more features.
diff --git a/parlor/__init__.py b/parlor/__init__.py
diff --git a/parlor/admin.py b/parlor/admin.py
@@ -0,0 +1,15 @@ -1 1 from django.contrib import admin -1 2 -1 3 -1 4 class TranslatableAdmin(admin.ModelAdmin): -1 5 translation_inline_class = admin.TabularInline -1 6 -1 7 def get_inlines(self, request, obj): -1 8 class TranslationInline(self.translation_inline_class): -1 9 model = obj.translations.model -1 10 min_num = 2 -1 11 extra = 0 -1 12 return [ -1 13 TranslationInline, -1 14 *super().get_inlines(request, obj), -1 15 ]
diff --git a/parlor/models.py b/parlor/models.py
@@ -0,0 +1,45 @@
-1 1 from django.core.exceptions import ObjectDoesNotExist
-1 2 from django.db import models
-1 3 from django.utils.functional import cached_property
-1 4 from django.utils.translation import get_language
-1 5
-1 6
-1 7 class TranslationFallback:
-1 8 def __getattr__(self, key):
-1 9 return 'not translated'
-1 10
-1 11
-1 12 class TranslatableModel(models.Model):
-1 13 class Meta:
-1 14 abstract = True
-1 15
-1 16 @cached_property
-1 17 def translation(self):
-1 18 lang = get_language()
-1 19 if self.pk:
-1 20 try:
-1 21 return self.translations.get(language_code=lang)
-1 22 except ObjectDoesNotExist:
-1 23 pass
-1 24 return TranslationFallback()
-1 25
-1 26 def __getattr__(self, key):
-1 27 fields = self.translations.model._meta.get_fields()
-1 28 if key in (f.attname for f in fields):
-1 29 return getattr(self.translation, key)
-1 30 else:
-1 31 raise AttributeError
-1 32
-1 33 @classmethod
-1 34 def lang_field(cls):
-1 35 return models.CharField('Language', max_length=15, db_index=True)
-1 36
-1 37 @classmethod
-1 38 def parent_field(cls):
-1 39 return models.ForeignKey(
-1 40 cls,
-1 41 on_delete=models.CASCADE,
-1 42 editable=False,
-1 43 null=True,
-1 44 related_name='translations',
-1 45 )
diff --git a/pyproject.toml b/pyproject.toml
@@ -0,0 +1,43 @@
-1 1 [build-system]
-1 2 requires = ["setuptools"]
-1 3 build-backend = "setuptools.build_meta"
-1 4
-1 5 [project]
-1 6 name = "django-parlor"
-1 7 version = "0.0.1"
-1 8 description = "Django model translations with even less nasty hacks."
-1 9 readme = "README.md"
-1 10 license = {text = "MIT"}
-1 11 keywords = ["django", "translation"]
-1 12 authors = [
-1 13 {name = "Tobias Bengfort", email = "tobias.bengfort@posteo.de"},
-1 14 ]
-1 15 classifiers = [
-1 16 "Development Status :: 4 - Beta",
-1 17 "Environment :: Web Environment",
-1 18 "Framework :: Django",
-1 19 "Intended Audience :: Developers",
-1 20 "License :: OSI Approved :: MIT License",
-1 21 "Operating System :: OS Independent",
-1 22 "Programming Language :: Python",
-1 23 "Programming Language :: Python :: 3",
-1 24 ]
-1 25 dependencies = [
-1 26 "django>=3.2",
-1 27 ]
-1 28
-1 29 [project.urls]
-1 30 Homepage = "https://github.com/xi/django-parlor"
-1 31
-1 32 [tool.setuptools.packages.find]
-1 33 include = ["parlor*"]
-1 34
-1 35 [tool.ruff.lint]
-1 36 select = ["E", "F", "W", "C9", "I", "Q", "UP", "RUF"]
-1 37 ignore = ["RUF012"]
-1 38
-1 39 [tool.ruff.lint.flake8-quotes]
-1 40 inline-quotes = "single"
-1 41
-1 42 [tool.ruff.lint.isort]
-1 43 force-single-line = true