Skip to content

Commit 6e631b4

Browse files
authored
Merge pull request #65 from Aristotle-Metadata-Enterprises/fixValues
Fixes .values in QuerySet for translatable values
2 parents ccc3085 + 891cd16 commit 6e631b4

File tree

9 files changed

+84
-7
lines changed

9 files changed

+84
-7
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Pros:
117117

118118
Cons:
119119
* You need to alter the models, so you can't make third-party libraries translatable.
120-
* It doesn't work on `queryset.values_list` - but we have a workaround below.
120+
* It doesn't work on `queryset.values_list` or `queryset.values` natively - but we have an easy Queryset Mixin to add language support for both below.
121121

122122
## Why write a new Django field translator?
123123

@@ -283,17 +283,19 @@ with set_field_language("fr"):
283283
greeting.save()
284284
```
285285

286-
## Using Garnett with `values_list`
286+
## Using Garnett with `values_list` or `values`
287287

288288
This is one of the areas that garnett _doesn't_ work immediately, but there is a solution.
289289

290-
In the places you are using values lists, wrap any translated field in an L-expression and the values list will return correctly. For example:
290+
In the places you are using values lists or `values`, wrap any translated field in an L-expression and the values list will return correctly. For example:
291291

292292
```python
293293
from garnett.expressions import L
294294
Book.objects.values_list(L("title"))
295+
Book.objects.values(L("title"))
295296
```
296297

298+
297299
## Using Garnett with Django-Rest-Framework
298300

299301
As `TranslationField`s are based on JSONField, by default Django-Rest-Framework renders these as a JSONField, which may not be ideal.

garnett/expressions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77

88
# Based on: https://code.djangoproject.com/ticket/29769#comment:5
9+
# Updated comment here
10+
# https://code.djangoproject.com/ticket/31639
911
class LangF(F):
1012
def resolve_expression(
1113
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False

garnett/mixins.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from django.db.models.query import BaseIterable
2+
from garnett.expressions import L
3+
4+
PREFIX = "L_garnett__"
5+
6+
7+
class TranslatableValuesIterable(BaseIterable):
8+
"""
9+
Unwind the modifications that are made before calling .values()
10+
Iterable returned by QuerySet.values() that yields a dict for each row.
11+
"""
12+
13+
def clean_garnett_field(self, field_name) -> str:
14+
"""Return the field name minus the prefix"""
15+
return field_name.replace(PREFIX, "")
16+
17+
def __iter__(self):
18+
queryset = self.queryset
19+
query = queryset.query
20+
compiler = query.get_compiler(queryset.db)
21+
22+
# extra(select=...) cols are always at the start of the row.
23+
names = [
24+
*query.extra_select,
25+
*query.values_select,
26+
*query.annotation_select,
27+
]
28+
indexes = range(len(names))
29+
for row in compiler.results_iter(
30+
chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
31+
):
32+
yield {self.clean_garnett_field(names[i]): row[i] for i in indexes}
33+
34+
35+
class TranslatedQuerySetMixin:
36+
"""
37+
A translated QuerySet mixin to add extra functionality to translated fields
38+
Must be mixedin to a QuerySet
39+
"""
40+
41+
def values(self, *fields, **expressions):
42+
"""
43+
.values() for translatable fields
44+
Still expects values to be passed with L()
45+
"""
46+
47+
# Convert anything that is an L from a field to an expression - so it treats it as an expression
48+
# rather than a field.
49+
# We will clean the field prefix in our custom iterable class "TranslatableQuerySetMixin"
50+
cleaned_fields = []
51+
for field in fields:
52+
if isinstance(field, L):
53+
expressions.update(
54+
{f"{PREFIX}{field.source_expressions[0].name}": field}
55+
)
56+
else:
57+
cleaned_fields.append(field)
58+
59+
clone = super().values(*cleaned_fields, **expressions)
60+
clone._iterable_class = TranslatableValuesIterable
61+
62+
return clone

garnett/patch.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ class JoinInfo:
2525
@property
2626
def transform_function(self):
2727
if isinstance(self.final_field, TranslatedField):
28-
# If its a partial, it must have had a transformer applied - leave it alone!
28+
# If it's a partial, it must have had a transformer applied - leave it alone!
2929
if isinstance(self.transform_function_func, functools.partial):
30-
3130
return self.transform_function_func
3231

3332
name = get_current_language()

garnett/translatedstr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def get_fallback_text(cls, content):
9898

9999
class NextTranslatedStr(TranslatedStr, HTMLTranslationMixin):
100100
"""
101-
A translated string that fallsback based on the order of preferred langages in the app.
101+
A translated string that falls back based on the order of preferred languages in the app.
102102
"""
103103

104104
@classmethod

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-garnett"
3-
version = "0.4.4"
3+
version = "0.4.5"
44
description = "Simple translatable Django fields"
55
authors = ["Aristotle Metadata Enterprises"]
66
license = "BSD-3-Clause"

run.sh

100644100755
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#!/bin/bash
2+
13
export PYTHONPATH=$PYTHONPATH:/app:/app/tests/:/usr/src/app:/usr/src/app/tests/
24
export DJANGO_SETTINGS_MODULE=library_app.settings
35

tests/library_app/managers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.db.models import QuerySet
2+
from garnett.mixins import TranslatedQuerySetMixin
3+
4+
5+
class BookQuerySet(TranslatedQuerySetMixin, QuerySet):
6+
pass

tests/library_app/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,24 @@ def __html__(self) -> str:
5050

5151
class Book(models.Model):
5252
number_of_pages = models.PositiveIntegerField()
53+
5354
title = fields.Translated(
5455
models.CharField(max_length=250, validators=[validate_length]),
5556
fallback=TitleTranslatedStr,
5657
help_text=_("The name for a book. (Multilingal field)"),
5758
)
59+
5860
author = models.TextField(
5961
help_text=_(
6062
"The name of the person who wrote the book (Single language field)"
6163
),
6264
default="Anon",
6365
)
66+
6467
description = fields.Translated(
6568
models.TextField(help_text=_("Short details about a book. (Multilingal field)"))
6669
)
70+
6771
category = models.JSONField(blank=True, null=True)
6872

6973
def get_absolute_url(self):

0 commit comments

Comments
 (0)