예제 #1
0
    def save(self, commit=True):
        if self.instance.pk is None:
            fail_message = 'created'
            new = True
        else:
            fail_message = 'changed'
            new = False
        super(TranslatableModelForm, self).save(True)
        trans_model = self.instance._meta.translations_model
        language_code = self.cleaned_data.get('language_code', get_language())
        if not new:
            trans = get_cached_translation(self.instance)
            if not trans or trans.language_code != language_code:
                try:
                    trans = get_translation(self.instance, language_code)
                except trans_model.DoesNotExist:
                    trans = trans_model()
        else:
            trans = trans_model()

        trans.language_code = language_code
        trans.master = self.instance
        trans = save_instance(self, trans, self._meta.fields, fail_message,
                              commit, construct=True)
        return combine(trans, self.Meta.model)
예제 #2
0
        def iterator(self):
            qs = self._clone()._add_language_filter()
            qs._known_related_objects = {}  # super's iterator will attempt to set them
            if qs._forced_unique_fields:
                with ForcedUniqueFields(qs._forced_unique_fields):
                    objects = list(super(TranslationQueryset, qs).iterator())

                if type(qs.query.select_related) == dict:
                    for obj in objects:
                        qs._use_related_translations(obj, qs.query.select_related)
            else:
                objects = super(TranslationQueryset, qs).iterator()

            for obj in objects:
                for name in self._hvad_switch_fields:
                    try:
                        setattr(obj.master, name, getattr(obj, name))
                    except AttributeError: # pragma: no cover
                        pass
                    else:
                        delattr(obj, name)
                obj = combine(obj, qs.shared_model)
                # use known objects from self, not qs as we cleared it earlier
                for field, rel_objs in self._known_related_objects.items():
                    if hasattr(obj, field.get_cache_name()):
                        # should not happen, but we conform to Django behavior
                        continue #pragma: no cover
                    pk = getattr(obj, field.get_attname())
                    try:
                        rel_obj = rel_objs[pk]
                    except KeyError: #pragma: no cover
                        pass
                    else:
                        setattr(obj, field.name, rel_obj)
                yield obj
예제 #3
0
        def __iter__(self):
            qs = self.queryset._clone()._add_language_filter()
            qs._iterable_class = ModelIterable
            qs._known_related_objects = {}
            if qs._forced_unique_fields:
                with ForcedUniqueFields(qs._forced_unique_fields):
                    objects = list(qs.iterator())

                if type(qs.query.select_related) == dict:
                    for obj in objects:
                        qs._use_related_translations(obj, qs.query.select_related)
            else:
                objects = qs.iterator()

            for obj in objects:
                for name in qs._hvad_switch_fields:
                    try:
                        setattr(obj.master, name, getattr(obj, name))
                    except AttributeError: # pragma: no cover
                        pass
                    else:
                        delattr(obj, name)
                obj = combine(obj, qs.shared_model)
                # use known objects from self.queryset, not qs as we cleared it earlier
                for field, rel_objs in self.queryset._known_related_objects.items():
                    if hasattr(obj, field.get_cache_name()):
                        continue # pragma: no cover (conform to Django behavior)
                    pk = getattr(obj, field.get_attname())
                    try:
                        rel_obj = rel_objs[pk]
                    except KeyError: # pragma: no cover
                        pass
                    else:
                        setattr(obj, field.name, rel_obj)
                yield obj
예제 #4
0
파일: forms.py 프로젝트: jqb/django-hvad
    def clean(self):
        super(BaseTranslationFormSet, self).clean()

        # Trigger combined instance validation
        master = self.instance
        stashed = get_cached_translation(master)

        for form in self.forms:
            form.instance.master = master
            combined = combine(form.instance, master.__class__)
            exclusions = form._get_validation_exclusions()
            # fields from the shared model should not be validated
            exclusions.extend(f.name for f in combined._meta.fields)
            try:
                if django.VERSION >= (1, 6):
                    combined.full_clean(exclude=exclusions,
                                        validate_unique=form._validate_unique)
                else:
                    combined.full_clean(exclude=exclusions)
            except ValidationError as e:
                form._update_errors(e)

        if stashed is None:
            delattr(master, master._meta.translations_cache)
        else:
            setattr(master, master._meta.translations_cache, stashed)

        # Validate that at least one translation exists
        forms_to_delete = self.deleted_forms
        valid = [((form.instance and form.instance.pk is not None) or
                  (form.is_valid() and form.has_changed()))
                 and not form in forms_to_delete for form in self.forms]
        if valid.count(True) < 1:
            raise ValidationError(_('At least one translation must be provided'),
                                  code='notranslation')
예제 #5
0
    def save(self, commit=True):
        if self.instance.pk is None:
            fail_message = 'created'
            new = True
        else:
            fail_message = 'changed'
            new = False

        if self.errors:
            opts = instance._meta
            raise ValueError("The %s could not be %s because the data didn't"
                             " validate." % (opts.object_name, fail_message))

        trans_model = self.instance._meta.translations_model
        language_code = self.cleaned_data.get('language_code', get_language())

        if not new:
            trans = get_cached_translation(self.instance)
            if not trans or trans.language_code != language_code:
                try:
                    trans = get_translation(self.instance, language_code)
                except trans_model.DoesNotExist:
                    trans = trans_model()
        else:
            trans = trans_model()

        trans = construct_instance(self, trans, self._meta.fields)
        trans.language_code = language_code
        trans.master = self.instance
        self.instance = combine(trans, self.Meta.model)

        super(TranslatableModelForm, self).save(commit=commit)
        return self.instance
예제 #6
0
    def save(self, commit=True):
        if self.instance.pk is None:
            fail_message = 'created'
            new = True
        else:
            fail_message = 'changed'
            new = False

        if self.errors:
            opts = self.instance._meta
            raise ValueError("The %s could not be %s because the data didn't"
                             " validate." % (opts.object_name, fail_message))

        trans_model = self.instance._meta.translations_model
        language_code = self.cleaned_data.get('language_code', get_language())

        if not new:
            trans = get_cached_translation(self.instance)
            if not trans or trans.language_code != language_code:
                try:
                    trans = get_translation(self.instance, language_code)
                except trans_model.DoesNotExist:
                    trans = trans_model()
        else:
            trans = trans_model()

        trans = construct_instance(self, trans, self._meta.fields)
        trans.language_code = language_code
        trans.master = self.instance
        self.instance = combine(trans, self.Meta.model)

        super(TranslatableModelForm, self).save(commit=commit)
        return self.instance
예제 #7
0
 def _post_clean(self):
     if self.instance.pk:
         try:
             trans = trans = get_translation(self.instance, self.instance.language_code)
             trans.master = self.instance
             self.instance = combine(trans, self.Meta.model)
         except self.instance._meta.translations_model.DoesNotExist:
             language_code = self.cleaned_data.get('language_code', get_language())
             self.instance = self.instance.translate(language_code)
     return super(TranslatableModelForm, self)._post_clean()
예제 #8
0
    def test_combine(self):
        model = Normal._meta.translations_model
        qs = model.objects.select_related('master').filter(master=self.normal_id[1])

        for translation in qs:
            combined = combine(translation, NormalProxy)
            self.assertEqual(combined.pk, self.normal_id[1])
            self.assertEqual(combined.shared_field, NORMAL[1].shared_field)
            self.assertEqual(combined.translated_field,
                             NORMAL[1].translated_field[translation.language_code])
            self.assertIsInstance(combined, NormalProxy)
예제 #9
0
 def _post_clean(self):
     if self.instance.pk:
         try:
             # Don't use self.instance.language_code here! That will fail if
             # the instance is not translated into the current language. If
             # it succeeded, then the instance would already be translated,
             # and there'd be no point combining it with the same
             # translation again.
             trans = get_translation(self.instance, self.language)
             trans.master = self.instance
             self.instance = combine(trans, self.Meta.model)
         except self.instance._meta.translations_model.DoesNotExist:
             self.instance = self.instance.translate(self.language)
     return super(TranslatableModelForm, self)._post_clean()
예제 #10
0
 def _post_clean(self):
     if self.instance.pk:
         try:
             # Don't use self.instance.language_code here! That will fail if
             # the instance is not translated into the current language. If
             # it succeeded, then the instance would already be translated,
             # and there'd be no point combining it with the same
             # translation again.
             trans = get_translation(self.instance, self.language)
             trans.master = self.instance
             self.instance = combine(trans, self.Meta.model)
         except self.instance._meta.translations_model.DoesNotExist:
             self.instance = self.instance.translate(self.language)
     return super(TranslatableModelForm, self)._post_clean()
예제 #11
0
    def iterator(self):
        """
        If this queryset is not filtered by a language code yet, it should be
        filtered first by calling self.language.
        
        If someone doesn't want a queryset filtered by language, they should use
        Model.objects.untranslated()
        """
        if not self._language_code:
            for obj in self.language().iterator():
                yield obj
        else:
            if self._forced_unique_fields:
                # In order for select_related to properly load data from
                # translated models, we have to force django to treat
                # certain fields as one-to-one relations
                # before this queryset calls get_cached_row()
                # We change it back so that things get reset to normal
                # before execution returns to user code.
                # It would be more direct and robust if we could wrap
                # django.db.models.query.get_cached_row() instead, but that's not a class
                # method, sadly, so we cannot override it just for this query

                # Enable temporary forced "unique" attribute for related translated models:
                for field in self._forced_unique_fields:
                    field._unique = True
                # Pre-fetch all objects:
                objects = [
                    o for o in super(TranslationQueryset, self).iterator()
                ]
                # Disable temporary forced attribute:
                for field in self._forced_unique_fields:
                    field._unique = False

                if type(self.query.select_related) == dict:
                    for obj in objects:
                        self._use_related_translations(
                            obj, self.query.select_related)
            else:
                objects = super(TranslationQueryset, self).iterator()
            for obj in objects:
                # non-cascade-deletion hack:
                if not obj.master:
                    yield obj
                else:
                    yield combine(obj, self.shared_model)
예제 #12
0
    def _save_translation(self, form, commit=True):
        obj = form.save(commit=False)
        assert isinstance(obj, BaseTranslationModel)

        if commit:
            # We need to trigger custom save actions on the combined model
            master = self.instance
            stashed = get_cached_translation(master)
            obj.master = master
            combined = combine(obj, master.__class__)
            combined.save()
            if hasattr(combined, 'save_m2m'):  # cannot happen, but feature
                combined.save_m2m()  # could be added, be ready
            if stashed is None:
                delattr(master, master._meta.translations_cache)
            else:
                setattr(master, master._meta.translations_cache, stashed)
        return obj
예제 #13
0
 def iterator(self):
     """
     If this queryset is not filtered by a language code yet, it should be
     filtered first by calling self.language.
     
     If someone doesn't want a queryset filtered by language, they should use
     Model.objects.untranslated()
     """
     if not self._language_code:
         for obj in self.language().iterator():
             yield obj
     else:
         for obj in super(TranslationQueryset, self).iterator():
             # non-cascade-deletion hack:
             if not obj.master:
                 yield obj
             else:
                 yield combine(obj)
예제 #14
0
    def _save_translation(self, form, commit=True):
        obj = form.save(commit=False)
        assert isinstance(obj, BaseTranslationModel)

        if commit:
            # We need to trigger custom save actions on the combined model
            master = self.instance
            stashed = get_cached_translation(master)
            obj.master = master
            combined = combine(obj, master.__class__)
            combined.save()
            if hasattr(combined, 'save_m2m'):   # cannot happen, but feature
                combined.save_m2m()             # could be added, be ready
            if stashed is None:
                delattr(master, master._meta.translations_cache)
            else:
                setattr(master, master._meta.translations_cache, stashed)
        return obj
예제 #15
0
 def iterator(self):
     """
     If this queryset is not filtered by a language code yet, it should be
     filtered first by calling self.language.
     
     If someone doesn't want a queryset filtered by language, they should use
     Model.objects.untranslated()
     """
     if not self._language_code:
         for obj in self.language().iterator():
             yield obj
     else:
         for obj in super(TranslationQueryset, self).iterator():
             # non-cascade-deletion hack:
             if not obj.master:
                 yield obj
             else:
                 yield combine(obj, self.shared_model)
예제 #16
0
    def iterator(self):
        """
        If this queryset is not filtered by a language code yet, it should be
        filtered first by calling self.language.
        
        If someone doesn't want a queryset filtered by language, they should use
        Model.objects.untranslated()
        """
        if not self._language_code:
            for obj in self.language().iterator():
                yield obj
        else:
            if self._forced_unique_fields:
                # In order for select_related to properly load data from
                # translated models, we have to force django to treat
                # certain fields as one-to-one relations
                # before this queryset calls get_cached_row()
                # We change it back so that things get reset to normal
                # before execution returns to user code.
                # It would be more direct and robust if we could wrap
                # django.db.models.query.get_cached_row() instead, but that's not a class
                # method, sadly, so we cannot override it just for this query

                # Enable temporary forced "unique" attribute for related translated models:
                for field in self._forced_unique_fields:
                    field._unique = True
                # Pre-fetch all objects:
                objects = [o for o in super(TranslationQueryset, self).iterator()]
                # Disable temporary forced attribute:
                for field in self._forced_unique_fields:
                    field._unique = False

                if type(self.query.select_related) == dict:
                    for obj in objects:
                        self._use_related_translations(obj, self.query.select_related)
            else:
                objects = super(TranslationQueryset, self).iterator()
            for obj in objects:
                # non-cascade-deletion hack:
                if not obj.master:
                    yield obj
                else:
                    yield combine(obj, self.shared_model)
예제 #17
0
 def _get_real_instances(self, base_results):
     """
     The logic for this method was taken from django-polymorphic by Bert
     Constantin (https://github.com/bconstantin/django_polymorphic) and was
     slightly altered to fit the needs of django-hvad.
     """
     # get the primary keys of the shared model results
     base_ids = [obj.pk for obj in base_results]
     fallbacks = [
         get_language() if lang is None else lang
         for lang in self.translation_fallbacks
     ]
     # get all translations for the fallbacks chosen for those shared models,
     # note that this query is *BIG* and might return a lot of data, but it's
     # arguably faster than running one query for each result or even worse
     # one query per result per language until we find something
     translations_manager = self.model._meta.translations_model.objects
     baseqs = translations_manager.select_related('master')
     translations = baseqs.filter(language_code__in=fallbacks,
                                  master__pk__in=base_ids)
     fallback_objects = defaultdict(dict)
     # turn the results into a dict of dicts with shared model primary key as
     # keys for the first dict and language codes for the second dict
     for obj in translations:
         fallback_objects[obj.master.pk][obj.language_code] = obj
     # iterate over the share dmodel results
     for instance in base_results:
         translation = None
         # find the translation
         for fallback in fallbacks:
             translation = fallback_objects[instance.pk].get(fallback, None)
             if translation is not None:
                 break
         # if we found a translation, yield the combined result
         if translation:
             yield combine(translation, self.model)
         else:
             # otherwise yield the shared instance only
             _logger.error("no translation for %s.%s (pk=%s)" %
                           (instance._meta.app_label,
                            instance.__class__.__name__, str(instance.pk)))
             yield instance
