Skip to content

Commit 42fe323

Browse files
committed
feat: Add flexibility to use either BigInt or UUID for translation table primary key
- Introduced global and model-specific configuration for Lisan model primary keys - Allowed choice of BigInt or UUID primary key, configurable via settings or per model - Updated LisanModelMeta to dynamically apply primary key type to the translation model
1 parent 1b81354 commit 42fe323

File tree

6 files changed

+203
-53
lines changed

6 files changed

+203
-53
lines changed

CHANGELOG.md

+67
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,70 @@
1+
## [v0.1.5] - 2024-11-11
2+
3+
This release introduces two major enhancements to `Lisan`'s translation model functionality, improving flexibility in primary key configurations and structuring translation data for API responses.
4+
5+
### Added
6+
- **Flexible Primary Key Configuration for Translation Tables**:
7+
- Added support to configure primary key type for translation (Lisan) tables, allowing the use of either `BigInt` or `UUID` as primary keys.
8+
- Configurable globally via `settings.py` (`LISAN_PRIMARY_KEY_TYPE`) or per model by setting the `lisan_primary_key_type` attribute.
9+
- Ensures flexibility for projects that require UUIDs or BigInts based on specific requirements or database configurations.
10+
11+
- **Nested Translation Serializer for Structured API Responses**:
12+
- Introduced a `TranslationSerializer` to handle multilingual data in API responses, providing a structured, nested format.
13+
- Integrated `TranslationSerializer` within `LisanSerializerMixin`, enabling organized representation of translations in API responses.
14+
- Allows each translation entry to include fields such as `language_code` and all specified translatable fields, making it easier to work with multilingual data in client applications.
15+
16+
### Improved
17+
- **Dynamic Primary Key Assignment for Lisan Models**:
18+
- Enhanced the `LisanModelMeta` metaclass to detect and apply the specified primary key type dynamically, either at the model level or globally.
19+
- Ensured that the primary key type for Many-to-Many join tables remains `AutoField` even when the `Lisan` model uses `UUIDField` as its primary key, simplifying compatibility with Django’s default join tables.
20+
21+
### Configuration Changes
22+
- **New Settings**:
23+
- `LISAN_PRIMARY_KEY_TYPE`: Allows configuration of the primary key type for translation tables globally. Options include `BigAutoField` (default) and `UUIDField`.
24+
25+
### Migration Notes
26+
- A new migration is required if you change the primary key type for existing translation tables. After updating, use the following commands:
27+
28+
```bash
29+
python manage.py makemigrations
30+
python manage.py migrate
31+
```
32+
33+
### Example Usage
34+
35+
- **Primary Key Configuration**: Define primary key type globally in `settings.py` or per model:
36+
```python
37+
# In settings.py
38+
LISAN_PRIMARY_KEY_TYPE = models.UUIDField
39+
40+
# Per model configuration
41+
class MyModel(LisanModelMixin, models.Model):
42+
lisan_primary_key_type = models.UUIDField
43+
```
44+
45+
- **Translation Serializer**: Access structured translation data in API responses with `TranslationSerializer`:
46+
```json
47+
{
48+
"id": 1,
49+
"title": "Sample Title",
50+
"description": "Sample Description",
51+
"translations": [
52+
{
53+
"language_code": "am",
54+
"title": "ምሳሌ ርእስ",
55+
"description": "ምሳሌ መግለጫ"
56+
},
57+
{
58+
"language_code": "en",
59+
"title": "Sample Title",
60+
"description": "Sample Description"
61+
}
62+
]
63+
}
64+
```
65+
66+
---
67+
168
## [v0.1.4] - 2024-10-07
269

370
This release introduces improvements in the translation validation process for partial updates, ensuring that translations are properly validated unless explicitly omitted in partial updates.

README.md

+40-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
## Features
88

