mirror of
https://github.com/Hopiu/django-modeltranslation.git
synced 2026-03-16 22:10:31 +00:00
style: Apply black
This commit is contained in:
parent
6cbf0ce725
commit
25b6011a1f
22 changed files with 969 additions and 492 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ class ModeltranslationConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from modeltranslation.models import handle_translation_registrations
|
||||
|
||||
handle_translation_registrations()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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',)
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ''
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
46
runtests.py
46
runtests.py
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue