Skip to content

Commit

Permalink
feat: Support named argument for values_list (#644)
Browse files Browse the repository at this point in the history
Co-authored-by: serdtsekol <serdtsekol@gmail.com>
  • Loading branch information
last-partizan and serdtsekol1 committed Jul 10, 2022
1 parent 1b56110 commit 39bbe82
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 27 deletions.
60 changes: 37 additions & 23 deletions modeltranslation/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@
from django.contrib.admin.utils import get_model_from_relation
from django.core.exceptions import FieldDoesNotExist
from django.db import models
from django.db.models.query import ValuesIterable
from django.utils.tree import Node
from django.db.models.lookups import Lookup
from django.db.models.expressions import Col
from django.db.models.lookups import Lookup
from django.db.models.query import (
QuerySet,
ValuesIterable,
create_namedtuple_class,
)
from django.utils.tree import Node
from six import moves

from modeltranslation import settings
from modeltranslation.fields import TranslationField
from modeltranslation.utils import (
auto_populate,
build_localized_fieldname,
get_language,
auto_populate,
resolution_order,
)

Expand Down Expand Up @@ -172,7 +176,7 @@ def get_field_by_colum_name(model, col):
assert False, "No field found for column %s" % col


class MultilingualQuerySet(models.query.QuerySet):
class MultilingualQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
super(MultilingualQuerySet, self).__init__(*args, **kwargs)
self._post_init()
Expand Down Expand Up @@ -203,8 +207,6 @@ def __clone(self, **kwargs):
kwargs.setdefault('_populate', self._populate)
if hasattr(self, 'translation_fields'):
kwargs.setdefault('translation_fields', self.translation_fields)
if hasattr(self, 'fields_to_del'):
kwargs.setdefault('fields_to_del', self.fields_to_del)
if hasattr(self, 'original_fields'):
kwargs.setdefault('original_fields', self.original_fields)
cloned = super(MultilingualQuerySet, self)._clone()
Expand Down Expand Up @@ -421,7 +423,6 @@ def _values(self, *original, **kwargs):
clone = super(MultilingualQuerySet, self)._values(*list(new_fields), **kwargs)
clone.original_fields = tuple(original)
clone.translation_fields = translation_fields
clone.fields_to_del = new_fields - annotation_keys - set(original)
return clone

# This method was not present in django-linguo
Expand All @@ -437,23 +438,26 @@ def values(self, *fields, **expressions):
return clone

# This method was not present in django-linguo
def values_list(self, *fields, **kwargs):
def values_list(self, *fields, flat=False, named=False):
if not self._rewrite:
return super(MultilingualQuerySet, self).values_list(*fields, **kwargs)
flat = kwargs.pop('flat', False)
if kwargs:
raise TypeError('Unexpected keyword arguments to values_list: %s' % (list(kwargs),))
return super(MultilingualQuerySet, self).values_list(*fields, flat=flat, named=named)
if flat and named:
raise TypeError("'flat' and 'named' can't be used together.")
if flat and len(fields) > 1:
raise TypeError(
"'flat' is not valid when values_list is " "called with more than one field."
"'flat' is not valid when values_list is called with more than one field."
)
selects_all = not fields
if not fields:
# Emulate original queryset behaviour: get all fields that are not translation fields
fields = self._get_original_fields()
clone = self._values(*fields, prepare=True, selects_all=selects_all)
clone._iterable_class = (
FallbackFlatValuesListIterable if flat else FallbackValuesListIterable
FallbackNamedValuesListIterable
if named
else FallbackFlatValuesListIterable
if flat
else FallbackValuesListIterable
)
return clone

Expand All @@ -472,21 +476,31 @@ class X(object):

def __iter__(self):
instance = self.X()
for row in super(FallbackValuesIterable, self).__iter__():

fields = self.queryset.original_fields
fields += tuple(f for f in self.queryset.query.annotation_select if f not in fields)

for row in super().__iter__():
instance.__dict__.update(row)
for key in self.queryset.translation_fields:
row[key] = getattr(self.queryset.model, key).__get__(instance, None)
for key in self.queryset.fields_to_del:
del row[key]
yield row
# Restore original ordering.
yield {k: row[k] for k in fields}


class FallbackValuesListIterable(FallbackValuesIterable):
def __iter__(self):
fields = self.queryset.original_fields
fields += tuple(f for f in self.queryset.query.annotation_select if f not in fields)
for row in super(FallbackValuesListIterable, self).__iter__():
yield tuple(row[f] for f in fields)
for row in super().__iter__():
yield tuple(row.values())


class FallbackNamedValuesListIterable(FallbackValuesIterable):
def __iter__(self):
for row in super().__iter__():
names, values = row.keys(), row.values()
tuple_class = create_namedtuple_class(*names)
new = tuple.__new__
yield new(tuple_class, values)


class FallbackFlatValuesListIterable(FallbackValuesListIterable):
Expand Down
20 changes: 16 additions & 4 deletions modeltranslation/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from django.core.management import call_command
from django.core.management.base import CommandError
from django.db import IntegrityError
from django.db.models import Q, F, Count, TextField
from django.db.models import Q, F, Value, Count, TextField
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
from django.utils.translation import get_language, override, trans_real
Expand Down Expand Up @@ -2853,6 +2853,17 @@ def test_values(self):
with override('de'):
self.assertEqual(list(manager.values_list('title', flat=True)), ['de'])

# Values_list with named fields behave similarly.
# Also, it should preserve requested ordering.
(actual,) = manager.annotate(annotated=Value(True)).values_list(
'title', 'annotated', 'visits', named=True
)
expected = ('en', True, 0)
self.assertEqual(actual, expected)
self.assertEqual((actual.title, actual.annotated, actual.visits), expected)
with override('de'):
self.assertEqual(list(manager.values_list('title', 'visits', named=True)), [('de', 0)])

# One can always turn rewrite off
a = list(manager.rewrite(False).values_list('title', flat=True))
with override('de'):
Expand All @@ -2869,11 +2880,12 @@ def test_values(self):
self.assertEqual(list(manager.values('title')), [{'title': 'de'}, {'title': 'de2'}])

# When no fields are passed, list all fields in current language.
actual = list(manager.annotate(annotated=Value(True)).values())
self.assertEqual(
list(manager.values()),
actual,
[
{'id': id1, 'title': 'en', 'visits': 0, 'description': None},
{'id': id2, 'title': 'en2', 'visits': 0, 'description': None},
{'id': id1, 'title': 'en', 'visits': 0, 'description': None, 'annotated': True},
{'id': id2, 'title': 'en2', 'visits': 0, 'description': None, 'annotated': True},
],
)
# Similar for values_list
Expand Down

2 comments on commit 39bbe82

@gavinwahl
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks compatibility with Django 2.2 and 3.0 and 3.1. create_namedtuple_class is in django.db.models.query in those versions.

@last-partizan
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. Old versions aren't supported anymore. I will release fix with properly defined django version requirements.

Please sign in to comment.