[Python-modules-commits] [django-modeltranslation] 02/05: Imported Upstream version 0.11
Raphaël Hertzog
hertzog at moszumanska.debian.org
Mon Jun 27 21:05:59 UTC 2016
This is an automated email from the git hooks/post-receive script.
hertzog pushed a commit to branch master
in repository django-modeltranslation.
commit 407d5ec65e7f9d785bbb7c506958253388dd1797
Author: Sophie Brun <sophie at freexian.com>
Date: Mon Jun 27 11:17:31 2016 +0200
Imported Upstream version 0.11
---
.gitignore | 31 +
.travis.yml | 163 ++
AUTHORS.rst | 2 +
CHANGELOG.txt | 39 +
MANIFEST.in | 3 +
PKG-INFO | 39 +-
docs/modeltranslation/_static/.gitignore | 2 +
modeltranslation/__init__.py | 4 +-
modeltranslation/fields.py | 26 +-
modeltranslation/forms.py | 9 +-
.../management/commands/sync_translation_fields.py | 8 +-
.../commands/update_translation_fields.py | 6 +-
modeltranslation/manager.py | 208 +-
modeltranslation/tests/__init__.py | 2 +
modeltranslation/tests/fixtures/fixture.json | 10 +
modeltranslation/tests/models.py | 315 ++
modeltranslation/tests/project_translation.py | 8 +
modeltranslation/tests/settings.py | 24 +
modeltranslation/tests/test_app/__init__.py | 0
modeltranslation/tests/test_app/models.py | 14 +
modeltranslation/tests/test_app/translation.py | 8 +
modeltranslation/tests/test_settings.py | 6 +
modeltranslation/tests/tests.py | 3021 ++++++++++++++++++++
modeltranslation/tests/translation.py | 216 ++
modeltranslation/tests/urls.py | 14 +
modeltranslation/translator.py | 37 +-
runtests.py | 59 +
tox.ini | 161 ++
travis.py | 9 +
29 files changed, 4343 insertions(+), 101 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ce17a1e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+MANIFEST
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Sphinx
+_build
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..af5d6ef
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,163 @@
+language: python
+python:
+ - "2.6"
+ - "2.7"
+ - "3.2"
+ - "3.3"
+ - "3.4"
+ - "3.5"
+env:
+ - DJANGO=1.4 DB=sqlite
+ - DJANGO=1.4 DB=postgres
+ - DJANGO=1.4 DB=mysql
+ - DJANGO=1.5 DB=sqlite
+ - DJANGO=1.5 DB=postgres
+ - DJANGO=1.5 DB=mysql
+ - DJANGO=1.6 DB=sqlite
+ - DJANGO=1.6 DB=postgres
+ - DJANGO=1.6 DB=mysql
+ - DJANGO=1.7 DB=sqlite
+ - DJANGO=1.7 DB=postgres
+ - DJANGO=1.7 DB=mysql
+ - DJANGO=1.8 DB=sqlite
+ - DJANGO=1.8 DB=postgres
+ - DJANGO=1.8 DB=mysql
+ - DJANGO=1.9 DB=sqlite
+ - DJANGO=1.9 DB=postgres
+ - DJANGO=1.9 DB=mysql
+matrix:
+ exclude:
+ - python: "3.2"
+ env: DJANGO=1.4 DB=sqlite
+ - python: "3.2"
+ env: DJANGO=1.4 DB=postgres
+ - python: "3.2"
+ env: DJANGO=1.4 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.4 DB=sqlite
+ - python: "3.3"
+ env: DJANGO=1.4 DB=postgres
+ - python: "3.3"
+ env: DJANGO=1.4 DB=mysql
+ - python: "3.4"
+ env: DJANGO=1.4 DB=sqlite
+ - python: "3.4"
+ env: DJANGO=1.4 DB=postgres
+ - python: "3.4"
+ env: DJANGO=1.4 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.4 DB=sqlite
+ - python: "3.5"
+ env: DJANGO=1.4 DB=postgres
+ - python: "3.5"
+ env: DJANGO=1.4 DB=mysql
+
+ - python: "2.6"
+ env: DJANGO=1.7 DB=sqlite
+ - python: "2.6"
+ env: DJANGO=1.7 DB=postgres
+ - python: "2.6"
+ env: DJANGO=1.7 DB=mysql
+ - python: "2.6"
+ env: DJANGO=1.8 DB=sqlite
+ - python: "2.6"
+ env: DJANGO=1.8 DB=postgres
+ - python: "2.6"
+ env: DJANGO=1.8 DB=mysql
+ - python: "2.6"
+ env: DJANGO=1.9 DB=sqlite
+ - python: "2.6"
+ env: DJANGO=1.9 DB=postgres
+ - python: "2.6"
+ env: DJANGO=1.9 DB=mysql
+ - python: "3.2"
+ env: DJANGO=1.9 DB=sqlite
+ - python: "3.2"
+ env: DJANGO=1.9 DB=postgres
+ - python: "3.2"
+ env: DJANGO=1.9 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.9 DB=sqlite
+ - python: "3.3"
+ env: DJANGO=1.9 DB=postgres
+ - python: "3.3"
+ env: DJANGO=1.9 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.5 DB=sqlite
+ - python: "3.5"
+ env: DJANGO=1.5 DB=postgres
+ - python: "3.5"
+ env: DJANGO=1.5 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.6 DB=sqlite
+ - python: "3.5"
+ env: DJANGO=1.6 DB=postgres
+ - python: "3.5"
+ env: DJANGO=1.6 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.7 DB=sqlite
+ - python: "3.5"
+ env: DJANGO=1.7 DB=postgres
+ - python: "3.5"
+ env: DJANGO=1.7 DB=mysql
+
+ - python: "3.2"
+ env: DJANGO=1.5 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.5 DB=mysql
+ - python: "3.4"
+ env: DJANGO=1.5 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.5 DB=mysql
+ - python: "3.2"
+ env: DJANGO=1.6 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.6 DB=mysql
+ - python: "3.4"
+ env: DJANGO=1.6 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.6 DB=mysql
+ - python: "3.2"
+ env: DJANGO=1.7 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.7 DB=mysql
+ - python: "3.4"
+ env: DJANGO=1.7 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.7 DB=mysql
+ - python: "3.2"
+ env: DJANGO=1.8 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.8 DB=mysql
+ - python: "3.4"
+ env: DJANGO=1.8 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.8 DB=mysql
+ - python: "3.2"
+ env: DJANGO=1.9 DB=mysql
+ - python: "3.3"
+ env: DJANGO=1.9 DB=mysql
+ - python: "3.4"
+ env: DJANGO=1.9 DB=mysql
+ - python: "3.5"
+ env: DJANGO=1.9 DB=mysql
+before_install:
+ - pip install -q flake8 --use-mirrors
+ - PYFLAKES_NODOCTEST=1 flake8 modeltranslation
+before_script:
+ - mysql -e 'create database modeltranslation;'
+ - psql -c 'create database modeltranslation;' -U postgres
+install:
+ - if [[ $DB == mysql ]]; then pip install -q mysql-python --use-mirrors; fi
+ - if [[ $DB == postgres ]]; then pip install -q psycopg2 --use-mirrors; fi
+ - pip install -q Pillow --use-mirrors
+ - IDJANGO=$(./travis.py $DJANGO)
+ - pip install -q $IDJANGO
+ - pip install -e . --use-mirrors
+ - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install 'coverage<4.0.0'; fi
+ - pip install -q coveralls --use-mirrors
+script:
+ - django-admin.py --version
+ - coverage run --source=modeltranslation ./runtests.py
+after_success:
+ coveralls
diff --git a/AUTHORS.rst b/AUTHORS.rst
index f232ee9..f12e191 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -42,6 +42,8 @@ Contributors
* Lukas Lundgren
* zenoamaro
* oliphunt
+* Venelin Stoykov
+* Stratos Moros
* And many more ... (if you miss your name here, please let us know!)
.. _django-linguo: https://github.com/zmathew/django-linguo
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index a148df2..b71736e 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,3 +1,42 @@
+v0.11
+=====
+Date: 2016-01-31
+
+Released without changes.
+
+
+v0.11rc2
+========
+Date: 2015-12-15
+
+ FIXED: Custom manager in migrations.
+ (resolves issues #330, #339 and #350, thanks Jacek Tomaszewski)
+
+
+v0.11rc1
+========
+Date: 2015-12-07
+
+ ADDED: Support for Django 1.9
+ (resolves issue #349, thanks Jacek Tomaszewski)
+
+
+v0.10.2
+=======
+Date: 2015-10-27
+
+ FIXED: Proxy model inheritance for Django >=1.8
+ (resolves issues #304, thanks Stratos Moros)
+
+
+v0.10.1
+=======
+Date: 2015-09-04
+
+ FIXED: FallbackValuesListQuerySet.iterator which broke ORM datetimes
+ (resolves issue #324, thanks Venelin Stoykov)
+
+
v0.10.0
=======
Date: 2015-07-03
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..40af27f
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include *.txt *.rst
+recursive-include docs *.rst conf.py Makefile make.bat
+recursive-include modeltranslation/static *
diff --git a/PKG-INFO b/PKG-INFO
index 5ea8f46..1847281 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,24 +1,25 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.0
Name: django-modeltranslation
-Version: 0.10
+Version: 0.11
Summary: Translates Django models using a registration approach.
Home-page: https://github.com/deschler/django-modeltranslation
-Author: Dirk Eschler
-Author-email: eschler at gmail.com
+Author: Peter Eschler,
+ Dirk Eschler,
+ Jacek Tomaszewski
+Author-email: "Peter Eschler" <peschler at gmail.com>,
+ "Dirk Eschler" <eschler at gmail.com>,
+ "Jacek Tomaszewski" <jacek.tomek at gmail.com>
License: New BSD
-Download-URL: https://github.com/deschler/django-modeltranslation/archive/0.10.tar.gz
-Description: The modeltranslation application can be used to translate dynamic content of existing models to an arbitrary number of languages without having to change the original model classes. It uses a registration approach (comparable to Django's admin app) to be able to add translations to existing or new projects and is fully integrated into the Django admin backend.
+Description: The modeltranslation application can be used to translate dynamic
+ content of existing Django models to an arbitrary number of
+ languages without having to change the original model classes. It
+ uses a registration approach (comparable to Django's admin app) to
+ be able to add translations to existing or new projects and is
+ fully integrated into the Django admin backend.
+
+ The advantage of a registration approach is the ability to add
+ translations to models on a per-project basis. You can use the
+ same app in different projects, may they use translations or not,
+ and you never have to touch the original model class.
Platform: UNKNOWN
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Operating System :: OS Independent
-Classifier: Environment :: Web Environment
-Classifier: Intended Audience :: Developers
-Classifier: Framework :: Django
-Classifier: License :: OSI Approved :: BSD License
-Requires: django(>=1.4)
+Keywords: django translation i18n
diff --git a/docs/modeltranslation/_static/.gitignore b/docs/modeltranslation/_static/.gitignore
new file mode 100644
index 0000000..4397c3a
--- /dev/null
+++ b/docs/modeltranslation/_static/.gitignore
@@ -0,0 +1,2 @@
+!.gitignore
+
diff --git a/modeltranslation/__init__.py b/modeltranslation/__init__.py
index a4b04b2..98f4e18 100644
--- a/modeltranslation/__init__.py
+++ b/modeltranslation/__init__.py
@@ -3,7 +3,7 @@
Version code adopted from Django development version.
https://github.com/django/django
"""
-VERSION = (0, 10, 0, 'final', 0)
+VERSION = (0, 11, 0, 'final', 0)
default_app_config = 'modeltranslation.apps.ModeltranslationConfig'
@@ -32,7 +32,7 @@ def get_version(version=None):
sub = '.dev%s' % git_changeset
elif version[3] != 'final':
- mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
+ mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
sub = mapping[version[3]] + str(version[4])
return str(main + sub)
diff --git a/modeltranslation/fields.py b/modeltranslation/fields.py
index 9885c0d..26a98e7 100644
--- a/modeltranslation/fields.py
+++ b/modeltranslation/fields.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+from django import VERSION
from django import forms
from django.core.exceptions import ImproperlyConfigured
from django.db.models import fields
@@ -33,6 +34,8 @@ SUPPORTED_FIELDS = (
# Above implies also OneToOneField
)
+NEW_RELATED_API = VERSION >= (1, 9)
+
class NONE:
"""
@@ -150,7 +153,7 @@ class TranslationField(object):
self.verbose_name = build_localized_verbose_name(translated_field.verbose_name, language)
# ForeignKey support - rewrite related_name
- if self.rel and self.related and not self.rel.is_hidden():
+ if not NEW_RELATED_API and self.rel and self.related and not self.rel.is_hidden():
import copy
current = self.related.get_accessor_name()
self.rel = copy.copy(self.rel) # Since fields cannot share the same rel object.
@@ -166,6 +169,21 @@ class TranslationField(object):
self.rel.field = self # Django 1.6
if hasattr(self.rel.to._meta, '_related_objects_cache'):
del self.rel.to._meta._related_objects_cache
+ elif NEW_RELATED_API and self.remote_field and not self.remote_field.is_hidden():
+ import copy
+ current = self.remote_field.get_accessor_name()
+ # Since fields cannot share the same rel object:
+ self.remote_field = copy.copy(self.remote_field)
+
+ if self.remote_field.related_name is None:
+ # For implicit related_name use different query field name
+ loc_related_query_name = build_localized_fieldname(
+ self.related_query_name(), self.language)
+ self.related_query_name = lambda: loc_related_query_name
+ self.remote_field.related_name = build_localized_fieldname(current, self.language)
+ self.remote_field.field = self # Django 1.6
+ if hasattr(self.remote_field.to._meta, '_related_objects_cache'):
+ del self.remote_field.to._meta._related_objects_cache
# Django 1.5 changed definition of __hash__ for fields to be fine with hash requirements.
# It spoiled our machinery, since TranslationField has the same creation_counter as its
@@ -250,6 +268,12 @@ class TranslationField(object):
kwargs['db_column'] = self.db_column
return six.text_type(self.name), path, args, kwargs
+ def clone(self):
+ from django.utils.module_loading import import_string
+ name, path, args, kwargs = self.deconstruct()
+ cls = import_string(path)
+ return cls(*args, **kwargs)
+
def south_field_triple(self):
"""
Returns a suitable description of this field for South.
diff --git a/modeltranslation/forms.py b/modeltranslation/forms.py
index f74c2c9..9e529be 100644
--- a/modeltranslation/forms.py
+++ b/modeltranslation/forms.py
@@ -35,6 +35,13 @@ class NullableField(forms.Field):
# Django 1.6
def _has_changed(self, initial, data):
+ return self.has_changed(initial, data)
+
+ def has_changed(self, initial, data):
if (initial is None and data is not None) or (initial is not None and data is None):
return True
- return super(NullableField, self)._has_changed(initial, data)
+ obj = super(NullableField, self)
+ if hasattr(obj, 'has_changed'):
+ return obj.has_changed(initial, data)
+ else: # Django < 1.9 compat
+ return obj._has_changed(initial, data)
diff --git a/modeltranslation/management/commands/sync_translation_fields.py b/modeltranslation/management/commands/sync_translation_fields.py
index ac89948..9bd6147 100644
--- a/modeltranslation/management/commands/sync_translation_fields.py
+++ b/modeltranslation/management/commands/sync_translation_fields.py
@@ -11,7 +11,7 @@ Credits: Heavily inspired by django-transmeta's sync_transmeta_db command.
"""
from optparse import make_option
import django
-from django.core.management.base import NoArgsCommand
+from django.core.management.base import BaseCommand
from django.core.management.color import no_style
from django.db import connection, transaction
from django.utils.six import moves
@@ -46,17 +46,17 @@ def print_missing_langs(missing_langs, field_name, model_name):
field_name, model_name, ", ".join(missing_langs)))
-class Command(NoArgsCommand):
+class Command(BaseCommand):
help = ('Detect new translatable fields or new available languages and'
' sync database structure. Does not remove columns of removed'
' languages or undeclared fields.')
- option_list = NoArgsCommand.option_list + (
+ option_list = BaseCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive', default=True,
help='Do NOT prompt the user for input of any kind.'),
)
- def handle_noargs(self, **options):
+ def handle(self, *args, **options):
"""
Command execution.
"""
diff --git a/modeltranslation/management/commands/update_translation_fields.py b/modeltranslation/management/commands/update_translation_fields.py
index 76bfc68..98e1bfb 100644
--- a/modeltranslation/management/commands/update_translation_fields.py
+++ b/modeltranslation/management/commands/update_translation_fields.py
@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
from django.db.models import F, Q
-from django.core.management.base import NoArgsCommand
+from django.core.management.base import BaseCommand
from modeltranslation.settings import DEFAULT_LANGUAGE
from modeltranslation.translator import translator
from modeltranslation.utils import build_localized_fieldname
-class Command(NoArgsCommand):
+class Command(BaseCommand):
help = ('Updates empty values of default translation fields using'
' values from original fields (in all translated models).')
- def handle_noargs(self, **options):
+ def handle(self, *args, **options):
verbosity = int(options['verbosity'])
if verbosity > 0:
self.stdout.write("Using default language: %s\n" % DEFAULT_LANGUAGE)
diff --git a/modeltranslation/manager.py b/modeltranslation/manager.py
index e36df4a..7cf2b18 100644
--- a/modeltranslation/manager.py
+++ b/modeltranslation/manager.py
@@ -16,13 +16,20 @@ try:
except ImportError:
NEW_META_API = True
-from django.db.models.sql.where import Constraint
+try:
+ from django.db.models.query import ValuesQuerySet
+ from django.db.models.sql.where import Constraint
+ NEW_RELATED_API = False
+except ImportError:
+ from django.db.models.query import ValuesIterable
+ NEW_RELATED_API = True # Django 1.9
+
from django.utils.six import moves
from django.utils.tree import Node
try:
from django.db.models.lookups import Lookup
from django.db.models.sql.datastructures import Col
- NEW_LOOKUPS = True
+ NEW_LOOKUPS = True # Django 1.7, 1.8
except ImportError:
NEW_LOOKUPS = False
@@ -193,15 +200,27 @@ class MultilingualQuerySet(models.query.QuerySet):
return multilingual_queryset_factory, (self.__class__.__bases__[0],), self.__getstate__()
# This method was not present in django-linguo
- def _clone(self, klass=None, *args, **kwargs):
- if klass is not None and not issubclass(klass, MultilingualQuerySet):
- class NewClass(klass, MultilingualQuerySet):
- pass
- NewClass.__name__ = 'Multilingual%s' % klass.__name__
- klass = NewClass
- kwargs.setdefault('_rewrite', self._rewrite)
- kwargs.setdefault('_populate', self._populate)
- return super(MultilingualQuerySet, self)._clone(klass, *args, **kwargs)
+ if NEW_RELATED_API:
+ def _clone(self, klass=None, **kwargs):
+ kwargs.setdefault('_rewrite', self._rewrite)
+ 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)
+ return super(MultilingualQuerySet, self)._clone(**kwargs)
+ else:
+ def _clone(self, klass=None, *args, **kwargs):
+ if klass is not None and not issubclass(klass, MultilingualQuerySet):
+ class NewClass(klass, MultilingualQuerySet):
+ pass
+ NewClass.__name__ = 'Multilingual%s' % klass.__name__
+ klass = NewClass
+ kwargs.setdefault('_rewrite', self._rewrite)
+ kwargs.setdefault('_populate', self._populate)
+ return super(MultilingualQuerySet, self)._clone(klass, *args, **kwargs)
# This method was not present in django-linguo
def rewrite(self, mode=True):
@@ -220,7 +239,8 @@ class MultilingualQuerySet(models.query.QuerySet):
Useful when converting any QuerySet into MultilingualQuerySet.
"""
self._rewrite_where(self.query.where)
- self._rewrite_where(self.query.having)
+ if not NEW_RELATED_API:
+ self._rewrite_where(self.query.having)
self._rewrite_order()
self._rewrite_select_related()
@@ -318,7 +338,9 @@ class MultilingualQuerySet(models.query.QuerySet):
return super(MultilingualQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
def _get_original_fields(self):
- return [f.attname for f in self.model._meta.fields if not isinstance(f, TranslationField)]
+ source = (self.model._meta.concrete_fields if hasattr(self.model._meta, 'concrete_fields')
+ else self.model._meta.fields)
+ return [f.attname for f in source if not isinstance(f, TranslationField)]
def order_by(self, *field_names):
"""
@@ -380,6 +402,16 @@ class MultilingualQuerySet(models.query.QuerySet):
def raw_values(self, *fields):
return super(MultilingualQuerySet, self).values(*fields)
+ def _values(self, *original, **kwargs):
+ if not kwargs.get('prepare', False):
+ return super(MultilingualQuerySet, self)._values(*original)
+ new_fields, translation_fields = append_fallback(self.model, original)
+ clone = super(MultilingualQuerySet, self)._values(*list(new_fields))
+ clone.original_fields = tuple(original)
+ clone.translation_fields = translation_fields
+ clone.fields_to_del = new_fields - set(original)
+ return clone
+
# This method was not present in django-linguo
def values(self, *fields):
if not self._rewrite:
@@ -387,7 +419,12 @@ class MultilingualQuerySet(models.query.QuerySet):
if not fields:
# Emulate original queryset behaviour: get all fields that are not translation fields
fields = self._get_original_fields()
- return self._clone(klass=FallbackValuesQuerySet, setup=True, _fields=fields)
+ if NEW_RELATED_API:
+ clone = self._values(*fields, prepare=True)
+ clone._iterable_class = FallbackValuesIterable
+ return clone
+ else:
+ return self._clone(klass=FallbackValuesQuerySet, setup=True, _fields=fields)
# This method was not present in django-linguo
def values_list(self, *fields, **kwargs):
@@ -402,7 +439,14 @@ class MultilingualQuerySet(models.query.QuerySet):
if not fields:
# Emulate original queryset behaviour: get all fields that are not translation fields
fields = self._get_original_fields()
- return self._clone(klass=FallbackValuesListQuerySet, setup=True, flat=flat, _fields=fields)
+ if NEW_RELATED_API:
+ clone = self._values(*fields, prepare=True)
+ clone._iterable_class = (FallbackFlatValuesListIterable if flat
+ else FallbackValuesListIterable)
+ return clone
+ else:
+ return self._clone(klass=FallbackValuesListQuerySet, setup=True, flat=flat,
+ _fields=fields)
# This method was not present in django-linguo
def dates(self, field_name, *args, **kwargs):
@@ -412,63 +456,91 @@ class MultilingualQuerySet(models.query.QuerySet):
return super(MultilingualQuerySet, self).dates(new_key, *args, **kwargs)
-class FallbackValuesQuerySet(models.query.ValuesQuerySet, MultilingualQuerySet):
- def _setup_query(self):
- original = self._fields
- new_fields, self.translation_fields = append_fallback(self.model, original)
- self._fields = list(new_fields)
- self.fields_to_del = new_fields - set(original)
- super(FallbackValuesQuerySet, self)._setup_query()
-
- class X(object):
- # This stupid class is needed as object use __slots__ and has no __dict__.
- pass
+if NEW_RELATED_API:
+ class FallbackValuesIterable(ValuesIterable):
+ class X(object):
+ # This stupid class is needed as object use __slots__ and has no __dict__.
+ pass
- def iterator(self):
- instance = self.X()
- for row in super(FallbackValuesQuerySet, self).iterator():
- instance.__dict__.update(row)
- for key in self.translation_fields:
- row[key] = getattr(self.model, key).__get__(instance, None)
- for key in self.fields_to_del:
- del row[key]
- yield row
-
- def _clone(self, klass=None, setup=False, **kwargs):
- c = super(FallbackValuesQuerySet, self)._clone(klass, **kwargs)
- c.fields_to_del = self.fields_to_del
- c.translation_fields = self.translation_fields
- if setup and hasattr(c, '_setup_query'):
- c._setup_query()
- return c
-
-
-class FallbackValuesListQuerySet(FallbackValuesQuerySet):
- def iterator(self):
- fields = self.original_fields
- if hasattr(self, 'aggregate_names'):
- # Django <1.8
- fields += tuple(self.aggregate_names)
- if hasattr(self, 'annotation_names'):
- # Django >=1.8
- fields += tuple(self.annotation_names)
- for row in super(FallbackValuesListQuerySet, self).iterator():
- if self.flat and len(fields) == 1:
- yield row[fields[0]]
- else:
+ def __iter__(self):
+ instance = self.X()
+ for row in super(FallbackValuesIterable, self).__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
+
+ 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)
- def _setup_query(self):
- self.original_fields = tuple(self._fields)
- super(FallbackValuesListQuerySet, self)._setup_query()
+ class FallbackFlatValuesListIterable(FallbackValuesListIterable):
+ def __iter__(self):
+ for row in super(FallbackFlatValuesListIterable, self).__iter__():
+ yield row[0]
+
+else:
+ class FallbackValuesQuerySet(ValuesQuerySet, MultilingualQuerySet):
+ def _setup_query(self):
+ original = self._fields
+ new_fields, self.translation_fields = append_fallback(self.model, original)
+ self._fields = list(new_fields)
+ self.fields_to_del = new_fields - set(original)
+ super(FallbackValuesQuerySet, self)._setup_query()
+
+ class X(object):
+ # This stupid class is needed as object use __slots__ and has no __dict__.
+ pass
- def _clone(self, *args, **kwargs):
- clone = super(FallbackValuesListQuerySet, self)._clone(*args, **kwargs)
- clone.original_fields = self.original_fields
- if not hasattr(clone, "flat"):
- # Only assign flat if the clone didn't already get it from kwargs
- clone.flat = self.flat
- return clone
+ def iterator(self):
+ instance = self.X()
+ for row in super(FallbackValuesQuerySet, self).iterator():
+ instance.__dict__.update(row)
+ for key in self.translation_fields:
+ row[key] = getattr(self.model, key).__get__(instance, None)
+ for key in self.fields_to_del:
+ del row[key]
+ yield row
+
+ def _clone(self, klass=None, setup=False, **kwargs):
+ c = super(FallbackValuesQuerySet, self)._clone(klass, **kwargs)
+ c.fields_to_del = self.fields_to_del
+ c.translation_fields = self.translation_fields
+ if setup and hasattr(c, '_setup_query'):
+ c._setup_query()
+ return c
+
+ class FallbackValuesListQuerySet(FallbackValuesQuerySet):
+ def iterator(self):
+ fields = self.original_fields
+ if hasattr(self, 'aggregate_names'):
+ # Django <1.8
+ fields += tuple(f for f in self.aggregate_names if f not in fields)
+ if hasattr(self, 'annotation_names'):
+ # Django >=1.8
+ fields += tuple(f for f in self.annotation_names if f not in fields)
+ for row in super(FallbackValuesListQuerySet, self).iterator():
+ if self.flat and len(fields) == 1:
+ yield row[fields[0]]
+ else:
+ yield tuple(row[f] for f in fields)
+
+ def _setup_query(self):
+ self.original_fields = tuple(self._fields)
+ super(FallbackValuesListQuerySet, self)._setup_query()
+
+ def _clone(self, *args, **kwargs):
+ clone = super(FallbackValuesListQuerySet, self)._clone(*args, **kwargs)
+ clone.original_fields = self.original_fields
+ if not hasattr(clone, "flat"):
+ # Only assign flat if the clone didn't already get it from kwargs
+ clone.flat = self.flat
+ return clone
def get_queryset(obj):
diff --git a/modeltranslation/tests/__init__.py b/modeltranslation/tests/__init__.py
new file mode 100644
index 0000000..47945af
--- /dev/null
+++ b/modeltranslation/tests/__init__.py
@@ -0,0 +1,2 @@
+# For Django < 1.6 testrunner
+from .tests import * # NOQA
diff --git a/modeltranslation/tests/fixtures/fixture.json b/modeltranslation/tests/fixtures/fixture.json
new file mode 100644
index 0000000..83462f3
--- /dev/null
+++ b/modeltranslation/tests/fixtures/fixture.json
@@ -0,0 +1,10 @@
+[
+ {
+ "pk": 1,
+ "model": "tests.TestModel",
+ "fields": {
+ "title": "foo",
+ "text": "bar"
+ }
+ }
+]
diff --git a/modeltranslation/tests/models.py b/modeltranslation/tests/models.py
new file mode 100644
index 0000000..dcbd4b2
--- /dev/null
+++ b/modeltranslation/tests/models.py
@@ -0,0 +1,315 @@
+# -*- coding: utf-8 -*-
+from django.core import validators
+from django.db import models
+from django.utils import six
+from django.utils.translation import ugettext_lazy
+
+
+class TestModel(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+ text = models.TextField(blank=True, null=True)
+ url = models.URLField(blank=True, null=True)
+ email = models.EmailField(blank=True, null=True)
+
+
+class UniqueNullableModel(models.Model):
+ title = models.CharField(null=True, unique=True, max_length=255)
+
+
+# ######### Proxy model testing
+
+class ProxyTestModel(TestModel):
+ class Meta:
+ proxy = True
+
+ def get_title(self):
+ return self.title
+
+
+# ######### Fallback values testing
+
+class FallbackModel(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+ text = models.TextField(blank=True, null=True)
+ url = models.URLField(blank=True, null=True)
+ email = models.EmailField(blank=True, null=True)
+ description = models.CharField(max_length=255, null=True)
+
+
+class FallbackModel2(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+ text = models.TextField(blank=True, null=True)
+ url = models.URLField(blank=True, null=True)
+ email = models.EmailField(blank=True, null=True)
+
+
+# ######### File fields testing
+
+class FileFieldsModel(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+ file = models.FileField(upload_to='modeltranslation_tests', null=True, blank=True)
+ file2 = models.FileField(upload_to='modeltranslation_tests')
+ image = models.ImageField(upload_to='modeltranslation_tests', null=True, blank=True)
+
+
+# ######### Foreign Key / OneToOneField testing
+
+class NonTranslated(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+
+
+class ForeignKeyModel(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+ test = models.ForeignKey(TestModel, null=True, related_name="test_fks")
+ optional = models.ForeignKey(TestModel, blank=True, null=True)
+ hidden = models.ForeignKey(TestModel, blank=True, null=True, related_name="+")
+ non = models.ForeignKey(NonTranslated, blank=True, null=True, related_name="test_fks")
+ untrans = models.ForeignKey(TestModel, blank=True, null=True, related_name="test_fks_un")
+
+
+class OneToOneFieldModel(models.Model):
+ title = models.CharField(ugettext_lazy('title'), max_length=255)
+ test = models.OneToOneField(TestModel, null=True, related_name="test_o2o")
+ optional = models.OneToOneField(TestModel, blank=True, null=True)
+ # No hidden option for OneToOne
+ non = models.OneToOneField(NonTranslated, blank=True, null=True, related_name="test_o2o")
+
+
+# ######### Custom fields testing
+
+class OtherFieldsModel(models.Model):
+ """
+ This class is supposed to include other newly added fields types, so that
+ adding new supported field doesn't end in adding new test model.
+ """
+ # That's rich! PositiveIntegerField is only validated in forms, not in models.
+ int = models.PositiveIntegerField(default=42, validators=[validators.MinValueValidator(0)])
+ boolean = models.BooleanField(default=False)
+ nullboolean = models.NullBooleanField()
+ csi = models.CommaSeparatedIntegerField(max_length=255)
+ float = models.FloatField(blank=True, null=True)
+ decimal = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
+ ip = models.IPAddressField(blank=True, null=True)
+ date = models.DateField(blank=True, null=True)
+ datetime = models.DateTimeField(blank=True, null=True)
+ time = models.TimeField(blank=True, null=True)
+ genericip = models.GenericIPAddressField(blank=True, null=True)
+
+
+class FancyDescriptor(object):
+ """
+ Stupid demo descriptor, that store int in database and return string of that length on get.
+ """
+ def __init__(self, field):
+ self.field = field
+
+ def __get__(self, instance, owner):
+ length = instance.__dict__[self.field.name]
+ if length is None:
+ return ''
+ return 'a' * length
+
+ def __set__(self, obj, value):
+ if isinstance(value, six.integer_types):
+ obj.__dict__[self.field.name] = value
+ elif isinstance(value, six.string_types):
+ obj.__dict__[self.field.name] = len(value)
+ else:
+ obj.__dict__[self.field.name] = 0
+
+
+class FancyField(models.PositiveIntegerField):
+ def __init__(self, *args, **kwargs):
+ kwargs.setdefault('default', '')
+ super(FancyField, self).__init__(*args, **kwargs)
+
+ def contribute_to_class(self, cls, name):
+ super(FancyField, self).contribute_to_class(cls, name)
+ setattr(cls, self.name, FancyDescriptor(self))
+
+ def pre_save(self, model_instance, add):
+ value = super(FancyField, self).pre_save(model_instance, add)
+ # In this part value should be retrieved using descriptor and be a string
+ assert isinstance(value, six.string_types)
+ # We put an int to database
+ return len(value)
+
+
+class DescriptorModel(models.Model):
+ normal = FancyField()
+ trans = FancyField()
+
+
+# ######### Multitable inheritance testing
+
+class MultitableModelA(models.Model):
+ titlea = models.CharField(ugettext_lazy('title a'), max_length=255)
+
+
+class MultitableModelB(MultitableModelA):
+ titleb = models.CharField(ugettext_lazy('title b'), max_length=255)
+
+
+class MultitableModelC(MultitableModelB):
+ titlec = models.CharField(ugettext_lazy('title c'), max_length=255)
+
+
+class MultitableModelD(MultitableModelB):
+ titled = models.CharField(ugettext_lazy('title d'), max_length=255)
+
+
+# ######### Abstract inheritance testing
... 3851 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-modeltranslation.git
More information about the Python-modules-commits
mailing list