예제 #1
0
    def create_translation(self, language_code, **fields):
        """
        Add a translation to the model.

        The :func:`save_translations` function is called afterwards.

        The object will be saved immediately, similar to
        calling :func:`~django.db.models.manager.Manager.create`
        or :func:`~django.db.models.fields.related.RelatedManager.create` on related fields.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        meta = self._parler_meta
        if self._translations_cache[meta.root_model].get(language_code, None):  # MISSING evaluates to False too
            raise ValueError("Translation already exists: {0}".format(language_code))

        # Save all fields in the proper translated model.
        for translation in self._set_translated_fields(language_code, **fields):
            self.save_translation(translation)
예제 #2
0
    def create_translation(self, language_code, **fields):
        """
        Add a translation to the model.

        The :func:`save_translations` function is called afterwards.

        The object will be saved immediately, similar to
        calling :func:`~django.db.models.manager.Manager.create`
        or :func:`~django.db.models.fields.related.RelatedManager.create` on related fields.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        meta = self._parler_meta
        if self._translations_cache[meta.root_model].get(language_code, None):  # MISSING evaluates to False too
            raise ValueError("Translation already exists: {0}".format(language_code))

        # Save all fields in the proper translated model.
        for translation in self._set_translated_fields(language_code, **fields):
            self.save_translation(translation)
예제 #3
0
    def delete_translation(self, language_code, related_name=None):
        """
        Delete a translation from a model.

        :param language_code: The language to remove.
        :param related_name: If given, only the model matching that related_name is removed.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        if related_name is None:
            metas = self._parler_meta
        else:
            metas = [self._parler_meta[related_name]]

        num_deleted = 0
        for meta in metas:
            try:
                translation = self._get_translated_model(language_code, meta=meta)
            except meta.model.DoesNotExist:
                continue

            # By using the regular model delete, the cache is properly cleared
            # (via _delete_cached_translation) and signals are emitted.
            translation.delete()
            num_deleted += 1

            # Clear other local caches
            try:
                del self._translations_cache[meta.model][language_code]
            except KeyError:
                pass
            try:
                del self._prefetched_objects_cache[meta.rel_name]
            except (AttributeError, KeyError):
                pass

        if not num_deleted:
            raise ValueError("Translation does not exist: {0}".format(language_code))

        return num_deleted
예제 #4
0
    def delete_translation(self, language_code, related_name=None):
        """
        Delete a translation from a model.

        :param language_code: The language to remove.
        :param related_name: If given, only the model matching that related_name is removed.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        if related_name is None:
            metas = self._parler_meta
        else:
            metas = [self._parler_meta[related_name]]

        num_deleted = 0
        for meta in metas:
            try:
                translation = self._get_translated_model(language_code, meta=meta)
            except meta.model.DoesNotExist:
                continue

            # By using the regular model delete, the cache is properly cleared
            # (via _delete_cached_translation) and signals are emitted.
            translation.delete()
            num_deleted += 1

            # Clear other local caches
            try:
                del self._translations_cache[meta.model][language_code]
            except KeyError:
                pass
            try:
                del self._prefetched_objects_cache[meta.rel_name]
            except (AttributeError, KeyError):
                pass

        if not num_deleted:
            raise ValueError("Translation does not exist: {0}".format(language_code))

        return num_deleted
예제 #5
0
    def has_translation(self, language_code=None, related_name=None):
        """
        Return whether a translation for the given language exists.
        Defaults to the current language code.

        .. versionadded 1.2 Added the ``related_name`` parameter.
        """
        if language_code is None:
            language_code = self._current_language
            if language_code is None:
                raise ValueError(get_null_language_error())

        meta = self._parler_meta._get_extension_by_related_name(related_name)

        try:
            # Check the local cache directly, and the answer is known.
            # NOTE this may also return newly auto created translations which are not saved yet.
            return self._translations_cache[meta.model][language_code] is not MISSING
        except KeyError:
            # If there is a prefetch, will be using that.
            # However, don't assume the prefetch contains all possible languages.
            # With Django 1.8, there are custom Prefetch objects.
            # TODO: improve this, detect whether this is the case.
            if language_code in self._read_prefetched_translations(meta=meta):
                return True

            # Try to fetch from the cache first.
            # If the cache returns the fallback, it means the original does not exist.
            object = get_cached_translation(self, language_code, related_name=related_name, use_fallback=True)
            if object is not None:
                return object.language_code == language_code

            try:
                # Fetch from DB, fill the cache.
                self._get_translated_model(language_code, use_fallback=False, auto_create=False, meta=meta)
            except meta.model.DoesNotExist:
                return False
            else:
                return True
예제 #6
0
    def has_translation(self, language_code=None, related_name=None):
        """
        Return whether a translation for the given language exists.
        Defaults to the current language code.

        .. versionadded 1.2 Added the ``related_name`` parameter.
        """
        if language_code is None:
            language_code = self._current_language
            if language_code is None:
                raise ValueError(get_null_language_error())

        meta = self._parler_meta._get_extension_by_related_name(related_name)

        try:
            # Check the local cache directly, and the answer is known.
            # NOTE this may also return newly auto created translations which are not saved yet.
            return self._translations_cache[meta.model][language_code] is not MISSING
        except KeyError:
            # If there is a prefetch, will be using that.
            # However, don't assume the prefetch contains all possible languages.
            # With Django 1.8, there are custom Prefetch objects.
            # TODO: improve this, detect whether this is the case.
            if language_code in self._read_prefetched_translations(meta=meta):
                return True

            # Try to fetch from the cache first.
            # If the cache returns the fallback, it means the original does not exist.
            object = get_cached_translation(self, language_code, related_name=related_name, use_fallback=True)
            if object is not None:
                return object.language_code == language_code

            try:
                # Fetch from DB, fill the cache.
                self._get_translated_model(language_code, use_fallback=False, auto_create=False, meta=meta)
            except meta.model.DoesNotExist:
                return False
            else:
                return True
예제 #7
0
    def get_language(self, language_code, site_id=None):
        """
        Return the language settings for the current site

        This function can be used with other settings variables
        to support modules which create their own variation of the ``PARLER_LANGUAGES`` setting.
        For an example, see :func:`~parler.appsettings.add_default_language_settings`.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        if site_id is None:
            site_id = getattr(settings, "SITE_ID", None)

        for lang_dict in self.get(site_id, ()):
            if lang_dict["code"] == language_code:
                return lang_dict

        # no language match, search for variant: fr-ca falls back to fr
        for lang_dict in self.get(site_id, ()):
            if lang_dict["code"].split("-")[0] == language_code.split("-")[0]:
                return lang_dict

        return self["default"]
예제 #8
0
    def get_language(self, language_code, site_id=None):
        """
        Return the language settings for the current site

        This function can be used with other settings variables
        to support modules which create their own variation of the ``PARLER_LANGUAGES`` setting.
        For an example, see :func:`~parler.appsettings.add_default_language_settings`.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        if site_id is None:
            site_id = getattr(settings, 'SITE_ID', None)

        for lang_dict in self.get(site_id, ()):
            if lang_dict['code'] == language_code:
                return lang_dict

        # no language match, search for variant: fr-ca falls back to fr
        for lang_dict in self.get(site_id, ()):
            if lang_dict['code'].split('-')[0] == language_code.split('-')[0]:
                return lang_dict

        return self['default']
예제 #9
0
    def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False, meta=None):
        """
        Fetch the translated fields model.
        """
        if self._parler_meta is None:
            raise ImproperlyConfigured("No translation is assigned to the current model!")
        if self._translations_cache is None:
            raise RuntimeError("Accessing translated fields before super.__init__() is not possible.")

        if not language_code:
            language_code = self._current_language
            if language_code is None:
                raise ValueError(get_null_language_error())

        if meta is None:
            meta = self._parler_meta.root  # work on base model by default

        local_cache = self._translations_cache[meta.model]

        # 1. fetch the object from the local cache
        try:
            object = local_cache[language_code]

            # If cached object indicates the language doesn't exist, need to query the fallback.
            if object is not MISSING:
                return object
        except KeyError:
            # 2. No cache, need to query
            # Check that this object already exists, would be pointless otherwise to check for a translation.
            if not self._state.adding and self.pk is not None:
                prefetch = self._get_prefetched_translations(meta=meta)
                if prefetch is not None:
                    # 2.1, use prefetched data
                    # If the object is not found in the prefetched data (which contains all translations),
                    # it's pointless to check for memcached (2.2) or perform a single query (2.3)
                    for object in prefetch:
                        if object.language_code == language_code:
                            local_cache[language_code] = object
                            _cache_translation(object)  # Store in memcached
                            return object
                else:
                    # 2.2, fetch from memcached
                    object = get_cached_translation(self, language_code, related_name=meta.rel_name, use_fallback=use_fallback)
                    if object is not None:
                        # Track in local cache
                        if object.language_code != language_code:
                            local_cache[language_code] = MISSING  # Set fallback marker
                        local_cache[object.language_code] = object
                        return object
                    elif local_cache.get(language_code, None) is MISSING:
                        # If get_cached_translation() explicitly set the "does not exist" marker,
                        # there is no need to try a database query.
                        pass
                    else:
                        # 2.3, fetch from database
                        try:
                            object = self._get_translated_queryset(meta).get(language_code=language_code)
                        except meta.model.DoesNotExist:
                            pass
                        else:
                            local_cache[language_code] = object
                            _cache_translation(object)  # Store in memcached
                            return object

        # Not in cache, or default.
        # Not fetched from DB

        # 3. Auto create?
        if auto_create:
            # Auto create policy first (e.g. a __set__ call)
            kwargs = {
                'language_code': language_code,
            }
            if self.pk:
                # ID might be None at this point, and Django 1.8 does not allow that.
                kwargs['master'] = self

            object = meta.model(**kwargs)
            local_cache[language_code] = object
            # Not stored in memcached here yet, first fill + save it.
            return object

        # 4. Fallback?
        fallback_msg = None
        lang_dict = get_language_settings(language_code)

        if language_code not in local_cache:
            # Explicitly set a marker for the fact that this translation uses the fallback instead.
            # Avoid making that query again.
            local_cache[language_code] = MISSING  # None value is the marker.
            if not self._state.adding or self.pk is not None:
                _cache_translation_needs_fallback(self, language_code, related_name=meta.rel_name)

        fallback_choices = [lang_dict['code']] + list(lang_dict['fallbacks'])
        if use_fallback and fallback_choices:
            # Jump to fallback language, return directly.
            # Don't cache under this language_code
            for fallback_lang in fallback_choices:
                if fallback_lang == language_code:  # Skip the current language, could also be fallback 1 of 2 choices
                    continue

                try:
                    return self._get_translated_model(fallback_lang, use_fallback=False, auto_create=auto_create, meta=meta)
                except meta.model.DoesNotExist:
                    pass

            fallback_msg = " (tried fallbacks {0})".format(', '.join(lang_dict['fallbacks']))

        # None of the above, bail out!
        raise meta.model.DoesNotExist(
            "{0} does not have a translation for the current language!\n"
            "{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or ''
        ))
예제 #10
0
    def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False, meta=None):
        """
        Fetch the translated fields model.
        """
        if self._parler_meta is None:
            raise ImproperlyConfigured("No translation is assigned to the current model!")
        if self._translations_cache is None:
            raise RuntimeError("Accessing translated fields before super.__init__() is not possible.")

        if not language_code:
            language_code = self._current_language
            if language_code is None:
                raise ValueError(get_null_language_error())

        if meta is None:
            meta = self._parler_meta.root  # work on base model by default

        local_cache = self._translations_cache[meta.model]

        # 1. fetch the object from the local cache
        try:
            object = local_cache[language_code]

            # If cached object indicates the language doesn't exist, need to query the fallback.
            if object is not MISSING:
                return object
        except KeyError:
            # 2. No cache, need to query
            # Check that this object already exists, would be pointless otherwise to check for a translation.
            if not self._state.adding and self.pk is not None:
                prefetch = self._get_prefetched_translations(meta=meta)
                if prefetch is not None:
                    # 2.1, use prefetched data
                    # If the object is not found in the prefetched data (which contains all translations),
                    # it's pointless to check for memcached (2.2) or perform a single query (2.3)
                    for object in prefetch:
                        if object.language_code == language_code:
                            local_cache[language_code] = object
                            _cache_translation(object)  # Store in memcached
                            return object
                else:
                    # 2.2, fetch from memcached
                    object = get_cached_translation(self, language_code, related_name=meta.rel_name, use_fallback=use_fallback)
                    if object is not None:
                        # Track in local cache
                        if object.language_code != language_code:
                            local_cache[language_code] = MISSING  # Set fallback marker
                        local_cache[object.language_code] = object
                        return object
                    elif local_cache.get(language_code, None) is MISSING:
                        # If get_cached_translation() explicitly set the "does not exist" marker,
                        # there is no need to try a database query.
                        pass
                    else:
                        # 2.3, fetch from database
                        try:
                            object = self._get_translated_queryset(meta).get(language_code=language_code)
                        except meta.model.DoesNotExist:
                            pass
                        else:
                            local_cache[language_code] = object
                            _cache_translation(object)  # Store in memcached
                            return object

        # Not in cache, or default.
        # Not fetched from DB

        # 3. Auto create?
        if auto_create:
            # Auto create policy first (e.g. a __set__ call)
            kwargs = {
                'language_code': language_code,
            }
            if self.pk:
                # ID might be None at this point, and Django 1.8 does not allow that.
                kwargs['master'] = self

            object = meta.model(**kwargs)
            local_cache[language_code] = object
            # Not stored in memcached here yet, first fill + save it.
            return object

        # 4. Fallback?
        fallback_msg = None
        lang_dict = get_language_settings(language_code)

        if language_code not in local_cache:
            # Explicitly set a marker for the fact that this translation uses the fallback instead.
            # Avoid making that query again.
            local_cache[language_code] = MISSING  # None value is the marker.
            if not self._state.adding or self.pk is not None:
                _cache_translation_needs_fallback(self, language_code, related_name=meta.rel_name)

        fallback_choices = [lang_dict['code']] + list(lang_dict['fallbacks'])
        if use_fallback and fallback_choices:
            # Jump to fallback language, return directly.
            # Don't cache under this language_code
            for fallback_lang in fallback_choices:
                if fallback_lang == language_code:  # Skip the current language, could also be fallback 1 of 2 choices
                    continue

                try:
                    return self._get_translated_model(fallback_lang, use_fallback=False, auto_create=auto_create, meta=meta)
                except meta.model.DoesNotExist:
                    pass

            fallback_msg = " (tried fallbacks {0})".format(', '.join(lang_dict['fallbacks']))

        # None of the above, bail out!
        raise meta.model.DoesNotExist(
            "{0} does not have a translation for the current language!\n"
            "{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or ''
        ))