diff --git a/modeltranslation/__init__.py b/modeltranslation/__init__.py index 5de9607..8af4e3d 100644 --- a/modeltranslation/__init__.py +++ b/modeltranslation/__init__.py @@ -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)) diff --git a/modeltranslation/admin.py b/modeltranslation/admin.py index 6b6e994..0870665 100644 --- a/modeltranslation/admin.py +++ b/modeltranslation/admin.py @@ -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, diff --git a/modeltranslation/apps.py b/modeltranslation/apps.py index 7f4d49b..abd8758 100644 --- a/modeltranslation/apps.py +++ b/modeltranslation/apps.py @@ -8,4 +8,5 @@ class ModeltranslationConfig(AppConfig): def ready(self): from modeltranslation.models import handle_translation_registrations + handle_translation_registrations() diff --git a/modeltranslation/fields.py b/modeltranslation/fields.py index 2276493..12a7e6b 100644 --- a/modeltranslation/fields.py +++ b/modeltranslation/fields.py @@ -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 diff --git a/modeltranslation/forms.py b/modeltranslation/forms.py index 9e529be..efc50b9 100644 --- a/modeltranslation/forms.py +++ b/modeltranslation/forms.py @@ -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 diff --git a/modeltranslation/management/commands/loaddata.py b/modeltranslation/management/commands/loaddata.py index b936eef..0001edf 100644 --- a/modeltranslation/management/commands/loaddata.py +++ b/modeltranslation/management/commands/loaddata.py @@ -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') diff --git a/modeltranslation/management/commands/sync_translation_fields.py b/modeltranslation/management/commands/sync_translation_fields.py index 1850fe5..16ff4b8 100644 --- a/modeltranslation/management/commands/sync_translation_fields.py +++ b/modeltranslation/management/commands/sync_translation_fields.py @@ -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: diff --git a/modeltranslation/management/commands/update_translation_fields.py b/modeltranslation/management/commands/update_translation_fields.py index dbd23fa..5576f94 100644 --- a/modeltranslation/management/commands/update_translation_fields.py +++ b/modeltranslation/management/commands/update_translation_fields.py @@ -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: diff --git a/modeltranslation/manager.py b/modeltranslation/manager.py index 46fd68d..d1b7883 100644 --- a/modeltranslation/manager.py +++ b/modeltranslation/manager.py @@ -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) diff --git a/modeltranslation/models.py b/modeltranslation/models.py index bc018bb..f0c6bd8 100644 --- a/modeltranslation/models.py +++ b/modeltranslation/models.py @@ -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 diff --git a/modeltranslation/settings.py b/modeltranslation/settings.py index c02ce54..7022eaf 100644 --- a/modeltranslation/settings.py +++ b/modeltranslation/settings.py @@ -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, diff --git a/modeltranslation/tests/models.py b/modeltranslation/tests/models.py index 2ffede1..9647c2c 100644 --- a/modeltranslation/tests/models.py +++ b/modeltranslation/tests/models.py @@ -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 diff --git a/modeltranslation/tests/settings.py b/modeltranslation/tests/settings.py index 75d9803..0abbf72 100644 --- a/modeltranslation/tests/settings.py +++ b/modeltranslation/tests/settings.py @@ -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' diff --git a/modeltranslation/tests/test_app/models.py b/modeltranslation/tests/test_app/models.py index 371265e..51441ea 100644 --- a/modeltranslation/tests/test_app/models.py +++ b/modeltranslation/tests/test_app/models.py @@ -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) diff --git a/modeltranslation/tests/tests.py b/modeltranslation/tests/tests.py index 1410fe6..92f8ed9 100644 --- a/modeltranslation/tests/tests.py +++ b/modeltranslation/tests/tests.py @@ -31,8 +31,12 @@ from modeltranslation.forms import TranslationModelForm from modeltranslation.manager import MultilingualManager from modeltranslation.models import autodiscover from modeltranslation.tests.test_settings import TEST_SETTINGS -from modeltranslation.utils import (build_css_class, build_localized_fieldname, - auto_populate, fallbacks) +from modeltranslation.utils import ( + build_css_class, + build_localized_fieldname, + auto_populate, + fallbacks, +) MIGRATIONS = "django.contrib.auth" in TEST_SETTINGS['INSTALLED_APPS'] @@ -48,6 +52,7 @@ TEST_MODELS = 35 + (1 if MIGRATIONS else 0) class reload_override_settings(override_settings): """Context manager that not only override settings, but also reload modeltranslation conf.""" + def __enter__(self): super(reload_override_settings, self).__enter__() imp.reload(mt_settings) @@ -60,7 +65,8 @@ class reload_override_settings(override_settings): # In this test suite fallback language is turned off. This context manager temporarily turns it on. def default_fallback(): return reload_override_settings( - MODELTRANSLATION_FALLBACK_LANGUAGES=(mt_settings.DEFAULT_LANGUAGE,)) + MODELTRANSLATION_FALLBACK_LANGUAGES=(mt_settings.DEFAULT_LANGUAGE,) + ) def get_field_names(model): @@ -85,15 +91,13 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): @classmethod def _get_migrations_path(cls): - return os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "auth_migrations" - ) + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "auth_migrations") @classmethod def _copy_migrations(cls): # Locate the original contrib.auth migrations files import django.contrib.auth.migrations as auth_migrations + source_dir_path = os.path.dirname(auth_migrations.__file__) # Copy them to the local auth_migrations directory @@ -103,9 +107,7 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): target_path = os.path.join(target_dir_path, f) # Only migration files get copied - if os.path.isfile(source_path) and not fnmatch.fnmatch( - f, "__init__.py" - ): + if os.path.isfile(source_path) and not fnmatch.fnmatch(f, "__init__.py"): shutil.copyfile(source_path, target_path) @classmethod @@ -113,8 +115,7 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): target_dir_path = cls._get_migrations_path() for f in os.listdir(target_dir_path): target_path = os.path.join(target_dir_path, f) - if os.path.isfile(target_path) and not fnmatch.fnmatch( - f, "__init__.py"): + if os.path.isfile(target_path) and not fnmatch.fnmatch(f, "__init__.py"): os.remove(target_path) @classmethod @@ -137,6 +138,7 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): # 1. Reload translation in case USE_I18N was False from django.utils import translation as dj_trans + imp.reload(dj_trans) # 2. Reload MT because LANGUAGES likely changed. @@ -151,6 +153,7 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): if MIGRATIONS: del cls.cache.all_models['auth'] import sys + sys.modules.pop('modeltranslation.tests.models', None) sys.modules.pop('modeltranslation.tests.translation', None) if MIGRATIONS: @@ -161,6 +164,7 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): # 4. Autodiscover from modeltranslation.models import handle_translation_registrations + handle_translation_registrations() # 5. makemigrations (``migrate=False`` in case of south) @@ -168,10 +172,17 @@ class ModeltranslationTransactionTestBase(TransactionTestCase): call_command('makemigrations', 'auth', verbosity=2, interactive=False) # At this point there should not be any migrations to generate out = io.StringIO() - call_command('makemigrations', 'auth', verbosity=3, - dry_run=True, interactive=False, stdout=out) - assert "No changes detected in app 'auth'\n" == out.getvalue(), \ + call_command( + 'makemigrations', + 'auth', + verbosity=3, + dry_run=True, + interactive=False, + stdout=out, + ) + assert "No changes detected in app 'auth'\n" == out.getvalue(), ( "Unexpected auth migration:\n %s" % out.getvalue() + ) # 6. Syncdb (``migrate=False`` in case of south) call_command('migrate', verbosity=0, interactive=False, run_syncdb=True) @@ -222,19 +233,23 @@ class TestAutodiscover(ModeltranslationTestBase): super(TestAutodiscover, cls).setUpClass() from copy import copy from modeltranslation.translator import translator + cls.registry_cpy = copy(translator._registry) @classmethod def tearDownClass(cls): from modeltranslation.translator import translator + translator._registry = cls.registry_cpy super(TestAutodiscover, cls).tearDownClass() def tearDown(self): import sys + # Rollback model classes del self.cache.all_models['test_app'] from .test_app import models + imp.reload(models) # Delete translation modules from import cache sys.modules.pop('modeltranslation.tests.test_app.translation', None) @@ -243,6 +258,7 @@ class TestAutodiscover(ModeltranslationTestBase): def check_news(self): from .test_app.models import News + fields = dir(News()) self.assertIn('title', fields) self.assertIn('title_en', fields) @@ -253,6 +269,7 @@ class TestAutodiscover(ModeltranslationTestBase): def check_other(self, present=True): from .test_app.models import Other + fields = dir(Other()) self.assertIn('name', fields) if present: @@ -288,6 +305,7 @@ class TestAutodiscover(ModeltranslationTestBase): class ModeltranslationTest(ModeltranslationTestBase): """Basic tests for the modeltranslation application.""" + def test_registration(self): langs = tuple(val for val, label in django_settings.LANGUAGES) self.assertEqual(langs, tuple(mt_settings.AVAILABLE_LANGUAGES)) @@ -300,31 +318,46 @@ class ModeltranslationTest(ModeltranslationTestBase): self.assertEqual(len(translator.translator.get_registered_models()), TEST_MODELS) # Try to unregister a model that is not registered - self.assertRaises(translator.NotRegistered, - translator.translator.unregister, models.BasePage) + self.assertRaises( + translator.NotRegistered, translator.translator.unregister, models.BasePage + ) # Try to get options for a model that is not registered - self.assertRaises(translator.NotRegistered, - translator.translator.get_options_for_model, models.ThirdPartyModel) + self.assertRaises( + translator.NotRegistered, + translator.translator.get_options_for_model, + models.ThirdPartyModel, + ) # Ensure that a base can't be registered after a subclass. - self.assertRaises(translator.DescendantRegistered, - translator.translator.register, models.BasePage) + self.assertRaises( + translator.DescendantRegistered, translator.translator.register, models.BasePage + ) # Or unregistered before it. - self.assertRaises(translator.DescendantRegistered, - translator.translator.unregister, models.Slugged) + self.assertRaises( + translator.DescendantRegistered, translator.translator.unregister, models.Slugged + ) def test_registration_field_conflicts(self): before = len(translator.translator.get_registered_models()) # Exception should be raised when conflicting field name detected - self.assertRaises(ValueError, translator.translator.register, - models.ConflictModel, fields=('title',)) - self.assertRaises(ValueError, translator.translator.register, - models.AbstractConflictModelB, fields=('title',)) - self.assertRaises(ValueError, translator.translator.register, - models.MultitableConflictModelB, fields=('title',)) + self.assertRaises( + ValueError, translator.translator.register, models.ConflictModel, fields=('title',) + ) + self.assertRaises( + ValueError, + translator.translator.register, + models.AbstractConflictModelB, + fields=('title',), + ) + self.assertRaises( + ValueError, + translator.translator.register, + models.MultitableConflictModelB, + fields=('title',), + ) # Model should not be registered self.assertEqual(len(translator.translator.get_registered_models()), before) @@ -507,12 +540,13 @@ class ModeltranslationTest(ModeltranslationTestBase): trans_real.activate("en") self.assertEqual(n.title, '') # Falling back to default field value self.assertEqual( - n.text, - translation.FallbackModel2TranslationOptions.fallback_values['text']) + n.text, translation.FallbackModel2TranslationOptions.fallback_values['text'] + ) def _compare_instances(self, x, y, field): - self.assertEqual(getattr(x, field), getattr(y, field), - "Constructor diff on field %s." % field) + self.assertEqual( + getattr(x, field), getattr(y, field), "Constructor diff on field %s." % field + ) def _test_constructor(self, keywords): n = models.TestModel(**keywords) @@ -532,9 +566,12 @@ class ModeltranslationTest(ModeltranslationTestBase): # original only title='title', # both languages + original - email='q@q.qq', email_de='d@d.dd', email_en='e@e.ee', + email='q@q.qq', + email_de='d@d.dd', + email_en='e@e.ee', # both languages without original - text_en='text en', text_de='text de', + text_en='text en', + text_de='text de', ) self._test_constructor(keywords) @@ -544,9 +581,11 @@ class ModeltranslationTest(ModeltranslationTestBase): # only not current language url_en='http://www.google.com', # original + current - text='text def', text_de='text de', + text='text def', + text_de='text de', # original + not current - email='q@q.qq', email_en='e@e.ee', + email='q@q.qq', + email_en='e@e.ee', ) self._test_constructor(keywords) @@ -554,6 +593,7 @@ class ModeltranslationTest(ModeltranslationTestBase): class ModeltranslationTransactionTest(ModeltranslationTransactionTestBase): def test_unique_nullable_field(self): from django.db import transaction + models.UniqueNullableModel.objects.create() models.UniqueNullableModel.objects.create() models.UniqueNullableModel.objects.create(title=None) @@ -568,10 +608,7 @@ class ModeltranslationTransactionTest(ModeltranslationTransactionTestBase): class FallbackTests(ModeltranslationTestBase): - test_fallback = { - 'default': ('de',), - 'de': ('en',) - } + test_fallback = {'default': ('de',), 'de': ('en',)} def test_settings(self): # Initial @@ -590,6 +627,7 @@ class FallbackTests(ModeltranslationTestBase): def test_resolution_order(self): from modeltranslation.utils import resolution_order + with reload_override_settings(MODELTRANSLATION_FALLBACK_LANGUAGES=self.test_fallback): self.assertEqual(('en', 'de'), resolution_order('en')) self.assertEqual(('de', 'en'), resolution_order('de')) @@ -768,6 +806,7 @@ class FileFieldsTest(ModeltranslationTestBase): def test_empty_field(self): from django.db.models.fields.files import FieldFile + inst = models.FileFieldsModel() self.assertIsInstance(inst.file, FieldFile) self.assertIsInstance(inst.file2, FieldFile) @@ -778,6 +817,7 @@ class FileFieldsTest(ModeltranslationTestBase): def test_fallback(self): from django.db.models.fields.files import FieldFile + with reload_override_settings(MODELTRANSLATION_FALLBACK_LANGUAGES=('en',)): self.assertEqual(get_language(), 'de') inst = models.FileFieldsModel() @@ -900,14 +940,15 @@ class ForeignKeyFieldsTest(ModeltranslationTestBase): test_inst.save() # Instantiate many 'ForeignKeyModel' instances: - fk_inst_both = self.model(title_en='f_title_en', title_de='f_title_de', - test_de=test_inst, test_en=test_inst) + fk_inst_both = self.model( + title_en='f_title_en', title_de='f_title_de', test_de=test_inst, test_en=test_inst + ) fk_inst_both.save() - fk_inst_de = self.model(title_en='f_title_en', title_de='f_title_de', - test_de_id=test_inst.pk) + fk_inst_de = self.model( + title_en='f_title_en', title_de='f_title_de', test_de_id=test_inst.pk + ) fk_inst_de.save() - fk_inst_en = self.model(title_en='f_title_en', title_de='f_title_de', - test_en=test_inst) + fk_inst_en = self.model(title_en='f_title_en', title_de='f_title_de', test_en=test_inst) fk_inst_en.save() fk_option_de = self.model.objects.create(optional_de=test_inst) @@ -917,17 +958,17 @@ class ForeignKeyFieldsTest(ModeltranslationTestBase): # Explicit related_name testmodel_fields = get_field_names(models.TestModel) testmodel_methods = dir(models.TestModel) - self.assertIn('test_fks', testmodel_fields) + self.assertIn('test_fks', testmodel_fields) self.assertIn('test_fks_de', testmodel_fields) self.assertIn('test_fks_en', testmodel_fields) - self.assertIn('test_fks', testmodel_methods) + self.assertIn('test_fks', testmodel_methods) self.assertIn('test_fks_de', testmodel_methods) self.assertIn('test_fks_en', testmodel_methods) # Implicit related_name: manager descriptor name != query field name - self.assertIn('foreignkeymodel', testmodel_fields) + self.assertIn('foreignkeymodel', testmodel_fields) self.assertIn('foreignkeymodel_de', testmodel_fields) self.assertIn('foreignkeymodel_en', testmodel_fields) - self.assertIn('foreignkeymodel_set', testmodel_methods) + self.assertIn('foreignkeymodel_set', testmodel_methods) self.assertIn('foreignkeymodel_set_de', testmodel_methods) self.assertIn('foreignkeymodel_set_en', testmodel_methods) @@ -943,10 +984,10 @@ class ForeignKeyFieldsTest(ModeltranslationTestBase): # Check the default reverse accessor: trans_real.activate("de") - self.assertIn(fk_inst_de, test_inst.test_fks.all()) + self.assertIn(fk_inst_de, test_inst.test_fks.all()) self.assertNotIn(fk_inst_en, test_inst.test_fks.all()) trans_real.activate("en") - self.assertIn(fk_inst_en, test_inst.test_fks.all()) + self.assertIn(fk_inst_en, test_inst.test_fks.all()) self.assertNotIn(fk_inst_de, test_inst.test_fks.all()) # Check implicit related_name reverse accessor: @@ -1015,14 +1056,16 @@ class ForeignKeyFieldsTest(ModeltranslationTestBase): self.assertEqual(models.FilteredTestModel._meta.base_manager.__class__, MultilingualManager) # # create objects with relations to test_inst - fk_inst = models.ForeignKeyFilteredModel(test=test_inst, - title_en='f_title_en', title_de='f_title_de') + fk_inst = models.ForeignKeyFilteredModel( + test=test_inst, title_en='f_title_en', title_de='f_title_de' + ) fk_inst.save() - fk_inst.refresh_from_db() # force to reset cached values + fk_inst.refresh_from_db() # force to reset cached values self.assertEqual(models.ForeignKeyFilteredModel.objects.__class__, MultilingualManager) - self.assertEqual(models.ForeignKeyFilteredModel._meta.base_manager.__class__, - MultilingualManager) + self.assertEqual( + models.ForeignKeyFilteredModel._meta.base_manager.__class__, MultilingualManager + ) self.assertEqual(fk_inst.test, test_inst) def test_non_translated_relation(self): @@ -1030,7 +1073,8 @@ class ForeignKeyFieldsTest(ModeltranslationTestBase): non_en = models.NonTranslated.objects.create(title='title_en') fk_inst_both = self.model.objects.create( - title_en='f_title_en', title_de='f_title_de', non_de=non_de, non_en=non_en) + title_en='f_title_en', title_de='f_title_de', non_de=non_de, non_en=non_en + ) fk_inst_de = self.model.objects.create(non_de=non_de) fk_inst_en = self.model.objects.create(non_en=non_en) @@ -1111,11 +1155,11 @@ class OneToOneFieldsTest(ForeignKeyFieldsTest): test_inst.save() # Instantiate many 'OneToOneFieldModel' instances: - fk_inst_de = self.model(title_en='f_title_en', title_de='f_title_de', - test_de_id=test_inst.pk) + fk_inst_de = self.model( + title_en='f_title_en', title_de='f_title_de', test_de_id=test_inst.pk + ) fk_inst_de.save() - fk_inst_en = self.model(title_en='f_title_en', title_de='f_title_de', - test_en=test_inst) + fk_inst_en = self.model(title_en='f_title_en', title_de='f_title_de', test_en=test_inst) fk_inst_en.save() fk_option_de = self.model.objects.create(optional_de=test_inst) @@ -1125,17 +1169,17 @@ class OneToOneFieldsTest(ForeignKeyFieldsTest): # Explicit related_name testmodel_fields = get_field_names(models.TestModel) testmodel_methods = dir(models.TestModel) - self.assertIn('test_o2o', testmodel_fields) + self.assertIn('test_o2o', testmodel_fields) self.assertIn('test_o2o_de', testmodel_fields) self.assertIn('test_o2o_en', testmodel_fields) - self.assertIn('test_o2o', testmodel_methods) + self.assertIn('test_o2o', testmodel_methods) self.assertIn('test_o2o_de', testmodel_methods) self.assertIn('test_o2o_en', testmodel_methods) # Implicit related_name - self.assertIn('onetoonefieldmodel', testmodel_fields) + self.assertIn('onetoonefieldmodel', testmodel_fields) self.assertIn('onetoonefieldmodel_de', testmodel_fields) self.assertIn('onetoonefieldmodel_en', testmodel_fields) - self.assertIn('onetoonefieldmodel', testmodel_methods) + self.assertIn('onetoonefieldmodel', testmodel_methods) self.assertIn('onetoonefieldmodel_de', testmodel_methods) self.assertIn('onetoonefieldmodel_en', testmodel_methods) @@ -1203,9 +1247,11 @@ class OneToOneFieldsTest(ForeignKeyFieldsTest): non_en = models.NonTranslated.objects.create(title='title_en') fk_inst_de = self.model.objects.create( - title_en='f_title_en', title_de='f_title_de', non_de=non_de) + title_en='f_title_en', title_de='f_title_de', non_de=non_de + ) fk_inst_en = self.model.objects.create( - title_en='f_title_en2', title_de='f_title_de2', non_en=non_en) + title_en='f_title_en2', title_de='f_title_de2', non_en=non_en + ) # Forward relation + spanning manager = self.model.objects @@ -1548,11 +1594,10 @@ class OtherFieldsTest(ModeltranslationTestBase): qs = Model.objects.dates('datetime', 'year', 'DESC') - self.assertEqual(list(qs), [ - datetime.date(2015, 1, 1), - datetime.date(2014, 1, 1), - datetime.date(2013, 1, 1) - ]) + self.assertEqual( + list(qs), + [datetime.date(2015, 1, 1), datetime.date(2014, 1, 1), datetime.date(2013, 1, 1)], + ) def test_descriptors(self): # Descriptor store ints in database and returns string of 'a' of that length @@ -1600,6 +1645,7 @@ class ModeltranslationTestRule1(ModeltranslationTestBase): Rule 1: Reading the value from the original field returns the value in translated to the current language. """ + def _test_field(self, field_name, value_de, value_en, deactivate=True): field_name_de = '%s_de' % field_name field_name_en = '%s_en' % field_name @@ -1647,14 +1693,16 @@ class ModeltranslationTestRule1(ModeltranslationTestBase): self._test_field(field_name='text', value_de=text_de, value_en=text_en) def test_rule1_url_field(self): - self._test_field(field_name='url', - value_de='http://www.google.de', - value_en='http://www.google.com') + self._test_field( + field_name='url', value_de='http://www.google.de', value_en='http://www.google.com' + ) def test_rule1_email_field(self): - self._test_field(field_name='email', - value_de='django-modeltranslation@googlecode.de', - value_en='django-modeltranslation@googlecode.com') + self._test_field( + field_name='email', + value_de='django-modeltranslation@googlecode.de', + value_en='django-modeltranslation@googlecode.com', + ) class ModeltranslationTestRule2(ModeltranslationTestBase): @@ -1662,8 +1710,8 @@ class ModeltranslationTestRule2(ModeltranslationTestBase): Rule 2: Assigning a value to the original field updates the value in the associated current language translation field. """ - def _test_field(self, field_name, value1_de, value1_en, value2, value3, - deactivate=True): + + def _test_field(self, field_name, value1_de, value1_en, value2, value3, deactivate=True): field_name_de = '%s_de' % field_name field_name_en = '%s_en' % field_name params = {field_name_de: value1_de, field_name_en: value1_en} @@ -1697,25 +1745,31 @@ class ModeltranslationTestRule2(ModeltranslationTestBase): """ Basic CharField/TextField test. """ - self._test_field(field_name='title', - value1_de='title de', - value1_en='title en', - value2='Neuer Titel', - value3='new title') + self._test_field( + field_name='title', + value1_de='title de', + value1_en='title en', + value2='Neuer Titel', + value3='new title', + ) def test_rule2_url_field(self): - self._test_field(field_name='url', - value1_de='http://www.google.de', - value1_en='http://www.google.com', - value2='http://www.google.at', - value3='http://www.google.co.uk') + self._test_field( + field_name='url', + value1_de='http://www.google.de', + value1_en='http://www.google.com', + value2='http://www.google.at', + value3='http://www.google.co.uk', + ) def test_rule2_email_field(self): - self._test_field(field_name='email', - value1_de='django-modeltranslation@googlecode.de', - value1_en='django-modeltranslation@googlecode.com', - value2='django-modeltranslation@googlecode.at', - value3='django-modeltranslation@googlecode.co.uk') + self._test_field( + field_name='email', + value1_de='django-modeltranslation@googlecode.de', + value1_en='django-modeltranslation@googlecode.com', + value2='django-modeltranslation@googlecode.at', + value3='django-modeltranslation@googlecode.co.uk', + ) class ModeltranslationTestRule3(ModeltranslationTestBase): @@ -1784,6 +1838,7 @@ class ModelValidationTest(ModeltranslationTestBase): """ Tests if a translation model field validates correctly. """ + def assertRaisesValidation(self, func): try: func() @@ -1880,16 +1935,20 @@ class ModelValidationTest(ModeltranslationTestBase): self._test_model_validation( field_name='url', invalid_value='foo en', - valid_value='http://code.google.com/p/django-modeltranslation/') + valid_value='http://code.google.com/p/django-modeltranslation/', + ) def test_model_validation_email_field(self): self._test_model_validation( - field_name='email', invalid_value='foo en', - valid_value='django-modeltranslation@googlecode.com') + field_name='email', + invalid_value='foo en', + valid_value='django-modeltranslation@googlecode.com', + ) class ModelInheritanceTest(ModeltranslationTestBase): """Tests for inheritance support in modeltranslation.""" + def test_abstract_inheritance(self): field_names_b = get_field_names(models.AbstractModelB) self.assertTrue('titlea' in field_names_b) @@ -1964,16 +2023,43 @@ class ModelInheritanceTest(ModeltranslationTestBase): assertLocalFields(models.MetaData, ('keywords',)) assertLocalFields(models.RichText, ('content',)) # Local fields are inherited from abstract superclasses. - assertLocalFields(models.Displayable, ('slug', 'keywords',)) - assertLocalFields(models.Page, ('slug', 'keywords', 'title',)) + assertLocalFields( + models.Displayable, + ( + 'slug', + 'keywords', + ), + ) + assertLocalFields( + models.Page, + ( + 'slug', + 'keywords', + 'title', + ), + ) # But not from concrete superclasses. assertLocalFields(models.RichTextPage, ('content',)) # Fields inherited from concrete models are also available. assertFields(models.Slugged, ('slug',)) - assertFields(models.Page, ('slug', 'keywords', 'title',)) - assertFields(models.RichTextPage, ('slug', 'keywords', 'title', - 'content',)) + assertFields( + models.Page, + ( + 'slug', + 'keywords', + 'title', + ), + ) + assertFields( + models.RichTextPage, + ( + 'slug', + 'keywords', + 'title', + 'content', + ), + ) class ModelInheritanceFieldAggregationTest(ModeltranslationTestBase): @@ -1981,6 +2067,7 @@ class ModelInheritanceFieldAggregationTest(ModeltranslationTestBase): Tests for inheritance support with field aggregation in modeltranslation. """ + def test_field_aggregation(self): clsb = translation.FieldInheritanceCTranslationOptions self.assertTrue('titlea' in clsb.fields) @@ -2047,8 +2134,7 @@ class UpdateCommandTest(ModeltranslationTestBase): class TranslationAdminTest(ModeltranslationTestBase): def setUp(self): super(TranslationAdminTest, self).setUp() - self.test_obj = models.TestModel.objects.create( - title='Testtitle', text='Testtext') + self.test_obj = models.TestModel.objects.create(title='Testtitle', text='Testtext') self.site = AdminSite() def tearDown(self): @@ -2062,8 +2148,17 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) self.assertEqual( tuple(ma.get_form(request).base_fields.keys()), - ('title_de', 'title_en', 'text_de', 'text_en', 'url_de', 'url_en', - 'email_de', 'email_en')) + ( + 'title_de', + 'title_en', + 'text_de', + 'text_en', + 'url_de', + 'url_en', + 'email_de', + 'email_en', + ), + ) def test_default_fieldsets(self): class TestModelAdmin(admin.TranslationAdmin): @@ -2072,13 +2167,18 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) # We expect that the original field is excluded and only the # translation fields are included in fields - fields = ['title_de', 'title_en', 'text_de', 'text_en', - 'url_de', 'url_en', 'email_de', 'email_en'] - self.assertEqual( - ma.get_fieldsets(request), [(None, {'fields': fields})]) - self.assertEqual( - ma.get_fieldsets(request, self.test_obj), - [(None, {'fields': fields})]) + fields = [ + 'title_de', + 'title_en', + 'text_de', + 'text_en', + 'url_de', + 'url_en', + 'email_de', + 'email_en', + ] + self.assertEqual(ma.get_fieldsets(request), [(None, {'fields': fields})]) + self.assertEqual(ma.get_fieldsets(request, self.test_obj), [(None, {'fields': fields})]) def test_field_arguments(self): class TestModelAdmin(admin.TranslationAdmin): @@ -2088,7 +2188,8 @@ class TranslationAdminTest(ModeltranslationTestBase): fields = ['title_de', 'title_en'] self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) def test_field_arguments_restricted_on_form(self): # Using `fields`. @@ -2099,7 +2200,8 @@ class TranslationAdminTest(ModeltranslationTestBase): fields = ['title_de', 'title_en'] self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # Using `fieldsets`. class TestModelAdmin(admin.TranslationAdmin): @@ -2108,7 +2210,8 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # Using `exclude`. class TestModelAdmin(admin.TranslationAdmin): @@ -2116,18 +2219,17 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) fields = ['title_de', 'title_en', 'text_de', 'text_en'] - self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) # You can also pass a tuple to `exclude`. class TestModelAdmin(admin.TranslationAdmin): exclude = ('url', 'email') ma = TestModelAdmin(models.TestModel, self.site) + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # Using `fields` and `exclude`. class TestModelAdmin(admin.TranslationAdmin): @@ -2135,8 +2237,7 @@ class TranslationAdminTest(ModeltranslationTestBase): exclude = ['url'] ma = TestModelAdmin(models.TestModel, self.site) - self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), ('title_de', 'title_en')) + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), ('title_de', 'title_en')) # Using `fields` and `readonly_fields`. class TestModelAdmin(admin.TranslationAdmin): @@ -2144,8 +2245,7 @@ class TranslationAdminTest(ModeltranslationTestBase): readonly_fields = ['url'] ma = TestModelAdmin(models.TestModel, self.site) - self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), ('title_de', 'title_en')) + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), ('title_de', 'title_en')) # Using `readonly_fields`. # Note: readonly fields are not included in the form. @@ -2155,17 +2255,22 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) self.assertEqual( tuple(ma.get_form(request).base_fields.keys()), - ('text_de', 'text_en', 'url_de', 'url_en', 'email_de', 'email_en')) + ('text_de', 'text_en', 'url_de', 'url_en', 'email_de', 'email_en'), + ) # Using grouped fields. # Note: Current implementation flattens the nested fields. class TestModelAdmin(admin.TranslationAdmin): - fields = (('title', 'url'), 'email',) + fields = ( + ('title', 'url'), + 'email', + ) ma = TestModelAdmin(models.TestModel, self.site) self.assertEqual( tuple(ma.get_form(request).base_fields.keys()), - ('title_de', 'title_en', 'url_de', 'url_en', 'email_de', 'email_en')) + ('title_de', 'title_en', 'url_de', 'url_en', 'email_de', 'email_en'), + ) # Using grouped fields in `fieldsets`. class TestModelAdmin(admin.TranslationAdmin): @@ -2175,7 +2280,8 @@ class TranslationAdminTest(ModeltranslationTestBase): fields = ['email_de', 'email_en', 'title_de', 'title_en', 'url_de', 'url_en'] self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) def test_field_arguments_restricted_on_custom_form(self): # Using `fields`. @@ -2189,10 +2295,10 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) fields = ['url_de', 'url_en', 'email_de', 'email_en'] + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # Using `exclude`. class TestModelForm(forms.ModelForm): @@ -2205,10 +2311,10 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) fields = ['title_de', 'title_en', 'text_de', 'text_en'] + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # If both, the custom form an the ModelAdmin define an `exclude` # option, the ModelAdmin wins. This is Django behaviour. @@ -2217,12 +2323,11 @@ class TranslationAdminTest(ModeltranslationTestBase): exclude = ['url'] ma = TestModelAdmin(models.TestModel, self.site) - fields = ['title_de', 'title_en', 'text_de', 'text_en', 'email_de', - 'email_en'] + fields = ['title_de', 'title_en', 'text_de', 'text_en', 'email_de', 'email_en'] + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # Same for `fields`. class TestModelForm(forms.ModelForm): @@ -2236,16 +2341,18 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) fields = ['email_de', 'email_en'] + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) def test_model_form_widgets(self): class TestModelForm(forms.ModelForm): class Meta: model = models.TestModel - fields = ['text', ] + fields = [ + 'text', + ] widgets = { 'text': forms.Textarea(attrs={'myprop': 'myval'}), } @@ -2255,19 +2362,16 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) fields = ['text_de', 'text_en'] + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) for field in fields: - self.assertIn( - 'myprop', - ma.get_form(request).base_fields.get(field).widget.attrs.keys() - ) + self.assertIn('myprop', ma.get_form(request).base_fields.get(field).widget.attrs.keys()) self.assertIn( 'myval', - ma.get_form(request, self.test_obj).base_fields.get(field).widget.attrs.values() + ma.get_form(request, self.test_obj).base_fields.get(field).widget.attrs.values(), ) def test_widget_ordering_via_formfield_for_dbfield(self): @@ -2284,55 +2388,50 @@ class TranslationAdminTest(ModeltranslationTestBase): ma = TestModelAdmin(models.TestModel, self.site) fields = ['text_de', 'text_en'] + self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) - self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) for field in fields: - self.assertIn( - 'myprop', - ma.get_form(request).base_fields.get(field).widget.attrs.keys() - ) + self.assertIn('myprop', ma.get_form(request).base_fields.get(field).widget.attrs.keys()) self.assertIn( 'myval', - ma.get_form(request, self.test_obj).base_fields.get(field).widget.attrs.values() + ma.get_form(request, self.test_obj).base_fields.get(field).widget.attrs.values(), ) def test_inline_fieldsets(self): class DataInline(admin.TranslationStackedInline): model = models.DataModel - fieldsets = [ - ('Test', {'fields': ('data',)}) - ] + fieldsets = [('Test', {'fields': ('data',)})] class TestModelAdmin(admin.TranslationAdmin): - exclude = ('title', 'text',) + exclude = ( + 'title', + 'text', + ) inlines = [DataInline] class DataTranslationOptions(translator.TranslationOptions): fields = ('data',) - translator.translator.register(models.DataModel, - DataTranslationOptions) + translator.translator.register(models.DataModel, DataTranslationOptions) ma = TestModelAdmin(models.TestModel, self.site) fieldsets = [('Test', {'fields': ['data_de', 'data_en']})] try: - ma_fieldsets = ma.get_inline_instances( - request)[0].get_fieldsets(request) + ma_fieldsets = ma.get_inline_instances(request)[0].get_fieldsets(request) except AttributeError: # Django 1.3 fallback - ma_fieldsets = ma.inlines[0]( - models.TestModel, self.site).get_fieldsets(request) + ma_fieldsets = ma.inlines[0](models.TestModel, self.site).get_fieldsets(request) self.assertEqual(ma_fieldsets, fieldsets) try: - ma_fieldsets = ma.get_inline_instances( - request)[0].get_fieldsets(request, self.test_obj) + ma_fieldsets = ma.get_inline_instances(request)[0].get_fieldsets(request, self.test_obj) except AttributeError: # Django 1.3 fallback - ma_fieldsets = ma.inlines[0]( - models.TestModel, self.site).get_fieldsets(request, self.test_obj) + ma_fieldsets = ma.inlines[0](models.TestModel, self.site).get_fieldsets( + request, self.test_obj + ) self.assertEqual(ma_fieldsets, fieldsets) # Remove translation for DataModel @@ -2351,8 +2450,13 @@ class TranslationAdminTest(ModeltranslationTestBase): self.assertEqual(tuple(ma.list_display), tuple(list_display)) def test_build_css_class(self): - with reload_override_settings(LANGUAGES=(('de', 'German'), ('en', 'English'), - ('es-ar', 'Argentinian Spanish'),)): + with reload_override_settings( + LANGUAGES=( + ('de', 'German'), + ('en', 'English'), + ('es-ar', 'Argentinian Spanish'), + ) + ): fields = { 'foo_en': 'foo-en', 'foo_es_ar': 'foo-es_ar', @@ -2378,25 +2482,31 @@ class TranslationAdminTest(ModeltranslationTestBase): maa = MultitableModelAAdmin(models.MultitableModelA, self.site) mab = MultitableModelBAdmin(models.MultitableModelB, self.site) - self.assertEqual(tuple(maa.get_form(request).base_fields.keys()), - ('titlea_de', 'titlea_en')) - self.assertEqual(tuple(mab.get_form(request).base_fields.keys()), - ('titlea_de', 'titlea_en', 'titleb_de', 'titleb_en')) + self.assertEqual( + tuple(maa.get_form(request).base_fields.keys()), ('titlea_de', 'titlea_en') + ) + self.assertEqual( + tuple(mab.get_form(request).base_fields.keys()), + ('titlea_de', 'titlea_en', 'titleb_de', 'titleb_en'), + ) def test_group_fieldsets(self): # Declared fieldsets take precedence over group_fieldsets class GroupFieldsetsModelAdmin(admin.TranslationAdmin): fieldsets = [(None, {'fields': ['title']})] group_fieldsets = True + ma = GroupFieldsetsModelAdmin(models.GroupFieldsetsModel, self.site) fields = ['title_de', 'title_en'] self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) # Now set group_fieldsets only class GroupFieldsetsModelAdmin(admin.TranslationAdmin): group_fieldsets = True + ma = GroupFieldsetsModelAdmin(models.GroupFieldsetsModel, self.site) # Only text and title are registered for translation. We expect to get # three fieldsets. The first which gathers all untranslated field @@ -2415,6 +2525,7 @@ class TranslationAdminTest(ModeltranslationTestBase): class GroupFieldsetsModelAdmin(admin.TranslationAdmin): group_fieldsets = True exclude = ('email',) + ma = GroupFieldsetsModelAdmin(models.GroupFieldsetsModel, self.site) fieldsets = [ ('Title', {'classes': ('mt-fieldset',), 'fields': ['title_de', 'title_en']}), @@ -2427,10 +2538,11 @@ class TranslationAdminTest(ModeltranslationTestBase): class GroupFieldsetsModelAdmin(admin.TranslationAdmin): group_fieldsets = True exclude = ('text',) + ma = GroupFieldsetsModelAdmin(models.GroupFieldsetsModel, self.site) fieldsets = [ ('', {'fields': ['email']}), - ('Title', {'classes': ('mt-fieldset',), 'fields': ['title_de', 'title_en']}) + ('Title', {'classes': ('mt-fieldset',), 'fields': ['title_de', 'title_en']}), ] self.assertEqual(ma.get_fieldsets(request), fieldsets) self.assertEqual(ma.get_fieldsets(request, self.test_obj), fieldsets) @@ -2442,33 +2554,52 @@ class TranslationAdminTest(ModeltranslationTestBase): # Non-translated slug based on translated field (using active language) class NameModelAdmin(admin.TranslationAdmin): prepopulated_fields = {'slug': ('firstname',)} + ma = NameModelAdmin(models.NameModel, self.site) self.assertEqual(ma.prepopulated_fields, {'slug': ('firstname_de',)}) # Checking multi-field class NameModelAdmin(admin.TranslationAdmin): - prepopulated_fields = {'slug': ('firstname', 'lastname',)} + prepopulated_fields = { + 'slug': ( + 'firstname', + 'lastname', + ) + } + ma = NameModelAdmin(models.NameModel, self.site) - self.assertEqual(ma.prepopulated_fields, {'slug': ('firstname_de', 'lastname_de',)}) + self.assertEqual( + ma.prepopulated_fields, + { + 'slug': ( + 'firstname_de', + 'lastname_de', + ) + }, + ) # Non-translated slug based on non-translated field (no change) class NameModelAdmin(admin.TranslationAdmin): prepopulated_fields = {'slug': ('age',)} + ma = NameModelAdmin(models.NameModel, self.site) self.assertEqual(ma.prepopulated_fields, {'slug': ('age',)}) # Translated slug based on non-translated field (all populated on the same value) class NameModelAdmin(admin.TranslationAdmin): prepopulated_fields = {'slug2': ('age',)} + ma = NameModelAdmin(models.NameModel, self.site) self.assertEqual(ma.prepopulated_fields, {'slug2_en': ('age',), 'slug2_de': ('age',)}) # Translated slug based on translated field (corresponding) class NameModelAdmin(admin.TranslationAdmin): prepopulated_fields = {'slug2': ('firstname',)} + ma = NameModelAdmin(models.NameModel, self.site) - self.assertEqual(ma.prepopulated_fields, {'slug2_en': ('firstname_en',), - 'slug2_de': ('firstname_de',)}) + self.assertEqual( + ma.prepopulated_fields, {'slug2_en': ('firstname_en',), 'slug2_de': ('firstname_de',)} + ) # Check that current active language is used trans_real.activate('en') @@ -2476,13 +2607,16 @@ class TranslationAdminTest(ModeltranslationTestBase): class NameModelAdmin(admin.TranslationAdmin): prepopulated_fields = {'slug': ('firstname',)} + ma = NameModelAdmin(models.NameModel, self.site) self.assertEqual(ma.prepopulated_fields, {'slug': ('firstname_en',)}) # Prepopulation language can be overriden by MODELTRANSLATION_PREPOPULATE_LANGUAGE with reload_override_settings(MODELTRANSLATION_PREPOPULATE_LANGUAGE='de'): + class NameModelAdmin(admin.TranslationAdmin): prepopulated_fields = {'slug': ('firstname',)} + ma = NameModelAdmin(models.NameModel, self.site) self.assertEqual(ma.prepopulated_fields, {'slug': ('firstname_de',)}) @@ -2494,7 +2628,8 @@ class TranslationAdminTest(ModeltranslationTestBase): fields = ['title_de', 'title_en'] self.assertEqual(tuple(ma.get_form(request).base_fields.keys()), tuple(fields)) self.assertEqual( - tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields)) + tuple(ma.get_form(request, self.test_obj).base_fields.keys()), tuple(fields) + ) class ThirdPartyAppIntegrationTest(ModeltranslationTestBase): @@ -2503,6 +2638,7 @@ class ThirdPartyAppIntegrationTest(ModeltranslationTestBase): definition - but in this case the model is not registered for translation and in the other case it is. """ + registered = False @classmethod @@ -2567,8 +2703,9 @@ class TestManager(ModeltranslationTestBase): # Still possible to use explicit language version self.assertEqual(1, models.ManagerTestModel.objects.filter(title_en='en').count()) - self.assertEqual(2, models.ManagerTestModel.objects.filter( - title_en__contains='en').count()) + self.assertEqual( + 2, models.ManagerTestModel.objects.filter(title_en__contains='en').count() + ) models.ManagerTestModel.objects.update(title='new') self.assertEqual(2, models.ManagerTestModel.objects.filter(title='new').count()) @@ -2580,8 +2717,9 @@ class TestManager(ModeltranslationTestBase): self.assertEqual('new', m.title_de) # Test Python3 "dictionary changed size during iteration" - self.assertEqual(1, models.ManagerTestModel.objects.filter(title='en', - title_en='en').count()) + self.assertEqual( + 1, models.ManagerTestModel.objects.filter(title='en', title_en='en').count() + ) def test_q(self): """Test if Q queries are rewritten.""" @@ -2591,16 +2729,20 @@ class TestManager(ModeltranslationTestBase): n.save() self.assertEqual('en', get_language()) - self.assertEqual(0, models.ManagerTestModel.objects.filter(Q(title='de') | - Q(pk=42)).count()) - self.assertEqual(1, models.ManagerTestModel.objects.filter(Q(title='en') | - Q(pk=42)).count()) + self.assertEqual( + 0, models.ManagerTestModel.objects.filter(Q(title='de') | Q(pk=42)).count() + ) + self.assertEqual( + 1, models.ManagerTestModel.objects.filter(Q(title='en') | Q(pk=42)).count() + ) with override('de'): - self.assertEqual(1, models.ManagerTestModel.objects.filter(Q(title='de') | - Q(pk=42)).count()) - self.assertEqual(0, models.ManagerTestModel.objects.filter(Q(title='en') | - Q(pk=42)).count()) + self.assertEqual( + 1, models.ManagerTestModel.objects.filter(Q(title='de') | Q(pk=42)).count() + ) + self.assertEqual( + 0, models.ManagerTestModel.objects.filter(Q(title='en') | Q(pk=42)).count() + ) def test_f(self): """Test if F queries are rewritten.""" @@ -2679,8 +2821,9 @@ class TestManager(ModeltranslationTestBase): # Settings are taken into account - fallback can be disabled with override_settings(MODELTRANSLATION_ENABLE_FALLBACKS=False): - self.assert_fallback(manager.values, '', 'title', expected_de='de', - transform=lambda x: x['title']) + self.assert_fallback( + manager.values, '', 'title', expected_de='de', transform=lambda x: x['title'] + ) # Test fallback values manager = models.FallbackModel.objects @@ -2702,7 +2845,7 @@ class TestManager(ModeltranslationTestBase): # Raw_values returns real database values regardless of current language self.assertEqual(raw_obj['title'], raw_obj2['title']) # Values present language-aware data, from the moment of retrieval - self.assertEqual(obj['title'], 'en') + self.assertEqual(obj['title'], 'en') self.assertEqual(obj2['title'], 'de') # Values_list behave similarly @@ -2726,35 +2869,52 @@ class TestManager(ModeltranslationTestBase): self.assertEqual(list(manager.values('title')), [{'title': 'de'}, {'title': 'de2'}]) # When no fields are passed, list all fields in current language. - self.assertEqual(list(manager.values()), [ - {'id': id1, 'title': 'en', 'visits': 0, 'description': None}, - {'id': id2, 'title': 'en2', 'visits': 0, 'description': None} - ]) + self.assertEqual( + list(manager.values()), + [ + {'id': id1, 'title': 'en', 'visits': 0, 'description': None}, + {'id': id2, 'title': 'en2', 'visits': 0, 'description': None}, + ], + ) # Similar for values_list self.assertEqual(list(manager.values_list()), [(id1, 'en', 0, None), (id2, 'en2', 0, None)]) with override('de'): - self.assertEqual(list(manager.values_list()), - [(id1, 'de', 0, None), (id2, 'de2', 0, None)]) + self.assertEqual( + list(manager.values_list()), [(id1, 'de', 0, None), (id2, 'de2', 0, None)] + ) # Raw_values self.assertEqual(list(manager.raw_values()), list(manager.rewrite(False).values())) i2.delete() - self.assertEqual(list(manager.raw_values()), [ - {'id': id1, 'title': 'en', 'title_en': 'en', 'title_de': 'de', - 'visits': 0, 'visits_en': 0, 'visits_de': 0, - 'description': None, 'description_en': None, 'description_de': None}, - ]) + self.assertEqual( + list(manager.raw_values()), + [ + { + 'id': id1, + 'title': 'en', + 'title_en': 'en', + 'title_de': 'de', + 'visits': 0, + 'visits_en': 0, + 'visits_de': 0, + 'description': None, + 'description_en': None, + 'description_de': None, + }, + ], + ) # annotation issue (#374) - self.assertEqual(list(manager.values_list('title', flat=True).annotate(Count('title'))), - ['en']) + self.assertEqual( + list(manager.values_list('title', flat=True).annotate(Count('title'))), ['en'] + ) def test_values_list_annotation(self): models.TestModel(title='foo').save() models.TestModel(title='foo').save() self.assertEqual( list(models.TestModel.objects.all().values_list('title').annotate(Count('id'))), - [('foo', 2)] + [('foo', 2)], ) def test_custom_manager(self): @@ -2781,6 +2941,7 @@ class TestManager(ModeltranslationTestBase): def test_custom_manager_custom_method_name(self): """Test if custom method also returns MultilingualQuerySet""" from modeltranslation.manager import MultilingualQuerySet + qs = models.CustomManagerTestModel.objects.custom_qs() self.assertIsInstance(qs, MultilingualQuerySet) @@ -2788,8 +2949,9 @@ class TestManager(ModeltranslationTestBase): def test_3rd_party_custom_manager(self): from django.contrib.auth.models import Group, GroupManager from modeltranslation.manager import MultilingualManager + testmodel_fields = get_field_names(Group) - self.assertIn('name', testmodel_fields) + self.assertIn('name', testmodel_fields) self.assertIn('name_de', testmodel_fields) self.assertIn('name_en', testmodel_fields) self.assertIn('name_en', testmodel_fields) @@ -2822,6 +2984,7 @@ class TestManager(ModeltranslationTestBase): def test_non_objects_manager(self): """Test if managers other than ``objects`` are patched too""" from modeltranslation.manager import MultilingualManager + manager = models.CustomManagerTestModel.another_mgr_name self.assertTrue(isinstance(manager, MultilingualManager)) @@ -2830,9 +2993,9 @@ class TestManager(ModeltranslationTestBase): manager = models.CustomManagerChildTestModel._meta.default_manager self.assertEqual('objects', manager.name) self.assertTrue(isinstance(manager, MultilingualManager)) - self.assertTrue(isinstance( - models.CustomManagerChildTestModel.translations, - MultilingualManager)) + self.assertTrue( + isinstance(models.CustomManagerChildTestModel.translations, MultilingualManager) + ) def test_default_manager_for_inherited_models(self): manager = models.PlainChildTestModel._meta.default_manager @@ -2842,6 +3005,7 @@ class TestManager(ModeltranslationTestBase): def test_custom_manager2(self): """Test if user-defined queryset is still working""" from modeltranslation.manager import MultilingualManager, MultilingualQuerySet + manager = models.CustomManager2TestModel.objects self.assertTrue(isinstance(manager, models.CustomManager2)) self.assertTrue(isinstance(manager, MultilingualManager)) @@ -3085,8 +3249,7 @@ class TestManager(ModeltranslationTestBase): self.assertNotIn('_untrans_cache', fk_qs[0].__dict__) self.assertIn('_untrans_cache', fk_qs.select_related('untrans')[0].__dict__) self.assertNotIn( - '_untrans_cache', - fk_qs.select_related('untrans').select_related(None)[0].__dict__ + '_untrans_cache', fk_qs.select_related('untrans').select_related(None)[0].__dict__ ) # untrans is nullable so not included when select_related=True self.assertNotIn('_untrans_cache', fk_qs.select_related()[0].__dict__) @@ -3101,32 +3264,53 @@ class TestManager(ModeltranslationTestBase): self.assertNotIn('untrans', fk_qs[0]._state.fields_cache) self.assertIn('untrans', fk_qs.select_related('untrans')[0]._state.fields_cache) self.assertNotIn( - 'untrans', - fk_qs.select_related('untrans').select_related(None)[0]._state.fields_cache + 'untrans', fk_qs.select_related('untrans').select_related(None)[0]._state.fields_cache ) # untrans is nullable so not included when select_related=True self.assertNotIn('untrans', fk_qs.select_related()[0]._state.fields_cache) def test_translation_fields_appending(self): from modeltranslation.manager import append_lookup_keys, append_lookup_key - self.assertEqual(set(['untrans']), append_lookup_key(models.ForeignKeyModel, 'untrans')) - self.assertEqual(set(['title', 'title_en', 'title_de']), - append_lookup_key(models.ForeignKeyModel, 'title')) - self.assertEqual(set(['test', 'test_en', 'test_de']), - append_lookup_key(models.ForeignKeyModel, 'test')) - self.assertEqual(set(['title__eq', 'title_en__eq', 'title_de__eq']), - append_lookup_key(models.ForeignKeyModel, 'title__eq')) - self.assertEqual(set(['test__smt', 'test_en__smt', 'test_de__smt']), - append_lookup_key(models.ForeignKeyModel, 'test__smt')) - big_set = set(['test__url', 'test__url_en', 'test__url_de', - 'test_en__url', 'test_en__url_en', 'test_en__url_de', - 'test_de__url', 'test_de__url_en', 'test_de__url_de']) - self.assertEqual(big_set, append_lookup_key(models.ForeignKeyModel, 'test__url')) - self.assertEqual(set(['untrans__url', 'untrans__url_en', 'untrans__url_de']), - append_lookup_key(models.ForeignKeyModel, 'untrans__url')) - self.assertEqual(big_set.union(['title', 'title_en', 'title_de']), - append_lookup_keys(models.ForeignKeyModel, ['test__url', 'title'])) + self.assertEqual(set(['untrans']), append_lookup_key(models.ForeignKeyModel, 'untrans')) + self.assertEqual( + set(['title', 'title_en', 'title_de']), + append_lookup_key(models.ForeignKeyModel, 'title'), + ) + self.assertEqual( + set(['test', 'test_en', 'test_de']), append_lookup_key(models.ForeignKeyModel, 'test') + ) + self.assertEqual( + set(['title__eq', 'title_en__eq', 'title_de__eq']), + append_lookup_key(models.ForeignKeyModel, 'title__eq'), + ) + self.assertEqual( + set(['test__smt', 'test_en__smt', 'test_de__smt']), + append_lookup_key(models.ForeignKeyModel, 'test__smt'), + ) + big_set = set( + [ + 'test__url', + 'test__url_en', + 'test__url_de', + 'test_en__url', + 'test_en__url_en', + 'test_en__url_de', + 'test_de__url', + 'test_de__url_en', + 'test_de__url_de', + ] + ) + self.assertEqual(big_set, append_lookup_key(models.ForeignKeyModel, 'test__url')) + self.assertEqual( + set(['untrans__url', 'untrans__url_en', 'untrans__url_de']), + append_lookup_key(models.ForeignKeyModel, 'untrans__url'), + ) + + self.assertEqual( + big_set.union(['title', 'title_en', 'title_de']), + append_lookup_keys(models.ForeignKeyModel, ['test__url', 'title']), + ) def test_constructor_inheritance(self): inst = models.AbstractModelB() @@ -3138,17 +3322,29 @@ class TestManager(ModeltranslationTestBase): """Check that field names are rewritten in distinct keys.""" manager = models.ManagerTestModel.objects manager.create( - title_en='title_1_en', title_de='title_1_de', - description_en='desc_1_en', description_de='desc_1_de') + title_en='title_1_en', + title_de='title_1_de', + description_en='desc_1_en', + description_de='desc_1_de', + ) manager.create( - title_en='title_1_en', title_de='title_1_de', - description_en='desc_2_en', description_de='desc_2_de') + title_en='title_1_en', + title_de='title_1_de', + description_en='desc_2_en', + description_de='desc_2_de', + ) manager.create( - title_en='title_2_en', title_de='title_2_de', - description_en='desc_1_en', description_de='desc_1_de') + title_en='title_2_en', + title_de='title_2_de', + description_en='desc_1_en', + description_de='desc_1_de', + ) manager.create( - title_en='title_2_en', title_de='title_2_de', - description_en='desc_2_en', description_de='desc_2_de') + title_en='title_2_en', + title_de='title_2_de', + description_en='desc_2_en', + description_de='desc_2_de', + ) # Without field arguments to distinct() all fields are used to determine # distinctness, therefore when only looking at a subset of fields in the @@ -3166,17 +3362,21 @@ class TestManager(ModeltranslationTestBase): # same fields in the same order if django_settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql': titles_for_en = tuple( - (m.title, m.description) for m in manager.order_by( - 'title', 'description').distinct('title')) + (m.title, m.description) + for m in manager.order_by('title', 'description').distinct('title') + ) with override('de'): titles_for_de = tuple( - (m.title, m.description) for m in manager.order_by( - 'title', 'description').distinct('title')) + (m.title, m.description) + for m in manager.order_by('title', 'description').distinct('title') + ) self.assertEqual( - titles_for_en, (('title_1_en', 'desc_1_en'), ('title_2_en', 'desc_1_en'))) + titles_for_en, (('title_1_en', 'desc_1_en'), ('title_2_en', 'desc_1_en')) + ) self.assertEqual( - titles_for_de, (('title_1_de', 'desc_1_de'), ('title_2_de', 'desc_1_de'))) + titles_for_de, (('title_1_de', 'desc_1_de'), ('title_2_de', 'desc_1_de')) + ) class TranslationModelFormTest(ModeltranslationTestBase): @@ -3187,9 +3387,23 @@ class TranslationModelFormTest(ModeltranslationTestBase): fields = '__all__' form = TestModelForm() - self.assertEqual(list(form.base_fields), - ['title', 'title_de', 'title_en', 'text', 'text_de', 'text_en', - 'url', 'url_de', 'url_en', 'email', 'email_de', 'email_en']) + self.assertEqual( + list(form.base_fields), + [ + 'title', + 'title_de', + 'title_en', + 'text', + 'text_de', + 'text_en', + 'url', + 'url_de', + 'url_en', + 'email', + 'email_de', + 'email_en', + ], + ) self.assertEqual(list(form.fields), ['title', 'text', 'url', 'email']) def test_updating_with_empty_value(self): @@ -3197,14 +3411,16 @@ class TranslationModelFormTest(ModeltranslationTestBase): Can we update the current language translation with an empty value, when the original field is excluded from the form? """ + class Form(forms.ModelForm): class Meta: model = models.TestModel exclude = ('text',) instance = models.TestModel.objects.create(text_de='something') - form = Form({'text_de': '', 'title': 'a', 'email_de': '', 'email_en': ''}, - instance=instance) + form = Form( + {'text_de': '', 'title': 'a', 'email_de': '', 'email_en': ''}, instance=instance + ) instance = form.save() self.assertEqual('de', get_language()) self.assertEqual('', instance.text_de) @@ -3291,19 +3507,21 @@ class M2MTest(ModeltranslationTestBase): class InheritedPermissionTestCase(ModeltranslationTestBase): - @skipUnless(MIGRATIONS, 'migrations/auth not available') def test_managers_failure(self): """This fails with 0.13b.""" from modeltranslation.manager import MultilingualManager from django.contrib.auth.models import Permission, User from .models import InheritedPermission + self.assertFalse( isinstance(Permission.objects, MultilingualManager), - "Permission is using MultilingualManager") + "Permission is using MultilingualManager", + ) self.assertTrue( isinstance(InheritedPermission.objects, MultilingualManager), - "InheritedPermission is not using MultilingualManager") + "InheritedPermission is not using MultilingualManager", + ) # This happens at initialization time, depending on the models # initialized. @@ -3311,6 +3529,7 @@ class InheritedPermissionTestCase(ModeltranslationTestBase): self.assertFalse( isinstance(Permission.objects, MultilingualManager), - "Permission is using MultilingualManager") + "Permission is using MultilingualManager", + ) user = User.objects.create(username='123', is_active=True) user.has_perm('test_perm') diff --git a/modeltranslation/tests/translation.py b/modeltranslation/tests/translation.py index f7ef249..6d90001 100644 --- a/modeltranslation/tests/translation.py +++ b/modeltranslation/tests/translation.py @@ -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',) diff --git a/modeltranslation/tests/urls.py b/modeltranslation/tests/urls.py index 2cd14ef..73e3848 100644 --- a/modeltranslation/tests/urls.py +++ b/modeltranslation/tests/urls.py @@ -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)), +) diff --git a/modeltranslation/translator.py b/modeltranslation/translator.py index c7cad06..9902091 100644 --- a/modeltranslation/translator.py +++ b/modeltranslation/translator.py @@ -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 diff --git a/modeltranslation/utils.py b/modeltranslation/utils.py index 94961b5..e816191 100644 --- a/modeltranslation/utils.py +++ b/modeltranslation/utils.py @@ -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 '' diff --git a/modeltranslation/widgets.py b/modeltranslation/widgets.py index d6e3e8a..80c7d4b 100644 --- a/modeltranslation/widgets.py +++ b/modeltranslation/widgets.py @@ -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 = '{0} {2} {3}' # 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): """ diff --git a/runtests.py b/runtests.py index db42899..67b5c94 100755 --- a/runtests.py +++ b/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)) diff --git a/setup.py b/setup.py index de2f331..32e6747 100755 --- a/setup.py +++ b/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',