style: Apply black

This commit is contained in:
Serg Tereshchenko 2021-09-18 10:55:39 +03:00
parent 6cbf0ce725
commit 25b6011a1f
22 changed files with 969 additions and 492 deletions

View file

@ -54,9 +54,13 @@ def get_git_changeset():
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
git_log = subprocess.Popen(
'git log --pretty=format:%ct --quiet -1 HEAD', stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True, cwd=repo_dir,
universal_newlines=True)
'git log --pretty=format:%ct --quiet -1 HEAD',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
cwd=repo_dir,
universal_newlines=True,
)
timestamp = git_log.communicate()[0]
try:
timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))

View file

@ -8,8 +8,13 @@ from django import forms
from modeltranslation import settings as mt_settings
from modeltranslation.translator import translator
from modeltranslation.utils import (
get_translation_fields, build_css_class, build_localized_fieldname, get_language,
get_language_bidi, unique)
get_translation_fields,
build_css_class,
build_localized_fieldname,
get_language,
get_language_bidi,
unique,
)
from modeltranslation.widgets import ClearableWidgetWrapper
from django.contrib.contenttypes.admin import GenericTabularInline
@ -56,9 +61,7 @@ class TranslationBaseModelAdmin(BaseModelAdmin):
except AttributeError:
pass
else:
orig_formfield = self.formfield_for_dbfield(
orig_field, request, **kwargs
)
orig_formfield = self.formfield_for_dbfield(orig_field, request, **kwargs)
field.widget = deepcopy(orig_formfield.widget)
attrs = field.widget.attrs
# if any widget attrs are defined on the form they should be copied
@ -71,32 +74,33 @@ class TranslationBaseModelAdmin(BaseModelAdmin):
# field.widget = deepcopy(orig_formfield.widget)
if orig_field.name in self.both_empty_values_fields:
from modeltranslation.forms import NullableField, NullCharField
form_class = field.__class__
if issubclass(form_class, NullCharField):
# NullableField don't work with NullCharField
form_class.__bases__ = tuple(
b for b in form_class.__bases__ if b != NullCharField)
b for b in form_class.__bases__ if b != NullCharField
)
field.__class__ = type(
'Nullable%s' % form_class.__name__, (NullableField, form_class), {})
'Nullable%s' % form_class.__name__, (NullableField, form_class), {}
)
if (
(
db_field.empty_value == 'both' or
orig_field.name in self.both_empty_values_fields
) and isinstance(field.widget, (forms.TextInput, forms.Textarea))
):
db_field.empty_value == 'both' or orig_field.name in self.both_empty_values_fields
) and isinstance(field.widget, (forms.TextInput, forms.Textarea)):
field.widget = ClearableWidgetWrapper(field.widget)
css_classes = field.widget.attrs.get('class', '').split(' ')
css_classes.append('mt')
# Add localized fieldname css class
css_classes.append(build_css_class(db_field.name, 'mt-field'))
# Add mt-bidi css class if language is bidirectional
if(get_language_bidi(db_field.language)):
if get_language_bidi(db_field.language):
css_classes.append('mt-bidi')
if db_field.language == mt_settings.DEFAULT_LANGUAGE:
# Add another css class to identify a default modeltranslation widget
css_classes.append('mt-default')
if (orig_formfield.required or self._orig_was_required.get(
'%s.%s' % (orig_field.model._meta, orig_field.name))):
if orig_formfield.required or self._orig_was_required.get(
'%s.%s' % (orig_field.model._meta, orig_field.name)
):
# In case the original form field was required, make the
# default translation field required instead.
orig_formfield.required = False
@ -146,11 +150,12 @@ class TranslationBaseModelAdmin(BaseModelAdmin):
for opt in option:
if opt in self.trans_opts.fields:
index = option_new.index(opt)
option_new[index:index + 1] = get_translation_fields(opt)
option_new[index : index + 1] = get_translation_fields(opt)
elif isinstance(opt, (tuple, list)) and (
[o for o in opt if o in self.trans_opts.fields]):
[o for o in opt if o in self.trans_opts.fields]
):
index = option_new.index(opt)
option_new[index:index + 1] = self.replace_orig_field(opt)
option_new[index : index + 1] = self.replace_orig_field(opt)
option = option_new
return option
@ -166,10 +171,12 @@ class TranslationBaseModelAdmin(BaseModelAdmin):
def _patch_prepopulated_fields(self):
def localize(sources, lang):
"Append lang suffix (if applicable) to field list"
def append_lang(source):
if source in self.trans_opts.fields:
return build_localized_fieldname(source, lang)
return source
return tuple(map(append_lang, sources))
prepopulated_fields = {}
@ -263,8 +270,8 @@ class TranslationAdmin(TranslationBaseModelAdmin, admin.ModelAdmin):
index = editable_new.index(field)
display_index = display_new.index(field)
translation_fields = get_translation_fields(field)
editable_new[index:index + 1] = translation_fields
display_new[display_index:display_index + 1] = translation_fields
editable_new[index : index + 1] = translation_fields
display_new[display_index : display_index + 1] = translation_fields
self.list_editable = editable_new
self.list_display = display_new
@ -279,21 +286,32 @@ class TranslationAdmin(TranslationBaseModelAdmin, admin.ModelAdmin):
# Create a fieldset to group each translated field's localized fields
fields = sorted((f for f in self.opts.get_fields() if f.concrete))
untranslated_fields = [
f.name for f in fields if (
f.name
for f in fields
if (
# Exclude the primary key field
f is not self.opts.auto_field and
f is not self.opts.auto_field
# Exclude non-editable fields
f.editable and
and f.editable
# Exclude the translation fields
not hasattr(f, 'translated_field') and
and not hasattr(f, 'translated_field')
# Honour field arguments. We rely on the fact that the
# passed fieldsets argument is already fully filtered
# and takes options like exclude into account.
f.name in flattened_fieldsets
and f.name in flattened_fieldsets
)
]
# TODO: Allow setting a label
fieldsets = [('', {'fields': untranslated_fields},)] if untranslated_fields else []
fieldsets = (
[
(
'',
{'fields': untranslated_fields},
)
]
if untranslated_fields
else []
)
temp_fieldsets = {}
for orig_field, trans_fields in self.trans_opts.fields.items():
@ -303,13 +321,16 @@ class TranslationAdmin(TranslationBaseModelAdmin, admin.ModelAdmin):
# fieldset's label - using gettext_lazy in your model
# declaration can make that translatable.
label = self.model._meta.get_field(orig_field).verbose_name.capitalize()
temp_fieldsets[orig_field] = (label, {
'fields': trans_fieldnames,
'classes': ('mt-fieldset',)
})
temp_fieldsets[orig_field] = (
label,
{'fields': trans_fieldnames, 'classes': ('mt-fieldset',)},
)
fields_order = unique(f.translated_field.name for f in self.opts.fields if
hasattr(f, 'translated_field') and f.name in flattened_fieldsets)
fields_order = unique(
f.translated_field.name
for f in self.opts.fields
if hasattr(f, 'translated_field') and f.name in flattened_fieldsets
)
for field_name in fields_order:
fieldsets.append(temp_fieldsets.pop(field_name))
assert not temp_fieldsets # cleaned
@ -323,7 +344,9 @@ class TranslationAdmin(TranslationBaseModelAdmin, admin.ModelAdmin):
def get_fieldsets(self, request, obj=None):
return self._get_fieldsets_pre_form_or_formset(request, obj) or self._group_fieldsets(
self._get_fieldsets_post_form_or_formset(
request, self.get_form(request, obj, fields=None), obj))
request, self.get_form(request, obj, fields=None), obj
)
)
class TranslationInlineModelAdmin(TranslationBaseModelAdmin, InlineModelAdmin):
@ -363,6 +386,7 @@ class TabbedDjangoJqueryTranslationAdmin(TranslationAdmin):
Convenience class which includes the necessary media files for tabbed
translation fields. Reuses Django's internal jquery version.
"""
class Media:
js = (
'admin/js/jquery.init.js',
@ -380,6 +404,7 @@ class TabbedExternalJqueryTranslationAdmin(TranslationAdmin):
Convenience class which includes the necessary media files for tabbed
translation fields. Loads recent jquery version from a cdn.
"""
class Media:
js = (
mt_settings.JQUERY_URL,

View file

@ -8,4 +8,5 @@ class ModeltranslationConfig(AppConfig):
def ready(self):
from modeltranslation.models import handle_translation_registrations
handle_translation_registrations()

View file

@ -7,7 +7,11 @@ from django.db.models import fields
from modeltranslation import settings as mt_settings
from modeltranslation.utils import (
get_language, build_localized_fieldname, build_localized_verbose_name, resolution_order)
get_language,
build_localized_fieldname,
build_localized_verbose_name,
resolution_order,
)
from modeltranslation.widgets import ClearableWidgetWrapper
@ -43,6 +47,7 @@ class NONE:
given as a fallback or undefined value) or to mark that a nullable value
is not yet known and needs to be computed (e.g. field default).
"""
pass
@ -64,8 +69,7 @@ def create_translation_field(model, field_name, lang, empty_value):
field = model._meta.get_field(field_name)
cls_name = field.__class__.__name__
if not (isinstance(field, SUPPORTED_FIELDS) or cls_name in mt_settings.CUSTOM_FIELDS):
raise ImproperlyConfigured(
'%s is not supported by modeltranslation.' % cls_name)
raise ImproperlyConfigured('%s is not supported by modeltranslation.' % cls_name)
translation_class = field_factory(field.__class__)
return translation_class(translated_field=field, language=lang, empty_value=empty_value)
@ -98,6 +102,7 @@ class TranslationField(object):
The translation field needs to know which language it contains therefore
that needs to be specified when the field is created.
"""
def __init__(self, translated_field, language, empty_value, *args, **kwargs):
from modeltranslation.translator import translator
@ -155,6 +160,7 @@ class TranslationField(object):
# ForeignKey support - rewrite related_name
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.
# self.related doesn't need to be copied, as it will be recreated in
@ -163,7 +169,8 @@ class TranslationField(object):
if self.rel.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(), self.language
)
self.related_query_name = lambda: loc_related_query_name
self.rel.related_name = build_localized_fieldname(current, self.language)
self.rel.field = self # Django 1.6
@ -171,6 +178,7 @@ class TranslationField(object):
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)
@ -178,7 +186,8 @@ class TranslationField(object):
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(), 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
@ -192,8 +201,9 @@ class TranslationField(object):
# http://docs.python.org/2.7/reference/datamodel.html#object.__hash__
def __eq__(self, other):
if isinstance(other, fields.Field):
return (self.creation_counter == other.creation_counter and
self.language == getattr(other, 'language', None))
return self.creation_counter == other.creation_counter and self.language == getattr(
other, 'language', None
)
return super(TranslationField, self).__eq__(other)
def __ne__(self, other):
@ -229,15 +239,19 @@ class TranslationField(object):
if isinstance(formfield, forms.CharField):
if self.empty_value is None:
from modeltranslation.forms import NullCharField
form_class = formfield.__class__
kwargs['form_class'] = type(
'Null%s' % form_class.__name__, (NullCharField, form_class), {})
'Null%s' % form_class.__name__, (NullCharField, form_class), {}
)
formfield = super(TranslationField, self).formfield(*args, **kwargs)
elif self.empty_value == 'both':
from modeltranslation.forms import NullableField
form_class = formfield.__class__
kwargs['form_class'] = type(
'Nullable%s' % form_class.__name__, (NullableField, form_class), {})
'Nullable%s' % form_class.__name__, (NullableField, form_class), {}
)
formfield = super(TranslationField, self).formfield(*args, **kwargs)
if isinstance(formfield.widget, (forms.TextInput, forms.Textarea)):
formfield.widget = ClearableWidgetWrapper(formfield.widget)
@ -270,6 +284,7 @@ class TranslationField(object):
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)
@ -280,12 +295,15 @@ class TranslationField(object):
"""
# We'll just introspect the _actual_ field.
from south.modelsinspector import introspector
try:
# Check if the field provides its own 'field_class':
field_class = self.translated_field.south_field_triple()[0]
except AttributeError:
field_class = '%s.%s' % (self.translated_field.__class__.__module__,
self.translated_field.__class__.__name__)
field_class = '%s.%s' % (
self.translated_field.__class__.__module__,
self.translated_field.__class__.__name__,
)
args, kwargs = introspector(self)
# That's our definition!
return (field_class, args, kwargs)
@ -295,8 +313,10 @@ class TranslationFieldDescriptor(object):
"""
A descriptor used for the original translated field.
"""
def __init__(self, field, fallback_languages=None, fallback_value=NONE,
fallback_undefined=NONE):
def __init__(
self, field, fallback_languages=None, fallback_value=NONE, fallback_undefined=NONE
):
"""
Stores fallback options and the original field, so we know it's name
and default.
@ -328,7 +348,8 @@ class TranslationFieldDescriptor(object):
"""
if isinstance(val, fields.files.FieldFile):
return val.name and not (
isinstance(undefined, fields.files.FieldFile) and val == undefined)
isinstance(undefined, fields.files.FieldFile) and val == undefined
)
return val is not None and val != undefined
def __get__(self, instance, owner):
@ -359,8 +380,9 @@ class TranslationFieldDescriptor(object):
# instance of attr_class, but rather None or ''.
# Normally this case is handled in the descriptor, but since we have overridden it, we
# must mock it up.
if (isinstance(self.field, fields.files.FileField) and
not isinstance(default, self.field.attr_class)):
if isinstance(self.field, fields.files.FileField) and not isinstance(
default, self.field.attr_class
):
return self.field.attr_class(instance, self.field, default)
return default
@ -370,6 +392,7 @@ class TranslatedRelationIdDescriptor(object):
A descriptor used for the original '_id' attribute of a translated
ForeignKey field.
"""
def __init__(self, field_name, fallback_languages):
self.field_name = field_name # The name of the original field (excluding '_id')
self.fallback_languages = fallback_languages
@ -401,6 +424,7 @@ class LanguageCacheSingleObjectDescriptor(object):
"""
A Mixin for RelatedObjectDescriptors which use current language in cache lookups.
"""
accessor = None # needs to be set on instance
@property

View file

@ -17,6 +17,7 @@ class NullCharField(forms.CharField):
"""
CharField subclass that returns ``None`` when ``CharField`` would return empty string.
"""
def to_python(self, value):
if value in validators.EMPTY_VALUES:
return None
@ -28,6 +29,7 @@ class NullableField(forms.Field):
Form field mixin that ensures that ``None`` is not cast to anything (like
the empty string with ``CharField`` and its derivatives).
"""
def to_python(self, value):
if value is None:
return value

View file

@ -8,7 +8,7 @@ from modeltranslation.utils import auto_populate
ALLOWED = (None, False, 'all', 'default', 'required')
ALLOWED_FOR_PRINT = ', '.join(str(i) for i in (0, ) + ALLOWED[1:]) # For pretty-printing
ALLOWED_FOR_PRINT = ', '.join(str(i) for i in (0,) + ALLOWED[1:]) # For pretty-printing
def check_mode(option, opt_str, value, parser, namespace=None):
@ -22,13 +22,23 @@ def check_mode(option, opt_str, value, parser, namespace=None):
class Command(LoadDataCommand):
leave_locale_alone = mt_settings.LOADDATA_RETAIN_LOCALE # Django 1.6
help = ('Using this option will cause fixtures to be loaded under auto-population MODE.' +
'Allowed values are: %s' % ALLOWED_FOR_PRINT)
help = (
'Using this option will cause fixtures to be loaded under auto-population MODE.'
+ 'Allowed values are: %s' % ALLOWED_FOR_PRINT
)
if VERSION < (1, 8):
from optparse import make_option
option_list = LoadDataCommand.option_list + (
make_option('--populate', action='callback', callback=check_mode, type='string',
dest='populate', metavar='MODE', help=help),
make_option(
'--populate',
action='callback',
callback=check_mode,
type='string',
dest='populate',
metavar='MODE',
help=help,
),
)
else:
import argparse
@ -39,18 +49,26 @@ class Command(LoadDataCommand):
def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument('--populate', action=self.CheckAction, type=str, dest='populate',
metavar='MODE', help=self.help)
parser.add_argument(
'--populate',
action=self.CheckAction,
type=str,
dest='populate',
metavar='MODE',
help=self.help,
)
def __init__(self):
super(Command, self).__init__()
if mt_settings.LOADDATA_RETAIN_LOCALE and VERSION < (1, 6):
from django.utils import translation
self.locale = translation.get_language()
def handle(self, *fixture_labels, **options):
if hasattr(self, 'locale'):
from django.utils import translation
translation.activate(self.locale)
mode = options.get('populate')

View file

@ -41,25 +41,41 @@ def ask_for_confirmation(sql_sentences, model_full_name, interactive):
def print_missing_langs(missing_langs, field_name, model_name):
print('Missing languages in "%s" field from "%s" model: %s' % (
field_name, model_name, ", ".join(missing_langs)))
print(
'Missing languages in "%s" field from "%s" model: %s'
% (field_name, model_name, ", ".join(missing_langs))
)
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.')
help = (
'Detect new translatable fields or new available languages and'
' sync database structure. Does not remove columns of removed'
' languages or undeclared fields.'
)
if VERSION < (1, 8):
from optparse import make_option
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.'),
make_option(
'--noinput',
action='store_false',
dest='interactive',
default=True,
help='Do NOT prompt the user for input of any kind.',
),
)
else:
def add_arguments(self, parser):
parser.add_argument('--noinput', action='store_false', dest='interactive', default=True,
help='Do NOT prompt the user for input of any kind.'),
parser.add_argument(
'--noinput',
action='store_false',
dest='interactive',
default=True,
help='Do NOT prompt the user for input of any kind.',
),
def handle(self, *args, **options):
"""
@ -91,7 +107,8 @@ class Command(BaseCommand):
print_missing_langs(missing_langs, field_name, model_full_name)
sql_sentences = self.get_sync_sql(field_name, missing_langs, model)
execute_sql = ask_for_confirmation(
sql_sentences, model_full_name, self.interactive)
sql_sentences, model_full_name, self.interactive
)
if execute_sql:
print('Executing SQL...')
for sentence in sql_sentences:

View file

@ -11,23 +11,29 @@ COMMASPACE = ", "
class Command(BaseCommand):
help = ('Updates empty values of translation fields using'
' values from original fields (in all translated models).')
help = (
'Updates empty values of translation fields using'
' values from original fields (in all translated models).'
)
def add_arguments(self, parser):
parser.add_argument(
'app_label', nargs='?',
'app_label',
nargs='?',
help='App label of an application to update empty values.',
)
parser.add_argument(
'model_name', nargs='?',
'model_name',
nargs='?',
help='Model name to update empty values of only this model.',
)
parser.add_argument(
'--language',
action='store',
help=('Language translation field the be updated.'
' Default language field if not provided')
help=(
'Language translation field the be updated.'
' Default language field if not provided'
),
)
def handle(self, *args, **options):
@ -54,18 +60,19 @@ class Command(BaseCommand):
lang = options.get('language') or DEFAULT_LANGUAGE
if lang not in AVAILABLE_LANGUAGES:
raise CommandError(
"Cannot find language '%s'. Options are %s." % (
lang, COMMASPACE.join(AVAILABLE_LANGUAGES)
)
"Cannot find language '%s'. Options are %s."
% (lang, COMMASPACE.join(AVAILABLE_LANGUAGES))
)
else:
lang = lang.replace('-', '_')
if verbosity > 0:
self.stdout.write("Working on models: %s" % ', '.join([
"{app_label}.{object_name}".format(**m._meta.__dict__)
for m in models
]))
self.stdout.write(
"Working on models: %s"
% ', '.join(
["{app_label}.{object_name}".format(**m._meta.__dict__) for m in models]
)
)
for model in models:
if verbosity > 0:

View file