99
- **Automatic Translation Models:** Automatically generate translation models for your fields.
10+
- **Flexible Primary Key Configuration:** Use either BigInt or UUID as primary keys for translation tables, configurable globally or per model.
1011
- **Admin Integration:** Seamlessly manage translations through the Django admin interface.
1112
- **Fallback Mechanism:** Fallback to the default language if a translation is not available.
1213
- **Dynamic Getter Methods:** Automatically generate methods to access translated fields.
@@ -44,7 +45,7 @@ pip install lisan
4445

4546
### 1. Configuring Lisan
4647

47-
To start using `Lisan` in your project, you need to configure the language settings and middleware in your Django settings file.
48+
To start using `Lisan` in your project, configure the language settings, middleware, and primary key type (if needed) in your Django settings file.
4849

4950
#### Step 1.0: Add Lisan Language Settings
5051

@@ -55,7 +56,20 @@ LISAN_FALLBACK_LANGUAGES = ['fr', 'es', 'en'] # Customize fallback languages
5556
LISAN_DEFAULT_TRANSLATION_SERVICE = 'yourapp.google_translate_service.GoogleTranslateService' # Pluggable translation service
5657
```
5758

58-
#### Step 1.1: Add Lisan Middleware
59+
#### Step 1.1: Configure Primary Key Type (Optional)
60+
61+
You can configure `Lisan` to use either `BigInt` or `UUID` as the primary key for translation tables.
62+
63+
To set this globally, use the `LISAN_PRIMARY_KEY_TYPE` setting in `settings.py`:
64+
65+
```python
66+
from django.db import models
67+
LISAN_PRIMARY_KEY_TYPE = models.UUIDField # Options: models.BigAutoField (default) or models.UUIDField
68+
```
69+
70+
Alternatively, define `lisan_primary_key_type` on specific models to override the global setting.
71+
72+
#### Step 1.2: Add Lisan Middleware
5973

6074
Make sure to include `Lisan`'s middleware in your `MIDDLEWARE` settings for automatic language detection and management:
6175

@@ -86,6 +100,9 @@ class Snippet(LisanModelMixin, models.Model):
86100
description = models.TextField(blank=True, default='')
87101
created = models.DateTimeField(auto_now_add=True)
88102

103+
# Optionally specify UUIDField as primary key for translation tables
104+
lisan_primary_key_type = models.UUIDField
105+
89106
class Meta:
90107
ordering = ['created']
91108
```
@@ -162,9 +179,9 @@ To create a snippet with translations, send a `POST` request to the appropriate
162179
}
163180
```
164181

165-
### 2. Retrieving a Snippet with a Specific Translation
182+
### 2. Retrieving a Snippet with Translations Using Nested Translation Serializer
166183

167-
To retrieve a snippet in a specific language, send a `GET` request with the appropriate `Accept-Language` header to specify the desired language (e.g., `am` for Amharic).
184+
To retrieve translations for a snippet, use the `TranslationSerializer` to structure the translations in a nested format.
168185

169186
**Request Example**:
170187

@@ -173,15 +190,27 @@ GET /api/snippets/1/
173190
Accept-Language: am
174191
```
175192

176-
The response will return the snippet information in the requested language if available, or it will fallback to the default language:
193+
The response will include all translations for the snippet in a structured format:
177194

178195
**Response Example**:
179196

180197
```json
181198
{
182199
"id": 1,
183-
"title": "ኮድ ቅርጸት ምሳሌ",
184-
"description": "እንቁ ምሳሌ"
200+
"title": "Code Snippet Example",
201+
"description": "Example Description",
202+
"translations": [
203+
{
204+
"language_code": "am",
205+
"title": "ኮድ ቅርጸት ምሳሌ",
206+
"description": "እንቁ ምሳሌ"
207+
},
208+
{
209+
"language_code": "en",
210+
"title": "Code Snippet Example",
211+
"description": "Example Description"
212+
}
213+
]
185214
}
186215
```
187216

@@ -219,7 +248,9 @@ from googletrans import Translator
219248
from lisan.translation_services import BaseTranslationService
220249

221250
class GoogleTranslateService(BaseTranslationService):
222-
def __init__(self):
251+
def __init
252+
253+
__(self):
223254
self.translator = Translator()
224255

