class TranslatedFieldsModel( compat.with_metaclass(TranslatedFieldsModelBase, models.Model)): """ Base class for the model that holds the translated fields. """ language_code = compat.HideChoicesCharField(_("Language"), choices=settings.LANGUAGES, max_length=15, db_index=True) #: The mandatory Foreign key field to the shared model. master = None # FK to shared model. class Meta: abstract = True if django.VERSION >= (1, 7): default_permissions = () def __init__(self, *args, **kwargs): signals.pre_translation_init.send(sender=self.__class__, args=args, kwargs=kwargs) super(TranslatedFieldsModel, self).__init__(*args, **kwargs) self._original_values = self._get_field_values() signals.post_translation_init.send(sender=self.__class__, args=args, kwargs=kwargs) @property def is_modified(self): """ Tell whether the object content is modified since fetching it. """ return self._original_values != self._get_field_values() @property def is_empty(self): """ True when there are no translated fields. """ return len(self.get_translated_fields()) == 0 @property def shared_model(self): """ Returns the shared model this model is linked to. """ return self.__class__.master.field.rel.to @property def related_name(self): """ Returns the related name that this model is known at in the shared model. """ return self.__class__.master.field.rel.related_name def save_base(self, raw=False, using=None, **kwargs): # Send the pre_save signal using = using or router.db_for_write(self.__class__, instance=self) record_exists = self.pk is not None # Ignoring force_insert/force_update for now. if not self._meta.auto_created: signals.pre_translation_save.send(sender=self.shared_model, instance=self, raw=raw, using=using) # Perform save super(TranslatedFieldsModel, self).save_base(raw=raw, using=using, **kwargs) self._original_values = self._get_field_values() _cache_translation(self) # Send the post_save signal if not self._meta.auto_created: signals.post_translation_save.send(sender=self.shared_model, instance=self, created=(not record_exists), raw=raw, using=using) def delete(self, using=None): # Send pre-delete signal using = using or router.db_for_write(self.__class__, instance=self) if not self._meta.auto_created: signals.pre_translation_delete.send(sender=self.shared_model, instance=self, using=using) super(TranslatedFieldsModel, self).delete(using=using) _delete_cached_translation(self) # Send post-delete signal if not self._meta.auto_created: signals.post_translation_delete.send(sender=self.shared_model, instance=self, using=using) if django.VERSION >= (1, 8): def _get_field_values(self): # Use the new Model._meta API. return [ getattr(self, field.get_attname()) for field in self._meta.get_fields() ] else: def _get_field_values(self): # Return all field values in a consistent (sorted) manner. return [ getattr(self, field.get_attname()) for field, _ in self._meta.get_fields_with_model() ] @classmethod def get_translated_fields(cls): return [ f.name for f in cls._meta.local_fields if f.name not in ('language_code', 'master', 'id') ] @classmethod def contribute_translations(cls, shared_model): """ Add the proxy attributes to the shared model. """ # Instance at previous inheritance level, if set. base = shared_model._parler_meta if base is not None and base[-1].shared_model is shared_model: # If a second translations model is added, register it in the same object level. base.add_meta( ParlerMeta(shared_model=shared_model, translations_model=cls, related_name=cls.master.field.rel.related_name)) else: # Place a new _parler_meta at the current inheritance level. # It links to the previous base. shared_model._parler_meta = ParlerOptions( base, shared_model=shared_model, translations_model=cls, related_name=cls.master.field.rel.related_name) # Assign the proxy fields for name in cls.get_translated_fields(): try: # Check if an attribute already exists. # Note that the descriptor even proxies this request, so it should return our field. # # A model field might not be added yet, as this all happens in the contribute_to_class() loop. # Hence, only checking attributes here. The real fields are checked for in the _prepare() code. shared_field = getattr(shared_model, name) except AttributeError: # Add the proxy field for the shared field. TranslatedField().contribute_to_class(shared_model, name) else: # Currently not allowing to replace existing model fields with translatable fields. # That would be a nice feature addition however. if not isinstance(shared_field, (models.Field, TranslatedFieldDescriptor)): raise TypeError( "The model '{0}' already has a field named '{1}'". format(shared_model.__name__, name)) # When the descriptor was placed on an abstract model, # it doesn't point to the real model that holds the translations_model # "Upgrade" the descriptor on the class if shared_field.field.model is not shared_model: TranslatedField(any_language=shared_field.field. any_language).contribute_to_class( shared_model, name) # Make sure the DoesNotExist error can be detected als shared_model.DoesNotExist too, # and by inheriting from AttributeError it makes sure (admin) templates can handle the missing attribute. cls.DoesNotExist = type(str('DoesNotExist'), ( TranslationDoesNotExist, shared_model.DoesNotExist, cls.DoesNotExist, ), {}) def __unicode__(self): # use format to avoid weird error in django 1.4 # TypeError: coercing to Unicode: need string or buffer, __proxy__ found return "{0}".format(get_language_title(self.language_code)) def __repr__(self): return "<{0}: #{1}, {2}, master: #{3}>".format(self.__class__.__name__, self.pk, self.language_code, self.master_id)
class TranslatableModelForm( compat.with_metaclass(TranslatableModelFormMetaclass, BaseTranslatableModelForm, forms.ModelForm)): """
class TranslatableModelForm( compat.with_metaclass(TranslatableModelFormMetaclass, TranslatableModelFormMixin, forms.ModelForm)): """