@ -19,8 +19,12 @@ from six import moves
from modeltranslation import settings
from modeltranslation.fields import TranslationField
from modeltranslation.utils import (build_localized_fieldname, get_language,
auto_populate, resolution_order)
from modeltranslation.utils import (
build_localized_fieldname,
get_language,
auto_populate,
resolution_order,
)
_C2F_CACHE = {}
_F2TM_CACHE = {}
@ -28,6 +32,7 @@ _F2TM_CACHE = {}
def get_translatable_fields_for_model(model):
from modeltranslation.translator import NotRegistered, translator
try:
return translator.get_options_for_model(model).get_field_names()
except NotRegistered:
@ -67,6 +72,7 @@ def append_fallback(model, fields):
fields = set(fields)
trans = set()
from modeltranslation.translator import translator
opts = translator.get_options_for_model(model)
for key, _ in opts.fields.items():
if key in fields:
@ -81,6 +87,7 @@ def append_translated(model, fields):
"If translated field is encountered, add also all its translation fields."
fields = set(fields)
from modeltranslation.translator import translator
opts = translator.get_options_for_model(model)
for key, translated in opts.fields.items():
if key in fields:
@ -264,8 +271,9 @@ class MultilingualQuerySet(models.query.QuerySet):
self._rewrite_where(child)
def _rewrite_order(self):
self.query.order_by = [rewrite_order_lookup_key(self.model, field_name)
for field_name in self.query.order_by]
self.query.order_by = [
rewrite_order_lookup_key(self.model, field_name) for field_name in self.query.order_by
]
def _rewrite_select_related(self):
if isinstance(self.query.select_related, dict):
@ -311,17 +319,23 @@ class MultilingualQuerySet(models.query.QuerySet):
return args, kwargs
if VERSION >= (3, 2):
def _filter_or_exclude(self, negate, args, kwargs):
args, kwargs = self._rewrite_filter_or_exclude(args, kwargs)
return super(MultilingualQuerySet, self)._filter_or_exclude(negate, args, kwargs)
else:
def _filter_or_exclude(self, negate, *args, **kwargs):
args, kwargs = self._rewrite_filter_or_exclude(args, kwargs)
return super(MultilingualQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
def _get_original_fields(self):
source = (self.model._meta.concrete_fields if hasattr(self.model._meta, 'concrete_fields')
else self.model._meta.fields)
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):
@ -356,6 +370,7 @@ class MultilingualQuerySet(models.query.QuerySet):
del kwargs[key]
kwargs[new_key] = self._rewrite_f(val)
return super(MultilingualQuerySet, self).update(**kwargs)
update.alters_data = True
# This method was not present in django-linguo
@ -425,14 +440,16 @@ class MultilingualQuerySet(models.query.QuerySet):
if kwargs:
raise TypeError('Unexpected keyword arguments to values_list: %s' % (list(kwargs),))
if flat and len(fields) > 1:
raise TypeError("'flat' is not valid when values_list is "
"called with more than one field.")
raise TypeError(
"'flat' is not valid when values_list is " "called with more than one field."
)
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)
clone._iterable_class = (FallbackFlatValuesListIterable if flat
else FallbackValuesListIterable)
clone._iterable_class = (
FallbackFlatValuesListIterable if flat else FallbackValuesListIterable
)
return clone
# This method was not present in django-linguo
@ -477,8 +494,10 @@ def multilingual_queryset_factory(old_cls, instantiate=True):
if old_cls == models.query.QuerySet:
NewClass = MultilingualQuerySet
else:
class NewClass(old_cls, MultilingualQuerySet):
pass
NewClass.__name__ = 'Multilingual%s' % old_cls.__name__
return NewClass() if instantiate else NewClass
@ -488,6 +507,7 @@ class MultilingualQuerysetManager(models.Manager):
This class gets hooked in MRO just before plain Manager, so that every call to
get_queryset returns MultilingualQuerySet.
"""
def get_queryset(self):
qs = super(MultilingualQuerysetManager, self).get_queryset()
return self._patch_queryset(qs)
@ -500,7 +520,6 @@ class MultilingualQuerysetManager(models.Manager):
class MultilingualManager(MultilingualQuerysetManager):
def rewrite(self, *args, **kwargs):
return self.get_queryset().rewrite(*args, **kwargs)

View file

@ -16,6 +16,7 @@ def autodiscover():
from importlib import import_module
from django.apps import apps
mods = [(app_config.name, app_config.module) for app_config in apps.get_app_configs()]
for (app, mod) in mods:
@ -47,8 +48,10 @@ def autodiscover():
if sys.argv[1] in ('runserver', 'runserver_plus'):
models = translator.get_registered_models()
names = ', '.join(m.__name__ for m in models)
print('modeltranslation: Registered %d models for translation'
' (%s) [pid: %d].' % (len(models), names, os.getpid()))
print(
'modeltranslation: Registered %d models for translation'
' (%s) [pid: %d].' % (len(models), names, os.getpid())
)
except IndexError:
pass

View file

@ -13,9 +13,7 @@ AVAILABLE_LANGUAGES = list(
)
DEFAULT_LANGUAGE = getattr(settings, "MODELTRANSLATION_DEFAULT_LANGUAGE", None)
if DEFAULT_LANGUAGE and DEFAULT_LANGUAGE not in AVAILABLE_LANGUAGES:
raise ImproperlyConfigured(
"MODELTRANSLATION_DEFAULT_LANGUAGE not in LANGUAGES setting."
)
raise ImproperlyConfigured("MODELTRANSLATION_DEFAULT_LANGUAGE not in LANGUAGES setting.")
elif not DEFAULT_LANGUAGE:
DEFAULT_LANGUAGE = AVAILABLE_LANGUAGES[0]
@ -23,17 +21,13 @@ elif not DEFAULT_LANGUAGE:
# (If not set, the current request language will be used)
PREPOPULATE_LANGUAGE = getattr(settings, "MODELTRANSLATION_PREPOPULATE_LANGUAGE", None)
if PREPOPULATE_LANGUAGE and PREPOPULATE_LANGUAGE not in AVAILABLE_LANGUAGES:
raise ImproperlyConfigured(
"MODELTRANSLATION_PREPOPULATE_LANGUAGE not in LANGUAGES setting."
)
raise ImproperlyConfigured("MODELTRANSLATION_PREPOPULATE_LANGUAGE not in LANGUAGES setting.")
# Load allowed CUSTOM_FIELDS from django settings
CUSTOM_FIELDS = getattr(settings, "MODELTRANSLATION_CUSTOM_FIELDS", ())
# Don't change this setting unless you really know what you are doing
ENABLE_REGISTRATIONS = getattr(
settings, "MODELTRANSLATION_ENABLE_REGISTRATIONS", settings.USE_I18N
)
ENABLE_REGISTRATIONS = getattr(settings, "MODELTRANSLATION_ENABLE_REGISTRATIONS", settings.USE_I18N)
# Modeltranslation specific debug setting
DEBUG = getattr(settings, "MODELTRANSLATION_DEBUG", False)
@ -44,9 +38,7 @@ AUTO_POPULATE = getattr(settings, "MODELTRANSLATION_AUTO_POPULATE", False)
# MODELTRANSLATION_FALLBACK_LANGUAGES = ('en', 'de')
# MODELTRANSLATION_FALLBACK_LANGUAGES = {'default': ('en', 'de'), 'fr': ('de',)}
# By default we fallback to the default language
FALLBACK_LANGUAGES = getattr(
settings, "MODELTRANSLATION_FALLBACK_LANGUAGES", (DEFAULT_LANGUAGE,)
)
FALLBACK_LANGUAGES = getattr(settings, "MODELTRANSLATION_FALLBACK_LANGUAGES", (DEFAULT_LANGUAGE,))
if isinstance(FALLBACK_LANGUAGES, (tuple, list)):
FALLBACK_LANGUAGES = {"default": tuple(FALLBACK_LANGUAGES)}
if "default" not in FALLBACK_LANGUAGES:
@ -60,20 +52,16 @@ for key, value in FALLBACK_LANGUAGES.items():
)
if not isinstance(value, (tuple, list)):
raise ImproperlyConfigured(
'MODELTRANSLATION_FALLBACK_LANGUAGES: value for key "%s" is not list nor tuple.'
% key
'MODELTRANSLATION_FALLBACK_LANGUAGES: value for key "%s" is not list nor tuple.' % key
)
for lang in value:
if lang not in AVAILABLE_LANGUAGES:
raise ImproperlyConfigured(
'MODELTRANSLATION_FALLBACK_LANGUAGES: "%s" not in LANGUAGES setting.'
% lang
'MODELTRANSLATION_FALLBACK_LANGUAGES: "%s" not in LANGUAGES setting.' % lang
)
ENABLE_FALLBACKS = getattr(settings, "MODELTRANSLATION_ENABLE_FALLBACKS", True)
LOADDATA_RETAIN_LOCALE = getattr(
settings, "MODELTRANSLATION_LOADDATA_RETAIN_LOCALE", True
)
LOADDATA_RETAIN_LOCALE = getattr(settings, "MODELTRANSLATION_LOADDATA_RETAIN_LOCALE", True)
JQUERY_URL = getattr(
settings,

View file

@ -21,6 +21,7 @@ class UniqueNullableModel(models.Model):
# ######### Proxy model testing
class ProxyTestModel(TestModel):
class Meta:
proxy = True
@ -31,6 +32,7 @@ class ProxyTestModel(TestModel):
# ######### Fallback values testing
class FallbackModel(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
text = models.TextField(blank=True, null=True)
@ -48,6 +50,7 @@ class FallbackModel2(models.Model):
# ######### File fields testing
class FileFieldsModel(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
file = models.FileField(upload_to='modeltranslation_tests', null=True, blank=True)
@ -57,6 +60,7 @@ class FileFieldsModel(models.Model):
# ######### Foreign Key / OneToOneField testing
class NonTranslated(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
@ -64,39 +68,63 @@ class NonTranslated(models.Model):
class ForeignKeyModel(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
test = models.ForeignKey(
TestModel, null=True, related_name="test_fks", on_delete=models.CASCADE,
TestModel,
null=True,
related_name="test_fks",
on_delete=models.CASCADE,
)
optional = models.ForeignKey(TestModel, blank=True, null=True, on_delete=models.CASCADE)
hidden = models.ForeignKey(
TestModel, blank=True, null=True, related_name="+", on_delete=models.CASCADE,
TestModel,
blank=True,
null=True,
related_name="+",
on_delete=models.CASCADE,
)
non = models.ForeignKey(
NonTranslated, blank=True, null=True, related_name="test_fks", on_delete=models.CASCADE,
NonTranslated,
blank=True,
null=True,
related_name="test_fks",
on_delete=models.CASCADE,
)
untrans = models.ForeignKey(
TestModel, blank=True, null=True, related_name="test_fks_un", on_delete=models.CASCADE,
TestModel,
blank=True,
null=True,
related_name="test_fks_un",
on_delete=models.CASCADE,
)
class OneToOneFieldModel(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
test = models.OneToOneField(
TestModel, null=True, related_name="test_o2o", on_delete=models.CASCADE,
TestModel,
null=True,
related_name="test_o2o",
on_delete=models.CASCADE,
)
optional = models.OneToOneField(TestModel, blank=True, null=True, on_delete=models.CASCADE)
# No hidden option for OneToOne
non = models.OneToOneField(
NonTranslated, blank=True, null=True, related_name="test_o2o", on_delete=models.CASCADE,
NonTranslated,
blank=True,
null=True,
related_name="test_o2o",
on_delete=models.CASCADE,
)
# ######### 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)
@ -115,6 +143,7 @@ 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
@ -157,6 +186,7 @@ class DescriptorModel(models.Model):
# ######### Multitable inheritance testing
class MultitableModelA(models.Model):
titlea = models.CharField(gettext_lazy('title a'), max_length=255)
@ -175,6 +205,7 @@ class MultitableModelD(MultitableModelB):
# ######### Abstract inheritance testing
class AbstractModelA(models.Model):
titlea = models.CharField(gettext_lazy('title a'), max_length=255)
@ -196,6 +227,7 @@ class AbstractModelB(AbstractModelA):
# ######### Fields inheritance testing
class Slugged(models.Model):
slug = models.CharField(max_length=255)
@ -237,6 +269,7 @@ class RichTextPage(Page, RichText):
# ######### Admin testing
class DataModel(models.Model):
data = models.TextField(blank=True, null=True)
@ -257,6 +290,7 @@ class NameModel(models.Model):
# ######### Integration testing
class ThirdPartyModel(models.Model):
name = models.CharField(max_length=20)
@ -280,7 +314,10 @@ class FilteredTestModel(models.Model):
class ForeignKeyFilteredModel(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
test = models.ForeignKey(
FilteredTestModel, null=True, related_name="test_fks", on_delete=models.CASCADE,
FilteredTestModel,
null=True,
related_name="test_fks",
on_delete=models.CASCADE,
)
@ -298,6 +335,7 @@ class CustomManager(models.Manager):
sup = super(CustomManager, self)
queryset = sup.get_queryset() if hasattr(sup, 'get_queryset') else sup.get_query_set()
return queryset.filter(title__contains='a').exclude(description__contains='x')
get_query_set = get_queryset
def custom_qs(self):
@ -324,6 +362,7 @@ class CustomQuerySet(models.query.QuerySet):
class CustomManager2(models.Manager):
def get_queryset(self):
return CustomQuerySet(self.model, using=self._db)
get_query_set = get_queryset
@ -358,6 +397,7 @@ class PlainChildTestModel(CustomManagerBaseModel):
# ######### Required fields testing
class RequiredModel(models.Model):
non_req = models.CharField(max_length=10, blank=True)
req = models.CharField(max_length=10)
@ -367,6 +407,7 @@ class RequiredModel(models.Model):
# ######### Name collision registration testing
class ConflictModel(models.Model):
title = models.CharField(gettext_lazy('title'), max_length=255)
title_de = models.IntegerField()
@ -393,6 +434,7 @@ class MultitableConflictModelB(MultitableConflictModelA):
# ######### Complex M2M with abstract classes and custom managers
class CustomQuerySetX(models.query.QuerySet):
pass
@ -400,6 +442,7 @@ class CustomQuerySetX(models.query.QuerySet):
class CustomManagerX(models.Manager):
def get_queryset(self):
return CustomQuerySetX(self.model, using=self._db)
get_query_set = get_queryset
@ -448,6 +491,7 @@ class AbstractModelY(models.Model):
class ModelY(AbstractModelY):
pass
# Non-abstract base models whos Manager is not allowed to be overwritten

View file

@ -5,12 +5,9 @@ Settings overrided for test time
from django.conf import settings
INSTALLED_APPS = tuple(settings.INSTALLED_APPS) + (
'modeltranslation.tests',
)
INSTALLED_APPS = tuple(settings.INSTALLED_APPS) + ('modeltranslation.tests',)
LANGUAGES = (('de', 'Deutsch'),
('en', 'English'))
LANGUAGES = (('de', 'Deutsch'), ('en', 'English'))
LANGUAGE_CODE = 'de'
MODELTRANSLATION_DEFAULT_LANGUAGE = 'de'

View file

@ -4,6 +4,7 @@ from django.db import models
class News(models.Model):
class Meta:
app_label = 'test_app'
title = models.CharField(max_length=50)
visits = models.SmallIntegerField(blank=True, null=True)
@ -11,4 +12,5 @@ class News(models.Model):
class Other(models.Model):
class Meta:
app_label = 'test_app'
name = models.CharField(max_length=50)

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,12 @@ from modeltranslation.tests import models
@register(models.TestModel)
class TestTranslationOptions(TranslationOptions):
fields = ('title', 'text', 'url', 'email',)
fields = (
'title',
'text',
'url',
'email',
)
empty_values = ''
@ -19,13 +24,20 @@ class UniqueNullableTranslationOptions(TranslationOptions):
# ######### Proxy model testing
@register(models.ProxyTestModel)
class ProxyTestTranslationOptions(TranslationOptions):
fields = ('title', 'text', 'url', 'email',)
fields = (
'title',
'text',
'url',
'email',
)
# ######### Fallback values testing
@register(models.FallbackModel)
class FallbackModelTranslationOptions(TranslationOptions):
fields = ('title', 'text', 'url', 'email', 'description')
@ -34,46 +46,81 @@ class FallbackModelTranslationOptions(TranslationOptions):
@register(models.FallbackModel2)
class FallbackModel2TranslationOptions(TranslationOptions):
fields = ('title', 'text', 'url', 'email',)
fields = (
'title',
'text',
'url',
'email',
)
fallback_values = {'text': gettext_lazy('Sorry, translation is not available.')}
fallback_undefined = {'title': 'no title'}
# ######### File fields testing
@register(models.FileFieldsModel)
class FileFieldsModelTranslationOptions(TranslationOptions):
fields = ('title', 'file', 'file2', 'image',)
fields = (
'title',
'file',
'file2',
'image',
)
# ######### Foreign Key / OneToOneField testing
@register(models.ForeignKeyModel)
class ForeignKeyModelTranslationOptions(TranslationOptions):
fields = ('title', 'test', 'optional', 'hidden', 'non',)
fields = (
'title',
'test',
'optional',
'hidden',
'non',
)
@register(models.OneToOneFieldModel)
class OneToOneFieldModelTranslationOptions(TranslationOptions):
fields = ('title', 'test', 'optional', 'non',)
fields = (
'title',
'test',
'optional',
'non',
)
@register(models.FilteredTestModel)
class FilteredTestModelTranslationOptions(TranslationOptions):
fields = ('title', )
fields = ('title',)
@register(models.ForeignKeyFilteredModel)
class ForeignKeyFilteredModelTranslationOptions(TranslationOptions):
fields = ('title', )
fields = ('title',)
# ######### Custom fields testing
@register(models.OtherFieldsModel)
class OtherFieldsModelTranslationOptions(TranslationOptions):
fields = ('int', 'boolean', 'nullboolean', 'csi', 'float', 'decimal',
'ip', 'genericip', 'date', 'datetime', 'time',)
fields = (
'int',
'boolean',
'nullboolean',
'csi',
'float',
'decimal',
'ip',
'genericip',
'date',
'datetime',
'time',
)
@register(models.DescriptorModel)
@ -83,6 +130,7 @@ class DescriptorModelTranslationOptions(TranslationOptions):
# ######### Multitable inheritance testing
@register(models.MultitableModelA)
class MultitableModelATranslationOptions(TranslationOptions):
fields = ('titlea',)
@ -100,6 +148,7 @@ class MultitableModelCTranslationOptions(TranslationOptions):
# ######### Abstract inheritance testing
@register(models.AbstractModelA)
class AbstractModelATranslationOptions(TranslationOptions):
fields = ('titlea',)
@ -112,6 +161,7 @@ class AbstractModelBTranslationOptions(TranslationOptions):
# ######### Fields inheritance testing
class SluggedTranslationOptions(TranslationOptions):
fields = ('slug',)
@ -139,23 +189,27 @@ translator.register(models.RichTextPage)
# ######### Manager testing
@register(models.ManagerTestModel)
class ManagerTestModelTranslationOptions(TranslationOptions):
fields = ('title', 'visits', 'description')
@register([
models.CustomManagerTestModel,
models.CustomManager2TestModel,
models.CustomManagerChildTestModel,
models.PlainChildTestModel
])
@register(
[
models.CustomManagerTestModel,
models.CustomManager2TestModel,
models.CustomManagerChildTestModel,
models.PlainChildTestModel,
]
)
class CustomManagerTestModelTranslationOptions(TranslationOptions):
fields = ('title',)
# ######### TranslationOptions field inheritance testing
class FieldInheritanceATranslationOptions(TranslationOptions):
fields = ['titlea']
@ -172,13 +226,15 @@ class FieldInheritanceDTranslationOptions(FieldInheritanceBTranslationOptions):
fields = ('titled',)
class FieldInheritanceETranslationOptions(FieldInheritanceCTranslationOptions,
FieldInheritanceDTranslationOptions):
class FieldInheritanceETranslationOptions(
FieldInheritanceCTranslationOptions, FieldInheritanceDTranslationOptions
):
fields = ('titlee',)
# ######### Integration testing
@register(models.ThirdPartyRegisteredModel)
class ThirdPartyTranslationOptions(TranslationOptions):
fields = ('name',)
@ -186,9 +242,13 @@ class ThirdPartyTranslationOptions(TranslationOptions):
# ######### Admin testing
@register(models.GroupFieldsetsModel)
class GroupFieldsetsTranslationOptions(TranslationOptions):
fields = ('title', 'text',)
fields = (
'title',
'text',
)
@register(models.NameModel)
@ -198,17 +258,22 @@ class NameTranslationOptions(TranslationOptions):
# ######### Required fields testing
@register(models.RequiredModel)
class RequiredTranslationOptions(TranslationOptions):
fields = ('non_req', 'req', 'req_reg', 'req_en_reg')
required_languages = {
'en': ('req_reg', 'req_en_reg',),
'en': (
'req_reg',
'req_en_reg',
),
'default': ('req_reg',), # for all other languages
}
# ######### Complex M2M with abstract classes and custom managers
@register(models.ModelX)
class ModelXOptions(TranslationOptions):
fields = ('name',)

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
try:
from django.conf.urls import include, patterns, url
# Workaround for pyflakes issue #13
assert (include, patterns, url) # noqa
except ImportError: # Django 1.3 fallback
@ -10,6 +11,6 @@ from django.contrib import admin
urlpatterns = patterns(
'',
url(r'^set_language/$', 'django.views.i18n.set_language', {},
name='set_language'),
url(r'^admin/', include(admin.site.urls)),)
url(r'^set_language/$', 'django.views.i18n.set_language', {}, name='set_language'),
url(r'^admin/', include(admin.site.urls)),
)

View file

@ -10,11 +10,19 @@ from django.utils.functional import cached_property
from six import with_metaclass
from modeltranslation import settings as mt_settings
from modeltranslation.fields import (NONE, create_translation_field, TranslationFieldDescriptor,
TranslatedRelationIdDescriptor,
LanguageCacheSingleObjectDescriptor)
from modeltranslation.manager import (MultilingualManager, MultilingualQuerysetManager,
rewrite_lookup_key, append_translated)
from modeltranslation.fields import (
NONE,
create_translation_field,
TranslationFieldDescriptor,
TranslatedRelationIdDescriptor,
LanguageCacheSingleObjectDescriptor,
)
from modeltranslation.manager import (
MultilingualManager,
MultilingualQuerysetManager,
rewrite_lookup_key,
append_translated,
)
from modeltranslation.utils import build_localized_fieldname, parse_field
@ -34,6 +42,7 @@ class FieldsAggregationMetaClass(type):
"""
Metaclass to handle custom inheritance of fields between classes.
"""
def __new__(cls, name, bases, attrs):
attrs['fields'] = set(attrs.get('fields', ()))
for base in bases:
@ -63,6 +72,7 @@ class TranslationOptions(with_metaclass(FieldsAggregationMetaClass, object)):
with translated model. This model may be not translated itself.
``related_fields`` contains names of reverse lookup fields.
"""
required_languages = ()
def __init__(self, model):
@ -90,13 +100,15 @@ class TranslationOptions(with_metaclass(FieldsAggregationMetaClass, object)):
for fieldnames in self.required_languages.values():
if any(f not in self.fields for f in fieldnames):
raise ImproperlyConfigured(
'Fieldname in required_languages which is not in fields option.')
'Fieldname in required_languages which is not in fields option.'
)
def _check_languages(self, languages, extra=()):
correct = list(mt_settings.AVAILABLE_LANGUAGES) + list(extra)
if any(lang not in correct for lang in languages):
raise ImproperlyConfigured(
'Language in required_languages which is not in AVAILABLE_LANGUAGES.')
'Language in required_languages which is not in AVAILABLE_LANGUAGES.'
)
def update(self, other):
"""
@ -126,7 +138,6 @@ class TranslationOptions(with_metaclass(FieldsAggregationMetaClass, object)):
class MultilingualOptions(options.Options):
@cached_property
def base_manager(self):
manager = super(MultilingualOptions, self).base_manager
@ -147,7 +158,8 @@ def add_translation_fields(model, opts):
for lang in mt_settings.AVAILABLE_LANGUAGES:
# Create a dynamic translation field
translation_field = create_translation_field(
model=model, field_name=field_name, lang=lang, empty_value=field_empty_value)
model=model, field_name=field_name, lang=lang, empty_value=field_empty_value
)
# Construct the name for the localized field
localized_field_name = build_localized_fieldname(field_name, lang)
# Check if the model already has a field by that name
@ -158,9 +170,11 @@ def add_translation_fields(model, opts):
if hasattr(cls, '_meta') and cls.__dict__.get(localized_field_name, None):
cls_opts = translator._get_options_for_model(cls)
if not cls._meta.abstract or field_name not in cls_opts.local_fields:
raise ValueError("Error adding translation field. Model '%s' already"
" contains a field named '%s'." %
(model._meta.object_name, localized_field_name))
raise ValueError(
"Error adding translation field. Model '%s' already"
" contains a field named '%s'."
% (model._meta.object_name, localized_field_name)
)
# This approach implements the translation fields as full valid
# django model fields and therefore adds them via add_to_class
model.add_to_class(localized_field_name, translation_field)
@ -186,8 +200,10 @@ def patch_manager_class(manager):
if manager.__class__ is Manager:
manager.__class__ = MultilingualManager
else:
class NewMultilingualManager(MultilingualManager, manager.__class__,
MultilingualQuerysetManager):
class NewMultilingualManager(
MultilingualManager, manager.__class__, MultilingualQuerysetManager
):
_old_module = manager.__module__
_old_class = manager.__class__.__name__
@ -205,11 +221,15 @@ def patch_manager_class(manager):
def __eq__(self, other):
if isinstance(other, NewMultilingualManager):
return self._old_module == other._old_module and \
self._old_class == other._old_class
return (
self._old_module == other._old_module
and self._old_class == other._old_class
)
if hasattr(other, "__module__") and hasattr(other, "__class__"):
return self._old_module == other.__module__ and \
self._old_class == other.__class__.__name__
return (
self._old_module == other.__module__
and self._old_class == other.__class__.__name__
)
return False
manager.__class__ = NewMultilingualManager
@ -262,6 +282,7 @@ def patch_constructor(model):
# Old key is intentionally left in case old_init wants to play with it
kwargs.setdefault(new_key, val)
old_init(self, *args, **kwargs)
model.__init__ = new_init
@ -294,6 +315,7 @@ def patch_clean_fields(model):
old_clean_fields(self, exclude)
finally:
setattr(self, '_mt_disable', False)
model.clean_fields = new_clean_fields
@ -310,6 +332,7 @@ def patch_get_deferred_fields(model):
if hasattr(self, '_fields_were_deferred'):
sup.update(self._fields_were_deferred)
return sup
model.get_deferred_fields = new_get_deferred_fields
@ -325,13 +348,20 @@ def patch_refresh_from_db(model):
if fields is not None:
fields = append_translated(self.__class__, fields)
return old_refresh_from_db(self, using, fields)
model.refresh_from_db = new_refresh_from_db
def delete_cache_fields(model):
opts = model._meta
cached_attrs = ('_field_cache', '_field_name_cache', '_name_map', 'fields', 'concrete_fields',
'local_concrete_fields')
cached_attrs = (
'_field_cache',
'_field_name_cache',
'_name_map',
'fields',
'concrete_fields',
'local_concrete_fields',
)
for attr in cached_attrs:
try:
delattr(opts, attr)
@ -397,6 +427,7 @@ def patch_related_object_descriptor_caching(ro_descriptor):
Patch SingleRelatedObjectDescriptor or ReverseSingleRelatedObjectDescriptor to use
language-aware caching.
"""
class NewSingleObjectDescriptor(LanguageCacheSingleObjectDescriptor, ro_descriptor.__class__):
pass
@ -415,6 +446,7 @@ class Translator(object):
A Translator object encapsulates an instance of a translator. Models are
registered with the Translator using the register() method.
"""
def __init__(self):
# All seen models (model class -> ``TranslationOptions`` instance).
self._registry = {}
@ -439,15 +471,19 @@ class Translator(object):
if model in self._registry:
if self._registry[model].registered:
raise AlreadyRegistered(
'Model "%s" is already registered for translation' %
model.__name__)
'Model "%s" is already registered for translation' % model.__name__
)
else:
descendants = [d.__name__ for d in self._registry.keys()
if issubclass(d, model) and d != model]
descendants = [
d.__name__
for d in self._registry.keys()
if issubclass(d, model) and d != model
]
if descendants:
raise DescendantRegistered(
'Model "%s" cannot be registered after its subclass'
' "%s"' % (model.__name__, descendants[0]))
' "%s"' % (model.__name__, descendants[0])
)
# Find inherited fields and create options instance for the model.
opts = self._get_options_for_model(model, opts_class, **options)
@ -475,9 +511,9 @@ class Translator(object):
# Delete all fields cache for related model (parent and children)
related = (
f for f in model._meta.get_fields()
if (f.one_to_many or f.one_to_one) and
f.auto_created
f
for f in model._meta.get_fields()
if (f.one_to_many or f.one_to_one) and f.auto_created
)
for related_obj in related:
@ -511,7 +547,8 @@ class Translator(object):
field,
fallback_languages=model_fallback_languages,
fallback_value=field_fallback_value,
fallback_undefined=field_fallback_undefined)
fallback_undefined=field_fallback_undefined,
)
setattr(model, field_name, descriptor)
if isinstance(field, ForeignKey):
# We need to use a special descriptor so that
@ -530,8 +567,8 @@ class Translator(object):
if isinstance(field, OneToOneField):
# Fix translated_field caching for SingleRelatedObjectDescriptor
sro_descriptor = (
getattr(field.remote_field.model, field.remote_field.get_accessor_name())
sro_descriptor = getattr(
field.remote_field.model, field.remote_field.get_accessor_name()
)
patch_related_object_descriptor_caching(sro_descriptor)
@ -558,8 +595,8 @@ class Translator(object):
# repatching all submodels.
raise DescendantRegistered(
'You need to unregister descendant "%s" before'
' unregistering its base "%s"' %
(desc.__name__, model.__name__))
' unregistering its base "%s"' % (desc.__name__, model.__name__)
)
del self._registry[desc]
def get_registered_models(self, abstract=True):
@ -567,8 +604,11 @@ class Translator(object):
Returns a list of all registered models, or just concrete
registered models.
"""
return [model for (model, opts) in self._registry.items()
if opts.registered and (not model._meta.abstract or abstract)]
return [
model
for (model, opts) in self._registry.items()
if opts.registered and (not model._meta.abstract or abstract)
]
def _get_options_for_model(self, model, opts_class=None, **options):
"""
@ -578,8 +618,11 @@ class Translator(object):
if model not in self._registry:
# Create a new type for backwards compatibility.
opts = type("%sTranslationOptions" % model.__name__,
(opts_class or TranslationOptions,), options)(model)
opts = type(
"%sTranslationOptions" % model.__name__,
(opts_class or TranslationOptions,),
options,
)(model)
# Fields for translation may be inherited from abstract
# superclasses, so we need to look at all parents.
@ -603,8 +646,9 @@ class Translator(object):
"""
opts = self._get_options_for_model(model)
if not opts.registered and not opts.related:
raise NotRegistered('The model "%s" is not registered for '
'translation' % model.__name__)
raise NotRegistered(
'The model "%s" is not registered for ' 'translation' % model.__name__
)
return opts