225256
def translate(self, text, target_language):
@@ -263,4 +294,4 @@ If you find any issues or have suggestions for improvements, feel free to open a
263294

264295
## License
265296

266-
Lisan is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.
297+
Lisan is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.

lisan/metaclasses.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from django.db import models
2+
from django.conf import settings
23
from django.utils.translation import gettext_lazy as _
34

45

5-
def create_lisan_model(model_cls, fields):
6+
def create_lisan_model(
7+
model_cls, fields, primary_key_type=models.BigAutoField):
68
"""
79
Dynamically create a Lisan model for the given model class.
810
@@ -37,6 +39,19 @@ class Meta:
3739
),
3840
}
3941

42+
if primary_key_type == models.UUIDField:
43+
import uuid
44+
# Configure UUIDField with auto-generation
45+
attrs['id'] = models.UUIDField(
46+
primary_key=True,
47+
default=uuid.uuid4,
48+
editable=False,
49+
verbose_name=_("id")
50+
)
51+
else:
52+
# Default primary key field setup
53+
attrs['id'] = primary_key_type(primary_key=True)
54+
4055
# Add the specified fields to the Lisan model
4156
for field_name, field in fields.items():
4257
attrs[field_name] = field
@@ -86,24 +101,37 @@ def __new__(cls, name, bases, attrs):
86101
"""
87102
if 'LisanModelMixin' in [base.__name__ for base in bases]:
88103
lisan_fields = attrs.get('lisan_fields')
89-
104+
90105
# If `lisan_fields` is not defined, raise an exception
91106
if lisan_fields is None:
92107
raise AttributeError(
93-
f"{name} must define 'lisan_fields' when using LisanModelMixin."
108+
f"{name} must define 'lisan_fields' when using LisanModelMixin." # noqa
94109
)
95110

96-
# Filter translatable fields by checking if they are defined in lisan_fields
111+
# Filter translatable fields by checking if they are
112+
# defined in lisan_fields
97113
translatable_fields = {
98114
key: value for key, value in attrs.items()
99115
if isinstance(value, models.Field) and key in lisan_fields
100116
}
101117

118+
# Determine primary key type, checking model-specific
119+
# setting first, then global
120+
primary_key_type = attrs.get(
121+
'lisan_primary_key_type', # Model-specific setting
122+
getattr(
123+
settings,
124+
'LISAN_PRIMARY_KEY_TYPE',
125+
models.BigAutoField
126+
)
127+
)
128+
102129
# Create the new model class
103130
new_class = super().__new__(cls, name, bases, attrs)
104131

105132
# Generate the Lisan model
106-
lisan_model = create_lisan_model(new_class, translatable_fields)
133+
lisan_model = create_lisan_model(
134+
new_class, translatable_fields, primary_key_type)
107135
setattr(new_class, 'Lisan', lisan_model)
108136

109137
# Add a ForeignKey linking the original model to the Lisan model

lisan/mixins.py

+16
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,29 @@ def set_lisan(self, language_code, **lisan_fields):
7575
f"Field '{field_name}' does not exist in the "
7676
"translation model."
7777
)
78+
79+
primary_key_field = getattr(self.Lisan._meta, 'pk', None)
80+
if primary_key_field and primary_key_field.name != 'id':
81+
primary_key_value = lisan_fields.pop(
82+
primary_key_field.name, None)
83+
else:
84+
primary_key_value = None
7885

7986
# Explicitly set the foreign key to self (the instance)
87+
# TODO: this is not capturing the UUID for the id
8088
lisan = self.Lisan(
8189
**lisan_fields,
8290
language_code=language_code,
8391
**{self._meta.model_name: self}
8492
)
93+
94+
if primary_key_value is not None:
95+
setattr(
96+
lisan,
97+
primary_key_field.name,
98+
primary_key_value
99+
)
100+
85101
lisan.save()
86102
self.lisans.add(lisan)
87103
else:

0 commit comments

Comments
 (0)