Esempio n. 1
0
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)
Esempio n. 2
0
 class TranslatableModelForm(
         compat.with_metaclass(TranslatableModelFormMetaclass,
                               BaseTranslatableModelForm, forms.ModelForm)):
     """
Esempio n. 3
0
class TranslatableModelForm(
        compat.with_metaclass(TranslatableModelFormMetaclass,
                              TranslatableModelFormMixin, forms.ModelForm)):
    """