Skip to content

Commit

Permalink
Merge pull request #65 from Aristotle-Metadata-Enterprises/fixValues
Browse files Browse the repository at this point in the history
Fixes .values in QuerySet for translatable values
  • Loading branch information
s-i-l-k-e authored Feb 20, 2022
2 parents ccc3085 + 891cd16 commit 6e631b4
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 7 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Pros:

Cons:
* You need to alter the models, so you can't make third-party libraries translatable.
* It doesn't work on `queryset.values_list` - but we have a workaround below.
* 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.

## Why write a new Django field translator?

Expand Down Expand Up @@ -283,17 +283,19 @@ with set_field_language("fr"):
greeting.save()
```

## Using Garnett with `values_list`
## Using Garnett with `values_list` or `values`

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

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:
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:

```python
from garnett.expressions import L
Book.objects.values_list(L("title"))
Book.objects.values(L("title"))
```


## Using Garnett with Django-Rest-Framework

As `TranslationField`s are based on JSONField, by default Django-Rest-Framework renders these as a JSONField, which may not be ideal.
Expand Down
2 changes: 2 additions & 0 deletions garnett/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@


# Based on: https://code.djangoproject.com/ticket/29769#comment:5
# Updated comment here
# https://code.djangoproject.com/ticket/31639
class LangF(F):
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
Expand Down
62 changes: 62 additions & 0 deletions garnett/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from django.db.models.query import BaseIterable
from garnett.expressions import L

PREFIX = "L_garnett__"


class TranslatableValuesIterable(BaseIterable):
"""
Unwind the modifications that are made before calling .values()
Iterable returned by QuerySet.values() that yields a dict for each row.
"""

def clean_garnett_field(self, field_name) -> str:
"""Return the field name minus the prefix"""
return field_name.replace(PREFIX, "")

def __iter__(self):
queryset = self.queryset
query = queryset.query
compiler = query.get_compiler(queryset.db)

# extra(select=...) cols are always at the start of the row.
names = [
*query.extra_select,
*query.values_select,
*query.annotation_select,
]
indexes = range(len(names))
for row in compiler.results_iter(
chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
):
yield {self.clean_garnett_field(names[i]): row[i] for i in indexes}


class TranslatedQuerySetMixin:
"""
A translated QuerySet mixin to add extra functionality to translated fields
Must be mixedin to a QuerySet
"""

def values(self, *fields, **expressions):
"""
.values() for translatable fields
Still expects values to be passed with L()
"""

# Convert anything that is an L from a field to an expression - so it treats it as an expression
# rather than a field.
# We will clean the field prefix in our custom iterable class "TranslatableQuerySetMixin"
cleaned_fields = []
for field in fields:
if isinstance(field, L):
expressions.update(
{f"{PREFIX}{field.source_expressions[0].name}": field}
)
else:
cleaned_fields.append(field)

clone = super().values(*cleaned_fields, **expressions)
clone._iterable_class = TranslatableValuesIterable

return clone
3 changes: 1 addition & 2 deletions garnett/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ class JoinInfo:
@property
def transform_function(self):
if isinstance(self.final_field, TranslatedField):
# If its a partial, it must have had a transformer applied - leave it alone!
# If it's a partial, it must have had a transformer applied - leave it alone!
if isinstance(self.transform_function_func, functools.partial):

return self.transform_function_func

name = get_current_language()
Expand Down
2 changes: 1 addition & 1 deletion garnett/translatedstr.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def get_fallback_text(cls, content):

class NextTranslatedStr(TranslatedStr, HTMLTranslationMixin):
"""
A translated string that fallsback based on the order of preferred langages in the app.
A translated string that falls back based on the order of preferred languages in the app.
"""

@classmethod
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "django-garnett"
version = "0.4.4"
version = "0.4.5"
description = "Simple translatable Django fields"
authors = ["Aristotle Metadata Enterprises"]
license = "BSD-3-Clause"
Expand Down
2 changes: 2 additions & 0 deletions run.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/bin/bash

export PYTHONPATH=$PYTHONPATH:/app:/app/tests/:/usr/src/app:/usr/src/app/tests/
export DJANGO_SETTINGS_MODULE=library_app.settings

Expand Down
6 changes: 6 additions & 0 deletions tests/library_app/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.db.models import QuerySet
from garnett.mixins import TranslatedQuerySetMixin


class BookQuerySet(TranslatedQuerySetMixin, QuerySet):
pass
4 changes: 4 additions & 0 deletions tests/library_app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,24 @@ def __html__(self) -> str:

class Book(models.Model):
number_of_pages = models.PositiveIntegerField()

title = fields.Translated(
models.CharField(max_length=250, validators=[validate_length]),
fallback=TitleTranslatedStr,
help_text=_("The name for a book. (Multilingal field)"),
)

author = models.TextField(
help_text=_(
"The name of the person who wrote the book (Single language field)"
),
default="Anon",
)

description = fields.Translated(
models.TextField(help_text=_("Short details about a book. (Multilingal field)"))
)

category = models.JSONField(blank=True, null=True)

def get_absolute_url(self):
Expand Down

0 comments on commit 6e631b4

Please sign in to comment.