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)
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
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
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"]
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']
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 '' ))