예제 #18
0
 def _get_real_instances(self, base_results):
     """
     The logic for this method was taken from django-polymorphic by Bert
     Constantin (https://github.com/bconstantin/django_polymorphic) and was
     slightly altered to fit the needs of django-hvad.
     """
     # get the primary keys of the shared model results
     base_ids = [obj.pk for obj in base_results]
     fallbacks = [get_language() if lang is None else lang
                  for lang in self.translation_fallbacks]
     # get all translations for the fallbacks chosen for those shared models,
     # note that this query is *BIG* and might return a lot of data, but it's
     # arguably faster than running one query for each result or even worse
     # one query per result per language until we find something
     translations_manager = self.model._meta.translations_model.objects
     baseqs = translations_manager.select_related('master')
     translations = baseqs.filter(language_code__in=fallbacks,
                                  master__pk__in=base_ids)
     fallback_objects = defaultdict(dict)
     # turn the results into a dict of dicts with shared model primary key as
     # keys for the first dict and language codes for the second dict
     for obj in translations:
         fallback_objects[obj.master.pk][obj.language_code] = obj
     # iterate over the share dmodel results
     for instance in base_results:
         translation = None
         # find the translation
         for fallback in fallbacks:
             translation = fallback_objects[instance.pk].get(fallback, None)
             if translation is not None:
                 break
         # if we found a translation, yield the combined result
         if translation:
             yield combine(translation, self.model)
         else:
             # otherwise yield the shared instance only
             _logger.error("no translation for %s.%s (pk=%s)" %
                           (instance._meta.app_label,
                            instance.__class__.__name__,
                            str(instance.pk)))
             yield instance
예제 #19
0
    def clean(self):
        super(BaseTranslationFormSet, self).clean()

        # Trigger combined instance validation
        master = self.instance
        stashed = get_cached_translation(master)

        for form in self.forms:
            form.instance.master = master
            combined = combine(form.instance, master.__class__)
            exclusions = form._get_validation_exclusions()
            # fields from the shared model should not be validated
            exclusions.extend(f.name for f in combined._meta.fields)
            try:
                if django.VERSION >= (1, 6):
                    combined.full_clean(exclude=exclusions,
                                        validate_unique=form._validate_unique)
                else:
                    combined.full_clean(exclude=exclusions)
            except ValidationError as e:
                form._update_errors(e)

        if stashed is None:
            delattr(master, master._meta.translations_cache)
        else:
            setattr(master, master._meta.translations_cache, stashed)

        # Validate that at least one translation exists
        forms_to_delete = self.deleted_forms
        provided = [
            form for form in self.forms
            if (getattr(form.instance, 'pk', None) is not None
                or form.has_changed()) and not form in forms_to_delete
        ]
        if len(provided) < 1:
            raise ValidationError(
                _('At least one translation must be provided'),
                code='notranslation')
예제 #20
0
        def iterator(self):
            qs = self._clone()._add_language_filter()
            qs._known_related_objects = {
            }  # super's iterator will attempt to set them
            if qs._forced_unique_fields:
                with ForcedUniqueFields(qs._forced_unique_fields):
                    objects = list(super(TranslationQueryset, qs).iterator())

                if type(qs.query.select_related) == dict:
                    for obj in objects:
                        qs._use_related_translations(obj,
                                                     qs.query.select_related)
            else:
                objects = super(TranslationQueryset, qs).iterator()

            for obj in objects:
                for name in self._hvad_switch_fields:
                    try:
                        setattr(obj.master, name, getattr(obj, name))
                    except AttributeError:  # pragma: no cover
                        pass
                    else:
                        delattr(obj, name)
                obj = combine(obj, qs.shared_model)
                # use known objects from self, not qs as we cleared it earlier
                for field, rel_objs in self._known_related_objects.items():
                    if hasattr(obj, field.get_cache_name()):
                        # should not happen, but we conform to Django behavior
                        continue  #pragma: no cover
                    pk = getattr(obj, field.get_attname())
                    try:
                        rel_obj = rel_objs[pk]
                    except KeyError:  #pragma: no cover
                        pass
                    else:
                        setattr(obj, field.name, rel_obj)
                yield obj
예제 #21
0
        def __iter__(self):
            qs = self.queryset._clone()._add_language_filter()
            qs._iterable_class = ModelIterable
            qs._known_related_objects = {}
            if qs._forced_unique_fields:
                with ForcedUniqueFields(qs._forced_unique_fields):
                    objects = list(qs.iterator())

                if type(qs.query.select_related) == dict:
                    for obj in objects:
                        qs._use_related_translations(obj,
                                                     qs.query.select_related)
            else:
                objects = qs.iterator()

            for obj in objects:
                for name in qs._hvad_switch_fields:
                    try:
                        setattr(obj.master, name, getattr(obj, name))
                    except AttributeError:  # pragma: no cover
                        pass
                    else:
                        delattr(obj, name)
                obj = combine(obj, qs.shared_model)
                # use known objects from self.queryset, not qs as we cleared it earlier
                for field, rel_objs in self.queryset._known_related_objects.items(
                ):
                    if hasattr(obj, field.get_cache_name()):
                        continue  # pragma: no cover (conform to Django behavior)
                    pk = getattr(obj, field.get_attname())
                    try:
                        rel_obj = rel_objs[pk]
                    except KeyError:  # pragma: no cover
                        pass
                    else:
                        setattr(obj, field.name, rel_obj)
                yield obj
예제 #22
0
파일: manager.py 프로젝트: jqb/django-hvad
    def iterator(self):
        """
        If this queryset is not filtered by a language code yet, it should be
        filtered first by calling self.language.

        If someone doesn't want a queryset filtered by language, they should use
        Model.objects.untranslated()
        """
        qs = self._clone()._add_language_filter()

        if qs._forced_unique_fields:
            # HACK: In order for select_related to properly load data from
            # translated models, we have to force django to treat
            # certain fields as one-to-one relations
            # before this queryset calls get_cached_row()
            # We change it back so that things get reset to normal
            # before execution returns to user code.
            # It would be more direct and robust if we could wrap
            # django.db.models.query.get_cached_row() instead, but that's not a class
            # method, sadly, so we cannot override it just for this query

            with ForcedUniqueFields(qs._forced_unique_fields):
                # Pre-fetch all objects:
                objects = list(super(TranslationQueryset, qs).iterator())

            if type(qs.query.select_related) == dict:
                for obj in objects:
                    qs._use_related_translations(obj, qs.query.select_related)
        else:
            objects = super(TranslationQueryset, qs).iterator()

        for obj in objects:
            # non-cascade-deletion hack:
            if not obj.master:
                yield obj
            else:
                yield combine(obj, qs.shared_model)
예제 #23
0
    def iterator(self):
        """
        If this queryset is not filtered by a language code yet, it should be
        filtered first by calling self.language.

        If someone doesn't want a queryset filtered by language, they should use
        Model.objects.untranslated()
        """
        qs = self._clone()._add_language_filter()
        qs._known_related_objects = {}  # super's iterator will attempt to set them

        if qs._forced_unique_fields:
            # HACK: In order for select_related to properly load data from
            # translated models, we have to force django to treat
            # certain fields as one-to-one relations
            # before this queryset calls get_cached_row()
            # We change it back so that things get reset to normal
            # before execution returns to user code.
            # It would be more direct and robust if we could wrap
            # django.db.models.query.get_cached_row() instead, but that's not a class
            # method, sadly, so we cannot override it just for this query

            with ForcedUniqueFields(qs._forced_unique_fields):
                # Pre-fetch all objects:
                objects = list(super(TranslationQueryset, qs).iterator())

            if type(qs.query.select_related) == dict:
                for obj in objects:
                    qs._use_related_translations(obj, qs.query.select_related)
        else:
            objects = super(TranslationQueryset, qs).iterator()

        for obj in objects:
            # non-cascade-deletion hack:
            if not obj.master:
                yield obj
            else:
                for name in self._hvad_switch_fields:
                    try:
                        setattr(obj.master, name, getattr(obj, name))
                    except AttributeError:
                        pass
                    else:
                        delattr(obj, name)
                obj = combine(obj, qs.shared_model)
                # use known objects from self, not qs as we cleared it earlier
                if django.VERSION >= (1, 6):
                    for field, rel_objs in self._known_related_objects.items():
                        if hasattr(obj, field.get_cache_name()):
                            continue # field was already cached
                        pk = getattr(obj, field.get_attname())
                        try:
                            rel_obj = rel_objs[pk]
                        except KeyError:
                            pass
                        else:
                            setattr(obj, field.name, rel_obj)
                else:
                    kro_attname, kro_instance = (getattr(self, 'known_related_object', None)
                                                 or (None, None))
                    if kro_instance:
                        setattr(obj, kro_attname, kro_instance)
                yield obj