View file

@ -59,7 +59,7 @@ build_localized_verbose_name = lazy(_build_localized_verbose_name, six.text_type
def _join_css_class(bits, offset):
if '-'.join(bits[-offset:]) in settings.AVAILABLE_LANGUAGES + ['en-us']:
return '%s-%s' % ('_'.join(bits[:len(bits) - offset]), '_'.join(bits[-offset:]))
return '%s-%s' % ('_'.join(bits[: len(bits) - offset]), '_'.join(bits[-offset:]))
return ''

View file

@ -22,6 +22,7 @@ class ClearableWidgetWrapper(Widget):
``None`` is assumed to be a proper choice for the empty value, but you may
pass another one to the constructor.
"""
clear_checkbox_label = _("None")
template = '<span class="clearable-input">{0} <span>{2}</span> {3}</span>'
# TODO: Label would be proper, but admin applies some hardly undoable
@ -67,15 +68,16 @@ class ClearableWidgetWrapper(Widget):
checkbox_id = self.clear_checkbox_id(checkbox_name)
checkbox_label = self.clear_checkbox_label
checkbox = self.checkbox.render(
checkbox_name,
value == self.empty_value,
attrs={'id': checkbox_id},
renderer=renderer)
return mark_safe(self.template.format(
conditional_escape(wrapped),
conditional_escape(checkbox_id),
conditional_escape(checkbox_label),
conditional_escape(checkbox)))
checkbox_name, value == self.empty_value, attrs={'id': checkbox_id}, renderer=renderer
)
return mark_safe(
self.template.format(
conditional_escape(wrapped),
conditional_escape(checkbox_id),
conditional_escape(checkbox_label),
conditional_escape(checkbox),
)
)
def value_from_datadict(self, data, files, name):
"""

View file

@ -14,28 +14,27 @@ def runtests(test_path='modeltranslation'):
# Choose database for settings
test_db = os.getenv('DB', 'sqlite')
test_db_host = os.getenv('DB_HOST', 'localhost')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:'
}
}
DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}
if test_db == 'mysql':
DATABASES['default'].update({
'ENGINE': 'django.db.backends.mysql',
'NAME': os.getenv('MYSQL_DATABASE', 'modeltranslation'),
'USER': os.getenv('MYSQL_USER', 'root'),
'PASSWORD': os.getenv('MYSQL_PASSWORD', 'password'),
'HOST': test_db_host,
})
DATABASES['default'].update(
{
'ENGINE': 'django.db.backends.mysql',
'NAME': os.getenv('MYSQL_DATABASE', 'modeltranslation'),
'USER': os.getenv('MYSQL_USER', 'root'),
'PASSWORD': os.getenv('MYSQL_PASSWORD', 'password'),
'HOST': test_db_host,
}
)
elif test_db == 'postgres':
DATABASES['default'].update({
'ENGINE': 'django.db.backends.postgresql',
'USER': os.getenv('POSTGRES_USER', 'postgres'),
'PASSWORD': os.getenv('POSTGRES_DB', 'postgres'),
'NAME': os.getenv('POSTGRES_DB', 'modeltranslation'),
'HOST': test_db_host,
})
DATABASES['default'].update(
{
'ENGINE': 'django.db.backends.postgresql',
'USER': os.getenv('POSTGRES_USER', 'postgres'),
'PASSWORD': os.getenv('POSTGRES_DB', 'postgres'),
'NAME': os.getenv('POSTGRES_DB', 'modeltranslation'),
'HOST': test_db_host,
}
)
# Configure test environment
settings.configure(
@ -46,16 +45,13 @@ def runtests(test_path='modeltranslation'):
'modeltranslation',
),
ROOT_URLCONF=None, # tests override urlconf, but it still needs to be defined
LANGUAGES=(
('en', 'English'),
),
LANGUAGES=(('en', 'English'),),
MIDDLEWARE_CLASSES=(),
)
django.setup()
warnings.simplefilter('always', DeprecationWarning)
failures = call_command(
'test', test_path, interactive=False, failfast=False, verbosity=2)
failures = call_command('test', test_path, interactive=False, failfast=False, verbosity=2)
sys.exit(bool(failures))

View file

@ -35,8 +35,7 @@ setup(
},
install_requires=['Django>=2.2', 'six'],
download_url=(
'https://github.com/deschler/django-modeltranslation/archive/%s.tar.gz'
% version
'https://github.com/deschler/django-modeltranslation/archive/%s.tar.gz' % version
),
classifiers=[
'Programming Language :: Python',