Exemple #1
0
class Commodity(AvailableProductMixin, Product):
    """
    This Commodity model inherits from polymorphic Product, and therefore has to be redefined.
    """
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    quantity = models.PositiveIntegerField(
        _("Quantity"),
        default=0,
        validators=[MinValueValidator(0)],
        help_text=_("Available quantity in stock")
    )

    # controlling the catalog
    placeholder = PlaceholderField("Commodity Details")
    show_breadcrumb = True  # hard coded to always show the product's breadcrumb

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Commodity")
        verbose_name_plural = _("Commodities")

    def get_price(self, request):
        return self.unit_price
class Tag(TranslatableModel):
    """
    Tag
    """
    active = models.BooleanField(_('Active'),
                                 default=True,
                                 help_text=bs.ACTIVE_FIELD_HELP_TEXT)
    date_added = models.DateTimeField(_('Date added'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'), auto_now=True)

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=255),
        slug=models.SlugField(_('Slug'), db_index=True),
        description=models.TextField(_('description'), blank=True),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    objects = TranslatableManager()

    class Meta:
        db_table = 'blogit_tags'
        verbose_name = _('Tag')
        verbose_name_plural = _('Tags')

    def __str__(self):
        return self.safe_translation_getter('name', any_language=True)

    def get_absolute_url(self, language=None):
        if not language:
            language = get_current_language()

        with switch_language(self, language):
            return reverse(
                'blogit_tag_detail',
                kwargs={'slug': self.safe_translation_getter('slug')})
Exemple #3
0
class SmartCard(Product):
    # common product fields
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    # product properties
    CARD_TYPE = (2 * ('{}{}'.format(s, t), )
                 for t in ('SD', 'SDXC', 'SDHC', 'SDHC II')
                 for s in ('', 'micro '))
    card_type = models.CharField(
        _("Card Type"),
        choices=CARD_TYPE,
        max_length=15,
    )

    SPEED = [(str(s), "{} MB/s".format(s))
             for s in (4, 20, 30, 40, 48, 80, 95, 280)]
    speed = models.CharField(
        _("Transfer Speed"),
        choices=SPEED,
        max_length=8,
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    storage = models.PositiveIntegerField(
        _("Storage Capacity"),
        help_text=_("Storage capacity in GB"),
    )

    multilingual = TranslatedFields(description=HTMLField(
        verbose_name=_("Description"),
        configuration='CKEDITOR_SETTINGS_DESCRIPTION',
        help_text=_(
            "Full description used in the catalog's detail view of Smart Cards."
        ),
    ), )

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")

    def get_price(self, request):
        return self.unit_price

    def get_product_variant(self, extra):
        """
        SmartCards do not have flavors, they are the product.
        """
        return self
Exemple #4
0
class Presenter(TranslatableModel):
    '''
    Person that participates in the event as a guest,
    ie. a speaker, a performer, a workshop presenter or a host.

    First and last name are the only required fields.
    '''
    translations = TranslatedFields(
        name=models.CharField(max_length=255, default=''),
        occupation=models.CharField(max_length=255, blank=True),
        short_bio=models.TextField(blank=True, verbose_name='Short bio'),
        quote=models.CharField(max_length=255, blank=True,
                               verbose_name='Inspirational quote')
    )

    link = models.URLField(blank=True,
                           verbose_name='Website or social media profile')

    image = VersatileImageField(
        'Image',
        upload_to='presenters/',
        width_field='image_width',
        height_field='image_height',
        null=True,
        blank=True,
    )
    image_height = models.PositiveIntegerField(editable=False, null=True)
    image_width = models.PositiveIntegerField(editable=False, null=True)

    image_alt = VersatileImageField(
        'Image 2',
        upload_to='presenters/',
        width_field='image_alt_width',
        height_field='image_alt_height',
        null=True,
        blank=True,
    )
    image_alt_height = models.PositiveIntegerField(editable=False, null=True)
    image_alt_width = models.PositiveIntegerField(editable=False, null=True)
    

    is_published = models.BooleanField(_('Published'), default=True)

    # Managers are an easy way to create custom filters for queries.
    #
    # Documentation link:
    # https://docs.djangoproject.com/en/2.2/topics/db/managers/

    objects = TranslatableManager()
    speakers = PresenterTypeManager(Activity.TALK)
    performers = PresenterTypeManager(Activity.PERFORMANCE)
    side_presenters = PresenterTypeManager(Activity.SIDE_EVENT)
    hosts = PresenterTypeManager(Activity.HOSTING)

    slug = EnglishAutoSlugField(populate_from=['name'], overwrite=True)

    def __str__(self):
        return self.name
Exemple #5
0
class TranslatableModel(TranslatableModelMixin, models.Model):
    """
    Base model class to handle translations.

    All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`.
    """
    class Meta:
        abstract = True

    # change the default manager to the translation manager
    objects = TranslatableManager()
class Category(MPTTModel, TranslatableModel):
    """
    Category
    """
    active = models.BooleanField(_('Active'),
                                 default=True,
                                 help_text=bs.ACTIVE_FIELD_HELP_TEXT)
    date_added = models.DateTimeField(_('Date added'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'), auto_now=True)

    parent = TreeForeignKey(
        'self',
        models.SET_NULL,
        blank=True,
        null=True,
        related_name='children',
        verbose_name=_('Parent'),
    )

    translations = TranslatedFields(
        name=models.CharField(_('Name'), max_length=255),
        slug=models.SlugField(_('Slug'), db_index=True),
        description=models.TextField(_('description'), blank=True),
        meta={'unique_together': [('slug', 'language_code')]},
    )

    objects = TranslatableManager()

    class Meta:
        db_table = 'blogit_categories'
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')

    def __str__(self):
        return self.safe_translation_getter('name', any_language=True)

    def get_absolute_url(self, language=None):
        if not language:
            language = get_current_language()

        with switch_language(self, language):
            return reverse('blogit_category_detail', args=[self.get_path()])

    def get_path(self):
        """
        Returns ful url path for category.
        """
        path = []
        for obj in self.get_ancestors(include_self=True):
            path.append(obj.safe_translation_getter('slug', ''))
        return '/'.join(path)
Exemple #7
0
class Commodity(AvailableProductMixin, Product, TranslatableModelMixin):
    """
    This Commodity model inherits from polymorphic Product, and therefore has to be redefined.
    """
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )
    product_code = models.ForeignKey(
        ProductList,
        on_delete=models.CASCADE,
    )
    multilingual = TranslatedFields(
        description=HTMLField(
            verbose_name=_("Description"),
            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
            blank=True,
            help_text=
            _("Full description used in the catalog's detail view of Smart Cards."
              ),
        ),
        caption=HTMLField(
            verbose_name=_("Caption"),
            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
            blank=True,
            help_text=
            _("Full description used in the catalog's detail view of Smart Cards."
              ),
        ),
    )
    # controlling the catalog
    placeholder = PlaceholderField("Commodity Details")
    show_breadcrumb = True  # hard coded to always show the product's breadcrumb

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Commodity")
        verbose_name_plural = _("Commodities")

    def get_price(self, request):
        return self.unit_price
Exemple #8
0
class SmartPhoneModel(Product):
    """
    A generic smart phone model, which must be concretized by a model `SmartPhone` - see below.
    """
    BATTERY_TYPES = [
        (1, "Lithium Polymer (Li-Poly)"),
        (2, "Lithium Ion (Li-Ion)"),
    ]
    WIFI_CONNECTIVITY = [
        (1, "802.11 b/g/n"),
    ]
    BLUETOOTH_CONNECTIVITY = [
        (1, "Bluetooth 4.0"),
        (2, "Bluetooth 3.0"),
        (3, "Bluetooth 2.1"),
    ]
    battery_type = models.PositiveSmallIntegerField(
        _("Battery type"),
        choices=BATTERY_TYPES,
    )

    battery_capacity = models.PositiveIntegerField(
        _("Capacity"),
        help_text=_("Battery capacity in mAh"),
    )

    ram_storage = models.PositiveIntegerField(
        _("RAM"),
        help_text=_("RAM storage in MB"),
    )

    wifi_connectivity = models.PositiveIntegerField(
        _("WiFi"),
        choices=WIFI_CONNECTIVITY,
        help_text=_("WiFi Connectivity"),
    )

    bluetooth = models.PositiveIntegerField(
        _("Bluetooth"),
        choices=BLUETOOTH_CONNECTIVITY,
        help_text=_("Bluetooth Connectivity"),
    )

    gps = models.BooleanField(
        _("GPS"),
        default=False,
        help_text=_("GPS integrated"),
    )

    operating_system = models.ForeignKey(
        OperatingSystem,
        on_delete=models.CASCADE,
        verbose_name=_("Operating System"),
    )

    width = models.DecimalField(
        _("Width"),
        max_digits=4,
        decimal_places=1,
        help_text=_("Width in mm"),
    )

    height = models.DecimalField(
        _("Height"),
        max_digits=4,
        decimal_places=1,
        help_text=_("Height in mm"),
    )

    weight = models.DecimalField(
        _("Weight"),
        max_digits=5,
        decimal_places=1,
        help_text=_("Weight in gram"),
    )

    screen_size = models.DecimalField(
        _("Screen size"),
        max_digits=4,
        decimal_places=2,
        help_text=_("Diagonal screen size in inch"),
    )

    multilingual = TranslatedFields(
        description=HTMLField(
            verbose_name=_("Description"),
            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
            help_text=_(
                "Full description used in the catalog's detail view of Smart Phones."),
        ),
    )

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Smart Phone")
        verbose_name_plural = _("Smart Phones")

    def get_price(self, request):
        """
        Return the starting price for instances of this smart phone model.
        """
        if not hasattr(self, '_price'):
            if self.variants.exists():
                currency = self.variants.first().unit_price.currency
                aggr = self.variants.aggregate(models.Min('unit_price'))
                self._price = MoneyMaker(currency)(aggr['unit_price__min'])
            else:
                self._price = Money()
        return self._price

    def get_availability(self, request, **kwargs):
        variant = self.get_product_variant(**kwargs)
        return variant.get_availability(request)

    def deduct_from_stock(self, quantity, **kwargs):
        variant = self.get_product_variant(**kwargs)
        variant.deduct_from_stock(quantity)

    def is_in_cart(self, cart, watched=False, **kwargs):
        try:
            product_code = kwargs['product_code']
        except KeyError:
            return
        cart_item_qs = CartItem.objects.filter(cart=cart, product=self)
        for cart_item in cart_item_qs:
            if cart_item.product_code == product_code:
                return cart_item

    def get_product_variant(self, **kwargs):
        try:
            product_code = kwargs.get('product_code')
            return self.variants.get(product_code=product_code)
        except SmartPhoneVariant.DoesNotExist as e:
            raise SmartPhoneModel.DoesNotExist(e)
Exemple #9
0
class KhimageModel(Product):

    title = models.CharField(_("Title"), max_length=250, null=True)
    author = models.ForeignKey(Person,
                               null=True,
                               blank=True,
                               verbose_name=_('author'))
    """
    A generic smart phone model, which must be concretized by a model `SmartPhone` - see below.
    """
    #~ BATTERY_TYPES = (
    #~ (1, "Lithium Polymer (Li-Poly)"),
    #~ (2, "Lithium Ion (Li-Ion)"),
    #~ )
    #~ WIFI_CONNECTIVITY = (
    #~ (1, "802.11 b/g/n"),
    #~ )
    #~ BLUETOOTH_CONNECTIVITY = (
    #~ (1, "Bluetooth 4.0"),
    #~ (2, "Bluetooth 3.0"),
    #~ (3, "Bluetooth 2.1"),
    #~ )
    #~ battery_type = models.PositiveSmallIntegerField(
    #~ _("Battery type"),
    #~ choices=BATTERY_TYPES,
    #~ )

    #~ battery_capacity = models.PositiveIntegerField(
    #~ _("Capacity"),
    #~ help_text=_("Battery capacity in mAh"),
    #~ )

    #~ ram_storage = models.PositiveIntegerField(
    #~ _("RAM"),
    #~ help_text=_("RAM storage in MB"),
    #~ )

    #~ wifi_connectivity = models.PositiveIntegerField(
    #~ _("WiFi"),
    #~ choices=WIFI_CONNECTIVITY,
    #~ help_text=_("WiFi Connectivity"),
    #~ )

    #~ bluetooth = models.PositiveIntegerField(
    #~ _("Bluetooth"),
    #~ choices=BLUETOOTH_CONNECTIVITY,
    #~ help_text=_("Bluetooth Connectivity"),
    #~ )

    #~ gps = models.BooleanField(
    #~ _("GPS"),
    #~ default=False,
    #~ help_text=_("GPS integrated"),
    #~ )

    genre = models.ForeignKey(
        Genre,
        verbose_name=_("Genre"),
        null=True,
        blank=True,
    )

    width = models.DecimalField(
        _("Width"),
        max_digits=4,
        decimal_places=1,
        help_text=_("Width in mm"),
    )

    height = models.DecimalField(
        _("Height"),
        max_digits=4,
        decimal_places=1,
        help_text=_("Height in mm"),
    )

    #~ weight = models.DecimalField(
    #~ _("Weight"),
    #~ max_digits=5,
    #~ decimal_places=1,
    #~ help_text=_("Weight in gram"),
    #~ )

    #~ screen_size = models.DecimalField(
    #~ _("Screen size"),
    #~ max_digits=4,
    #~ decimal_places=2,
    #~ help_text=_("Diagonal screen size in inch"),
    #~ )

    multilingual = TranslatedFields(description=HTMLField(
        verbose_name=_("Description"),
        configuration='CKEDITOR_SETTINGS_DESCRIPTION',
        help_text=
        _("Full description used in the catalog's detail view of Smart Phones."
          ),
        blank=True,
    ), )

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Kunsthaus Image")
        verbose_name_plural = _("Kunsthaus Images")

    def get_price(self, request):
        """
        Return the starting price for instances of this smart phone model.
        """
        if not hasattr(self, '_price'):
            if self.variants.exists():
                currency = self.variants.first().unit_price.currency
                aggr = self.variants.aggregate(models.Min('unit_price'))
                self._price = MoneyMaker(currency)(aggr['unit_price__min'])
            else:
                self._price = Money()
        return self._price

    def is_in_cart(self, cart, watched=False, **kwargs):
        from shop.models.cart import CartItemModel
        try:
            product_code = kwargs['product_code']
        except KeyError:
            return
        cart_item_qs = CartItemModel.objects.filter(cart=cart, product=self)
        for cart_item in cart_item_qs:
            if cart_item.product_code == product_code:
                return cart_item

    def get_product_variant(self, **kwargs):
        try:
            return self.variants.get(**kwargs)
        except SmartPhoneVariant.DoesNotExist as e:
            raise SmartPhoneModel.DoesNotExist(e)
Exemple #10
0
class MarkdownDocument(TranslatableModelMixin, BaseModel):
    """Model representing a markdown document."""

    RESOURCE_NAME = "markdown-documents"

    # Common LTI resource fields
    lti_id = models.CharField(
        max_length=255,
        verbose_name=_("lti id"),
        help_text=_("ID for synchronization with an external LTI tool"),
        blank=True,
        null=True,
    )
    playlist = models.ForeignKey(
        to=Playlist,
        related_name="%(class)ss",
        verbose_name=_("playlist"),
        help_text=_("playlist to which this file belongs"),
        # don't allow hard deleting a playlist if it still contains files
        on_delete=models.PROTECT,
    )
    position = models.PositiveIntegerField(
        verbose_name=_("position"),
        help_text=_("position of this file in the playlist"),
        default=0,
    )
    duplicated_from = models.ForeignKey(
        to="self",
        related_name="duplicates",
        verbose_name=_("duplicated from"),
        help_text=_("original file this one was duplicated from"),
        # don't delete a file if the one it was duplicated from is hard deleted
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )

    # Common resource attribute
    created_by = models.ForeignKey(
        to=User,
        related_name="created_%(class)s",
        verbose_name=_("author"),
        help_text=_("author of the file"),
        # file is (soft-)deleted if author is (soft-)deleted
        on_delete=models.CASCADE,
        null=True,
        blank=True,
    )
    is_public = models.BooleanField(
        default=False,
        verbose_name=_("is Markdown document public"),
        help_text=_("Is the Markdown document publicly accessible?"),
    )

    # Specific Markdown document fields
    is_draft = models.BooleanField(
        default=True,
        verbose_name=_("is Markdown document still a draft"),
        help_text=_("Is the Markdown document a draft?"),
    )

    translations = TranslatedFields(
        # Title might not be limited to 255 characters
        title=models.CharField(
            max_length=255,
            verbose_name=_("title"),
            help_text=_("Markdown document's title"),
        ),
        content=models.TextField(
            verbose_name=_("Markdown document content"),
            help_text=_("The document Markdown formatted content"),
            blank=True,
        ),
        rendered_content=models.TextField(
            verbose_name=_("Markdown document rendered content"),
            help_text=_("The Markdown document formatted content for students"),
            blank=True,
        ),
    )

    rendering_options = models.JSONField(
        default=dict,
        verbose_name=_("Markdown rendering options"),
        help_text=_(
            "Markdown rendering options are use by the frontend's markdown to HTML renderer"
        ),
        blank=True,
    )

    # use the translation manager, this is mandatory
    objects = TranslatableManager()

    class Meta:
        """Options for the ``MarkdownDocument`` model."""

        db_table = "md_document"
        verbose_name = _("Markdown document")
        verbose_name_plural = _("Markdown documents")
        constraints = [
            models.UniqueConstraint(
                fields=["lti_id", "playlist"],
                condition=models.Q(deleted=None),
                name="markdown_document_unique_idx",
            )
        ]

    @staticmethod
    def get_ready_clause():
        """Clause used in lti.utils.get_or_create_resource to filter the documents.

        Returns
        -------
        models.Q
            A condition added to a QuerySet
        """
        return models.Q(is_draft=False)

    def __str__(self):
        """Get the string representation of an instance, needs to be in an i18n context."""
        result = f"{self.title}"
        if self.deleted:
            result = _("{:s} [deleted]").format(result)
        return result

    @property
    def consumer_site(self):
        """Return the consumer site linked to this file via the playlist."""
        return self.playlist.consumer_site
Exemple #11
0
class TranslatableModel(models.Model):
    """
    Base model class to handle translations.

    All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`.
    """
    class Meta:
        abstract = True

    # Consider these fields "protected" or "internal" attributes.
    # Not part of the public API, but used internally in the class hierarchy.
    _translations_field = None
    _translations_model = None

    language_code = LanguageCodeDescriptor()

    # change the default manager to the translation manager
    objects = TranslatableManager()

    def __init__(self, *args, **kwargs):
        # Still allow to pass the translated fields (e.g. title=...) to this function.
        translated_kwargs = {}
        current_language = None
        if kwargs:
            current_language = kwargs.pop('_current_language', None)
            for field in self._translations_model.get_translated_fields():
                try:
                    translated_kwargs[field] = kwargs.pop(field)
                except KeyError:
                    pass

        # Run original Django model __init__
        super(TranslatableModel, self).__init__(*args, **kwargs)

        self._translations_cache = {}
        self._current_language = normalize_language_code(
            current_language or get_language()
        )  # What you used to fetch the object is what you get.

        # Assign translated args manually.
        if translated_kwargs:
            translation = self._get_translated_model(auto_create=True)
            for field, value in six.iteritems(translated_kwargs):
                setattr(translation, field, value)

    def get_current_language(self):
        """
        Get the current language.
        """
        # not a property, so won't conflict with model fields.
        return self._current_language

    def set_current_language(self, language_code, initialize=False):
        """
        Switch the currently activate language of the object.
        """
        self._current_language = normalize_language_code(language_code
                                                         or get_language())

        # Ensure the translation is present for __get__ queries.
        if initialize:
            self._get_translated_model(use_fallback=False, auto_create=True)

    def get_fallback_language(self):
        """
        Return the fallback language code,
        which is used in case there is no translation for the currently active language.
        """
        lang_dict = get_language_settings(self._current_language)
        return lang_dict['fallback'] if lang_dict[
            'fallback'] != self._current_language else None

    def has_translation(self, language_code=None):
        """
        Return whether a translation for the given language exists.
        Defaults to the current language code.
        """
        if language_code is None:
            language_code = self._current_language

        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[language_code] is not None
        except KeyError:
            try:
                # Fetch from DB, fill the cache.
                self._get_translated_model(language_code,
                                           use_fallback=False,
                                           auto_create=False)
            except self._translations_model.DoesNotExist:
                return False
            else:
                return True

    def get_available_languages(self):
        """
        Return the language codes of all translated variations.
        """
        qs = self._get_translated_queryset()
        if qs._prefetch_done:
            return sorted(obj.language_code for obj in qs)
        else:
            return qs.values_list('language_code',
                                  flat=True).order_by('language_code')

    def _get_translated_model(self,
                              language_code=None,
                              use_fallback=False,
                              auto_create=False):
        """
        Fetch the translated fields model.
        """
        if not self._translations_model or not self._translations_field:
            raise ImproperlyConfigured(
                "No translation is assigned to the current model!")

        if not language_code:
            language_code = self._current_language

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

            # If cached object indicates the language doesn't exist, need to query the fallback.
            if object is not None:
                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:
                qs = self._get_translated_queryset()
                if qs._prefetch_done:
                    # 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 qs:
                        if object.language_code == language_code:
                            self._translations_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,
                                                    use_fallback=use_fallback)
                    if object is not None:
                        # Track in local cache
                        if object.language_code != language_code:
                            self._translations_cache[
                                language_code] = None  # Set fallback marker
                        self._translations_cache[object.language_code] = object
                        return object
                    else:
                        # 2.3, fetch from database
                        try:
                            object = qs.get(language_code=language_code)
                        except self._translations_model.DoesNotExist:
                            pass
                        else:
                            self._translations_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)
            object = self._translations_model(
                language_code=language_code,
                master=self  # ID might be None at this point
            )
            self._translations_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 use_fallback and (lang_dict['fallback'] != language_code):
            # Explicitly set a marker for the fact that this translation uses the fallback instead.
            # Avoid making that query again.
            self._translations_cache[
                language_code] = None  # None value is the marker.
            if not self._state.adding or self.pk:
                _cache_translation_needs_fallback(self, language_code)

            # Jump to fallback language, return directly.
            # Don't cache under this language_code
            try:
                return self._get_translated_model(lang_dict['fallback'],
                                                  use_fallback=False,
                                                  auto_create=auto_create)
            except self._translations_model.DoesNotExist:
                fallback_msg = " (tried fallback {0})".format(
                    lang_dict['fallback'])

        # None of the above, bail out!
        raise self._translations_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 ''))

    def _get_any_translated_model(self):
        """
        Return any available translation.
        Returns None if there are no translations at all.
        """
        if self._translations_cache:
            # There is already a language available in the case. No need for queries.
            # Give consistent answers if they exist.
            try:
                return self._translations_cache.get(self._current_language, None) \
                    or self._translations_cache.get(self.get_fallback_language(), None) \
                    or next(t for t in six.itervalues(self._translations_cache) if t if not None)  # Skip fallback markers.
            except StopIteration:
                pass

        try:
            # Use prefetch if available, otherwise perform separate query.
            qs = self._get_translated_queryset()
            if qs._prefetch_done:
                translation = list(qs)[0]
            else:
                translation = qs[0]
        except IndexError:
            return None
        else:
            self._translations_cache[translation.language_code] = translation
            _cache_translation(translation)
            return translation

    def _get_translated_queryset(self):
        """
        Return the queryset that points to the translated model.
        If there is a prefetch, it can be read from this queryset.
        """
        # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache.
        accessor = getattr(self, self._translations_field)
        try:
            return accessor.get_queryset()
        except AttributeError:
            # Fallback for Django 1.4 and Django 1.5
            return accessor.get_query_set()

    def save(self, *args, **kwargs):
        super(TranslatableModel, self).save(*args, **kwargs)
        self.save_translations(*args, **kwargs)

    def delete(self, using=None):
        _delete_cached_translations(self)
        super(TranslatableModel, self).delete(using)

    def save_translations(self, *args, **kwargs):
        """
        The method to save all translations.
        This can be overwritten to implement any custom additions.
        This method calls :func:`save_translation` for every fetched language.

        :param args: Any custom arguments to pass to :func:`save`.
        :param kwargs: Any custom arguments to pass to :func:`save`.
        """
        # Copy cache, new objects (e.g. fallbacks) might be fetched if users override save_translation()
        translations = self._translations_cache.values()

        # Save all translated objects which were fetched.
        # This also supports switching languages several times, and save everything in the end.
        for translation in translations:
            if translation is None:  # Skip fallback markers
                continue

            self.save_translation(translation, *args, **kwargs)

    def save_translation(self, translation, *args, **kwargs):
        """
        Save the translation when it's modified, or unsaved.

        :param translation: The translation
        :type translation: TranslatedFieldsModel
        :param args: Any custom arguments to pass to :func:`save`.
        :param kwargs: Any custom arguments to pass to :func:`save`.
        """
        # Translation models without any fields are also supported.
        # This is useful for parent objects that have inlines;
        # the parent object defines how many translations there are.
        if translation.is_modified or (translation.is_empty
                                       and not translation.pk):
            if not translation.master_id:  # Might not exist during first construction
                translation._state.db = self._state.db
                translation.master = self
            translation.save(*args, **kwargs)

    def safe_translation_getter(self,
                                field,
                                default=None,
                                language_code=None,
                                any_language=False):
        """
        Fetch a translated property, and return a default value
        when both the translation and fallback language are missing.

        When ``any_language=True`` is used, the function also looks
        into other languages to find a suitable value. This feature can be useful
        for "title" attributes for example, to make sure there is at least something being displayed.
        Also consider using ``field = TranslatedField(any_language=True)`` in the model itself,
        to make this behavior the default for the given field.
        """
        # By default, query via descriptor (TranslatedFieldDescriptor)
        # which also attempts the fallback language if configured to do so.
        tr_model = self

        # Extra feature: query a single field from a other translation.
        if language_code and language_code != self._current_language:
            # Try to fetch a cached value first.
            value = get_cached_translated_field(self, language_code, field)
            if value is not None:
                return value

            try:
                tr_model = self._get_translated_model(language_code)
            except TranslationDoesNotExist:
                pass  # Use 'self'

        try:
            return getattr(tr_model, field)
        except TranslationDoesNotExist:
            pass

        if any_language:
            translation = self._get_any_translated_model()
            if translation is not None:
                return getattr(translation, field, default)

        return default
Exemple #12
0
class TranslatableModel(models.Model):
    """
    Base model class to handle translations.

    All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`.
    """
    class Meta:
        abstract = True

    #: Access to the metadata of the translatable model
    _parler_meta = None

    #: Access to the language code
    language_code = LanguageCodeDescriptor()

    # change the default manager to the translation manager
    objects = TranslatableManager()

    def __init__(self, *args, **kwargs):
        # Still allow to pass the translated fields (e.g. title=...) to this function.
        translated_kwargs = {}
        current_language = None
        if kwargs:
            current_language = kwargs.pop('_current_language', None)
            for field in self._parler_meta.get_all_fields():
                try:
                    translated_kwargs[field] = kwargs.pop(field)
                except KeyError:
                    pass

        # Have the attributes available, but they can't be ready yet;
        # self._state.adding is always True at this point,
        # the QuerySet.iterator() code changes it after construction.
        self._translations_cache = None
        self._current_language = None

        # Run original Django model __init__
        super(TranslatableModel, self).__init__(*args, **kwargs)

        # Assign translated args manually.
        self._translations_cache = defaultdict(dict)
        self._current_language = normalize_language_code(
            current_language or get_language()
        )  # What you used to fetch the object is what you get.

        if translated_kwargs:
            self._set_translated_fields(self._current_language,
                                        **translated_kwargs)

    def _set_translated_fields(self, language_code=None, **fields):
        """
        Assign fields to the translated models.
        """
        objects = []  # no generator, make sure objects are all filled first
        for parler_meta, model_fields in self._parler_meta._split_fields(
                **fields):
            translation = self._get_translated_model(
                language_code=language_code,
                auto_create=True,
                meta=parler_meta)
            for field, value in six.iteritems(model_fields):
                setattr(translation, field, value)

            objects.append(translation)
        return objects

    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.
        """
        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 get_current_language(self):
        """
        Get the current language.
        """
        # not a property, so won't conflict with model fields.
        return self._current_language

    def set_current_language(self, language_code, initialize=False):
        """
        Switch the currently activate language of the object.
        """
        self._current_language = normalize_language_code(language_code
                                                         or get_language())

        # Ensure the translation is present for __get__ queries.
        if initialize:
            self._get_translated_model(use_fallback=False, auto_create=True)

    def get_fallback_language(self):
        """
        Return the fallback language code,
        which is used in case there is no translation for the currently active language.
        """
        lang_dict = get_language_settings(self._current_language)
        return lang_dict['fallback'] if lang_dict[
            'fallback'] != self._current_language else None

    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

        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:
            # 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_available_languages(self,
                                related_name=None,
                                include_unsaved=False):
        """
        Return the language codes of all translated variations.

        .. versionadded 1.2 Added the ``include_unsaved`` and ``related_name`` parameters.
        """
        meta = self._parler_meta._get_extension_by_related_name(related_name)

        prefetch = self._get_prefetched_translations(meta=meta)
        if prefetch is not None:
            db_languages = sorted(obj.language_code for obj in prefetch)
        else:
            qs = self._get_translated_queryset(meta=meta)
            db_languages = qs.values_list('language_code',
                                          flat=True).order_by('language_code')

        if include_unsaved:
            local_languages = (
                k
                for k, v in six.iteritems(self._translations_cache[meta.model])
                if v is not MISSING)
            return list(set(db_languages) | set(local_languages))
        else:
            return db_languages

    def get_translation(self, language_code, related_name=None):
        """
        Fetch the translated model
        """
        meta = self._parler_meta._get_extension_by_related_name(related_name)
        return self._get_translated_model(language_code, meta=meta)

    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 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)

        if lang_dict['fallback'] != language_code and use_fallback:
            # Jump to fallback language, return directly.
            # Don't cache under this language_code
            try:
                return self._get_translated_model(lang_dict['fallback'],
                                                  use_fallback=False,
                                                  auto_create=auto_create,
                                                  meta=meta)
            except meta.model.DoesNotExist:
                fallback_msg = " (tried fallback {0})".format(
                    lang_dict['fallback'])

        # 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 ''))

    def _get_any_translated_model(self, meta=None):
        """
        Return any available translation.
        Returns None if there are no translations at all.
        """
        if meta is None:
            meta = self._parler_meta.root

        tr_model = meta.model
        local_cache = self._translations_cache[tr_model]
        if local_cache:
            # There is already a language available in the case. No need for queries.
            # Give consistent answers if they exist.
            try:
                return local_cache.get(self._current_language, None) \
                    or local_cache.get(self.get_fallback_language(), None) \
                    or next(t for t in six.itervalues(local_cache) if t is not MISSING)  # Skip fallback markers.
            except StopIteration:
                pass

        try:
            # Use prefetch if available, otherwise perform separate query.
            prefetch = self._get_prefetched_translations(meta=meta)
            if prefetch is not None:
                translation = prefetch[0]  # Already a list
            else:
                translation = self._get_translated_queryset(meta=meta)[0]
        except IndexError:
            return None
        else:
            local_cache[translation.language_code] = translation
            _cache_translation(translation)
            return translation

    def _get_translated_queryset(self, meta=None):
        """
        Return the queryset that points to the translated model.
        If there is a prefetch, it can be read from this queryset.
        """
        # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache.
        if meta is None:
            meta = self._parler_meta.root

        accessor = getattr(self, meta.rel_name)
        if django.VERSION >= (1, 6):
            # Call latest version
            return accessor.get_queryset()
        else:
            # Must call RelatedManager.get_query_set() and avoid calling a custom get_queryset()
            # method for packages with Django 1.6/1.7 compatibility.
            return accessor.get_query_set()

    def _get_prefetched_translations(self, meta=None):
        """
        Return the queryset with prefetch results.
        """
        if meta is None:
            meta = self._parler_meta.root

        related_name = meta.rel_name
        try:
            # Read the list directly, avoid QuerySet construction.
            # Accessing self._get_translated_queryset(parler_meta)._prefetch_done is more expensive.
            return self._prefetched_objects_cache[related_name]
        except (AttributeError, KeyError):
            return None

    def save(self, *args, **kwargs):
        super(TranslatableModel, self).save(*args, **kwargs)

        # Makes no sense to add these for translated model
        # Even worse: mptt 0.7 injects this parameter when it avoids updating the lft/rgt fields,
        # but that misses all the translated fields.
        kwargs.pop('update_fields', None)
        self.save_translations(*args, **kwargs)

    def delete(self, using=None):
        _delete_cached_translations(self)
        super(TranslatableModel, self).delete(using)

    def validate_unique(self, exclude=None):
        """
        Also validate the unique_together of the translated model.
        """
        # This is called from ModelForm._post_clean() or Model.full_clean()
        errors = {}
        try:
            super(TranslatableModel, self).validate_unique(exclude=exclude)
        except ValidationError as e:
            errors = e.message_dict  # Django 1.5 + 1.6 compatible

        for local_cache in six.itervalues(self._translations_cache):
            for translation in six.itervalues(local_cache):
                if translation is MISSING:  # Skip fallback markers
                    continue

                try:
                    translation.validate_unique(exclude=exclude)
                except ValidationError as e:
                    errors.update(e.message_dict)

        if errors:
            raise ValidationError(errors)

    def save_translations(self, *args, **kwargs):
        """
        The method to save all translations.
        This can be overwritten to implement any custom additions.
        This method calls :func:`save_translation` for every fetched language.

        :param args: Any custom arguments to pass to :func:`save`.
        :param kwargs: Any custom arguments to pass to :func:`save`.
        """
        # Copy cache, new objects (e.g. fallbacks) might be fetched if users override save_translation()
        # Not looping over the cache, but using _parler_meta so the translations are processed in the order of inheritance.
        local_caches = self._translations_cache.copy()
        for meta in self._parler_meta:
            local_cache = local_caches[meta.model]
            translations = list(local_cache.values())

            # Save all translated objects which were fetched.
            # This also supports switching languages several times, and save everything in the end.
            for translation in translations:
                if translation is MISSING:  # Skip fallback markers
                    continue

                self.save_translation(translation, *args, **kwargs)

    def save_translation(self, translation, *args, **kwargs):
        """
        Save the translation when it's modified, or unsaved.

        .. note::

           When a derived model provides additional translated fields,
           this method receives both the original and extended translation.
           To distinguish between both objects, check for ``translation.related_name``.

        :param translation: The translation
        :type translation: TranslatedFieldsModel
        :param args: Any custom arguments to pass to :func:`save`.
        :param kwargs: Any custom arguments to pass to :func:`save`.
        """
        if self.pk is None or self._state.adding:
            raise RuntimeError(
                "Can't save translations when the master object is not yet saved."
            )

        # Translation models without any fields are also supported.
        # This is useful for parent objects that have inlines;
        # the parent object defines how many translations there are.
        if translation.is_modified or (translation.is_empty
                                       and not translation.pk):
            if not translation.master_id:  # Might not exist during first construction
                translation._state.db = self._state.db
                translation.master = self
            translation.save(*args, **kwargs)

    def safe_translation_getter(self,
                                field,
                                default=None,
                                language_code=None,
                                any_language=False):
        """
        Fetch a translated property, and return a default value
        when both the translation and fallback language are missing.

        When ``any_language=True`` is used, the function also looks
        into other languages to find a suitable value. This feature can be useful
        for "title" attributes for example, to make sure there is at least something being displayed.
        Also consider using ``field = TranslatedField(any_language=True)`` in the model itself,
        to make this behavior the default for the given field.
        """
        meta = self._parler_meta._get_extension_by_field(field)

        # Extra feature: query a single field from a other translation.
        if language_code and language_code != self._current_language:
            try:
                tr_model = self._get_translated_model(language_code,
                                                      meta=meta,
                                                      use_fallback=True)
                return getattr(tr_model, field)
            except TranslationDoesNotExist:
                pass
        else:
            # By default, query via descriptor (TranslatedFieldDescriptor)
            # which also attempts the fallback language if configured to do so.
            try:
                return getattr(self, field)
            except TranslationDoesNotExist:
                pass

        if any_language:
            translation = self._get_any_translated_model(meta=meta)
            if translation is not None:
                return getattr(translation, field, default)

        return default
Exemple #13
0
class ScooterModel(Product):
    """
    A generic smart phone model, which must be concretized by a model `SmartPhone` - see below.
    """
    BATTERY_TYPES = (
        (1, "LG battery"),
        (2, "Chinese battery"),
    )
    #~ WIFI_CONNECTIVITY = (
    #~ (1, "802.11 b/g/n"),
    #~ )
    #~ BLUETOOTH_CONNECTIVITY = (
    #~ (1, "Bluetooth 4.0"),
    #~ (2, "Bluetooth 3.0"),
    #~ (3, "Bluetooth 2.1"),
    #~ )
    battery_type = models.PositiveSmallIntegerField(
        _("Battery type"),
        choices=BATTERY_TYPES,
    )

    battery_capacity = models.DecimalField(
        _("Battery capacity"),
        max_digits=3,
        decimal_places=1,
        help_text=_("Battery capacity in Ah"),
        null=True,
    )

    #~ ram_storage = models.PositiveIntegerField(
    #~ _("RAM"),
    #~ help_text=_("RAM storage in MB"),
    #~ )

    #~ wifi_connectivity = models.PositiveIntegerField(
    #~ _("WiFi"),
    #~ choices=WIFI_CONNECTIVITY,
    #~ help_text=_("WiFi Connectivity"),
    #~ )

    #~ bluetooth = models.PositiveIntegerField(
    #~ _("Bluetooth"),
    #~ choices=BLUETOOTH_CONNECTIVITY,
    #~ help_text=_("Bluetooth Connectivity"),
    #~ )

    bluetooth = models.BooleanField(
        _("Bluetooth"),
        default=True,
        help_text=_("Bluetooth Connectivity"),
    )

    operating_system = models.ForeignKey(
        OperatingSystem,
        verbose_name=_("Operating System"),
    )

    width = models.DecimalField(
        _("Width"),
        max_digits=5,
        decimal_places=0,
        help_text=_("Width in cm"),
    )

    height = models.DecimalField(
        _("Height"),
        max_digits=5,
        decimal_places=0,
        help_text=_("Height in cm"),
    )
    depth = models.DecimalField(
        _("Depth"),
        max_digits=5,
        decimal_places=0,
        help_text=_("Height in cm"),
    )

    weight = models.DecimalField(
        _("Weight"),
        max_digits=5,
        decimal_places=1,
        help_text=_("Weight in kg"),
    )

    #~ screen_size = models.DecimalField(
    #~ _("Screen size"),
    #~ max_digits=4,
    #~ decimal_places=2,
    #~ help_text=_("Diagonal screen size in inch"),
    #~ )
    max_speed = models.DecimalField(
        _("Maxumum speed"),
        max_digits=4,
        decimal_places=0,
        help_text=_("Maxumum speed in km/h"),
    )
    mileage = models.DecimalField(
        _("Maxumum mileage"),
        max_digits=4,
        decimal_places=0,
        help_text=_("Maxumum mileage per one battery charge in km"),
    )

    power = models.DecimalField(
        _("Motor power"),
        max_digits=4,
        decimal_places=0,
        help_text=_("Motor power in km"),
    )

    multilingual = TranslatedFields(description=HTMLField(
        verbose_name=_("Description"),
        configuration='CKEDITOR_SETTINGS_DESCRIPTION',
        help_text=_(
            "Full description used in the catalog's detail view of Scooters."),
    ), )

    default_manager = TranslatableManager()

    #~ placeholder = PlaceholderField('product_details')
    class Meta:
        verbose_name = _("Electric Scooter")
        verbose_name_plural = _("Electric Scooters")

    def get_price(self, request):
        """
        Return the starting price for instances of this smart phone model.
        """
        if not hasattr(self, '_price'):
            if self.variants.exists():
                currency = self.variants.first().unit_price.currency
                aggr = self.variants.aggregate(models.Min('unit_price'))
                self._price = MoneyMaker(currency)(aggr['unit_price__min'])
            else:
                self._price = Money()
        return self._price

    def is_in_cart(self, cart, watched=False, **kwargs):
        from shop.models.cart import CartItemModel
        try:
            product_code = kwargs['product_code']
        except KeyError:
            return
        cart_item_qs = CartItemModel.objects.filter(cart=cart, product=self)
        for cart_item in cart_item_qs:
            if cart_item.product_code == product_code:
                return cart_item

    def get_product_variant(self, **kwargs):
        try:
            return self.variants.get(**kwargs)
        except ScooterVariant.DoesNotExist as e:
            raise ScooterModel.DoesNotExist(e)
Exemple #14
0
class TranslatableModel(models.Model):
    """
    Base model class to handle translations.
    """

    # Consider these fields "protected" or "internal" attributes.
    # Not part of the public API, but used internally in the class hierarchy.
    _translations_field = None
    _translations_model = None

    language_code = LanguageCodeDescriptor()

    # change the default manager to the translation manager
    objects = TranslatableManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        # Still allow to pass the translated fields (e.g. title=...) to this function.
        translated_kwargs = {}
        current_language = None
        if kwargs:
            current_language = kwargs.pop('_current_language', None)
            for field in self._translations_model.get_translated_fields():
                try:
                    translated_kwargs[field] = kwargs.pop(field)
                except KeyError:
                    pass

        # Run original Django model __init__
        super(TranslatableModel, self).__init__(*args, **kwargs)

        self._translations_cache = {}
        self._current_language = normalize_language_code(
            current_language or get_language()
        )  # What you used to fetch the object is what you get.

        # Assign translated args manually.
        if translated_kwargs:
            translation = self._get_translated_model(auto_create=True)
            for field, value in translated_kwargs.iteritems():
                setattr(translation, field, value)

    def get_current_language(self):
        """
        Get the current language.
        """
        # not a property, so won't conflict with model fields.
        return self._current_language

    def set_current_language(self, language_code, initialize=False):
        """
        Switch the currently activate language of the object.
        """
        self._current_language = normalize_language_code(language_code
                                                         or get_language())

        # Ensure the translation is present for __get__ queries.
        if initialize:
            self._get_translated_model(use_fallback=False, auto_create=True)

    def get_fallback_language(self):
        """
        Return the fallback language code,
        which is used in case there is no translation for the currently active language.
        """
        lang_dict = get_language_settings(self._current_language)
        return lang_dict['fallback'] if lang_dict[
            'fallback'] != self._current_language else None

    def has_translation(self, language_code=None):
        """
        Return whether a translation for the given language exists.
        Defaults to the current language code.
        """
        if language_code is None:
            language_code = self._current_language

        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[language_code] is not None
        except KeyError:
            try:
                # Fetch from DB, fill the cache.
                self._get_translated_model(language_code,
                                           use_fallback=False,
                                           auto_create=False)
            except self._translations_model.DoesNotExist:
                return False
            else:
                return True

    def get_available_languages(self):
        """
        Return the language codes of all translated variations.
        """
        return self._translations_model.objects.using(self._state.db).filter(
            master=self).values_list('language_code',
                                     flat=True).order_by('language_code')

    def _get_translated_model(self,
                              language_code=None,
                              use_fallback=False,
                              auto_create=False):
        """
        Fetch the translated fields model.
        """
        if not self._translations_model or not self._translations_field:
            raise ImproperlyConfigured(
                "No translation is assigned to the current model!")

        if not language_code:
            language_code = self._current_language

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

            # If cached object indicates the language doesn't exist, need to query the fallback.
            if object is not None:
                return object
        except KeyError:
            # 2. No cache, need to query
            # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache.
            # Check that this object already exists, would be pointless otherwise to check for a translation.
            if not self._state.adding:
                # 2.1, fetch from memcache
                object = get_cached_translation(self, language_code)
                if object is not None:
                    # Track in local cache
                    self._translations_cache[language_code] = object
                    return object
                else:
                    # 2.2, fetch from database
                    accessor = getattr(self, self._translations_field)
                    try:
                        object = accessor.get(language_code=language_code)
                    except self._translations_model.DoesNotExist:
                        pass
                    else:
                        self._translations_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)
            object = self._translations_model(
                language_code=language_code,
                master=self  # ID might be None at this point
            )
            self._translations_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 use_fallback and (lang_dict['fallback'] != language_code):
            # Jump to fallback language, return directly.
            # Don't cache under this language_code
            self._translations_cache[
                language_code] = None  # explicit marker that language query was tried before.
            try:
                return self._get_translated_model(lang_dict['fallback'],
                                                  use_fallback=False,
                                                  auto_create=auto_create)
            except self._translations_model.DoesNotExist:
                fallback_msg = u" (tried fallback {0})".format(
                    lang_dict['fallback'])

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

    def _get_any_translated_model(self):
        """
        Return any available translation.
        Returns None if there are no translations at all.
        """
        if self._translations_cache:
            # There is already a language available in the case. No need for queries.
            # Give consistent answers if they exist.
            try:
                return self._translations_cache.get(self._current_language, None) \
                    or self._translations_cache.get(self.get_fallback_language(), None) \
                    or next(t for t in self._translations_cache.itervalues() if t if not None)  # Skip fallback markers.
            except StopIteration:
                pass

        try:
            translation = self._translations_model.objects.using(
                self._state.db).filter(master=self)[0]
        except IndexError:
            return None
        else:
            self._translations_cache[translation.language_code] = translation
            _cache_translation(translation)
            return translation

    def save(self, *args, **kwargs):
        super(TranslatableModel, self).save(*args, **kwargs)
        self.save_translations(*args, **kwargs)

    def delete(self, using=None):
        _delete_cached_translations(self)
        super(TranslatableModel, self).delete(using)

    def save_translations(self, *args, **kwargs):
        # Save all translated objects which were fetched.
        # This also supports switching languages several times, and save everything in the end.
        for translation in self._translations_cache.itervalues():
            if translation is None:  # Skip fallback markers
                continue

            self.save_translation(translation, *args, **kwargs)

    def save_translation(self, translation, *args, **kwargs):
        # Translation models without any fields are also supported.
        # This is useful for parent objects that have inlines;
        # the parent object defines how many translations there are.
        if translation.is_modified or (translation.is_empty
                                       and not translation.pk):
            if not translation.master_id:  # Might not exist during first construction
                translation._state.db = self._state.db
                translation.master = self
            translation.save(*args, **kwargs)

    def safe_translation_getter(self, field, default=None, any_language=False):
        """
        Fetch a translated property, and return a default value
        when both the translation and fallback language are missing.

        When ``any_language=True`` is used, the function also looks
        into other languages to find a suitable value. This feature can be useful
        for "title" attributes for example, to make sure there is at least something being displayed.
        Also consider using ``field = TranslatedField(any_language=True)`` in the model itself,
        to make this behavior the default for the given field.
        """
        try:
            return getattr(self, field)
        except TranslationDoesNotExist:
            pass

        if any_language:
            translation = self._get_any_translated_model()
            if translation is not None:
                return getattr(translation, field, default)

        return default
Exemple #15
0
class Migration(migrations.Migration):

    dependencies = [
        ('fluent_contents', '0001_initial'),
        ('geo', '0004_auto_20160929_0817'),
        ('cms', '0028_merge_20171006_1622'),
    ]

    operations = [
        migrations.CreateModel(
            name='CategoriesContent',
            fields=[
                ('contentitem_ptr',
                 models.OneToOneField(
                     auto_created=True,
                     on_delete=django.db.models.deletion.CASCADE,
                     parent_link=True,
                     primary_key=True,
                     serialize=False,
                     to='fluent_contents.ContentItem')),
                ('title', models.CharField(blank=True,
                                           max_length=40,
                                           null=True)),
                ('sub_title',
                 models.CharField(blank=True, max_length=70, null=True)),
                ('categories',
                 models.ManyToManyField(
                     db_table=b'cms_categoriescontent_categories',
                     to='categories.Category')),
            ],
            options={
                'db_table': 'contentitem_cms_categoriescontent',
                'verbose_name': 'Categories',
            },
            bases=('fluent_contents.contentitem', ),
            managers=[
                ('objects', ContentItemManager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.CreateModel(
            name='LocationsContent',
            fields=[
                ('contentitem_ptr',
                 models.OneToOneField(
                     auto_created=True,
                     on_delete=django.db.models.deletion.CASCADE,
                     parent_link=True,
                     primary_key=True,
                     serialize=False,
                     to='fluent_contents.ContentItem')),
                ('title', models.CharField(blank=True,
                                           max_length=40,
                                           null=True)),
                ('sub_title',
                 models.CharField(blank=True, max_length=70, null=True)),
                ('locations',
                 models.ManyToManyField(
                     db_table=b'cms_locationscontent_locations',
                     to='geo.Location')),
            ],
            options={
                'db_table': 'contentitem_cms_locationscontent',
                'verbose_name': 'Locations',
            },
            bases=('fluent_contents.contentitem', ),
            managers=[
                ('objects', ContentItemManager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.CreateModel(
            name='Slide',
            fields=[
                ('id',
                 models.AutoField(auto_created=True,
                                  primary_key=True,
                                  serialize=False,
                                  verbose_name='ID')),
            ],
            options={
                'abstract': False,
            },
            bases=(models.Model, ),
            managers=[
                ('objects', TranslatableManager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.CreateModel(
            name='SlidesContent',
            fields=[
                ('contentitem_ptr',
                 models.OneToOneField(
                     auto_created=True,
                     on_delete=django.db.models.deletion.CASCADE,
                     parent_link=True,
                     primary_key=True,
                     serialize=False,
                     to='fluent_contents.ContentItem')),
            ],
            options={
                'db_table': 'contentitem_cms_slidescontent',
                'verbose_name': 'Slides',
            },
            bases=('fluent_contents.contentitem', ),
            managers=[
                ('objects', ContentItemManager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.CreateModel(
            name='SlideTranslation',
            fields=[
                ('id',
                 models.AutoField(auto_created=True,
                                  primary_key=True,
                                  serialize=False,
                                  verbose_name='ID')),
                ('language_code',
                 models.CharField(db_index=True,
                                  max_length=15,
                                  verbose_name='Language')),
                ('tab_text',
                 models.CharField(
                     help_text='This is shown on tabs beneath the banner.',
                     max_length=100,
                     verbose_name='Tab text')),
                ('title',
                 models.CharField(blank=True,
                                  max_length=100,
                                  verbose_name='Title')),
                ('body', models.TextField(blank=True,
                                          verbose_name='Body text')),
                ('image',
                 bluebottle.utils.fields.ImageField(
                     blank=True,
                     max_length=255,
                     null=True,
                     upload_to=b'banner_slides/',
                     verbose_name='Image')),
                ('background_image',
                 bluebottle.utils.fields.ImageField(
                     blank=True,
                     max_length=255,
                     null=True,
                     upload_to=b'banner_slides/',
                     verbose_name='Background image')),
                ('video_url',
                 models.URLField(blank=True,
                                 default=b'',
                                 max_length=100,
                                 verbose_name='Video url')),
                ('link_text',
                 models.CharField(
                     blank=True,
                     help_text=
                     'This is the text on the button inside the banner.',
                     max_length=400,
                     verbose_name='Link text')),
                ('link_url',
                 models.CharField(
                     blank=True,
                     help_text=
                     'This is the link for the button inside the banner.',
                     max_length=400,
                     verbose_name='Link url')),
                ('master',
                 models.ForeignKey(editable=False,
                                   null=True,
                                   on_delete=django.db.models.deletion.CASCADE,
                                   related_name='translations',
                                   to='cms.Slide')),
            ],
            options={
                'managed': True,
                'db_table': 'cms_slide_translation',
                'db_tablespace': '',
                'default_permissions': (),
                'verbose_name': 'slide Translation',
            },
        ),
        migrations.CreateModel(
            name='Step',
            fields=[
                ('id',
                 models.AutoField(auto_created=True,
                                  primary_key=True,
                                  serialize=False,
                                  verbose_name='ID')),
                ('image',
                 bluebottle.utils.fields.ImageField(blank=True,
                                                    max_length=255,
                                                    null=True,
                                                    upload_to=b'step_images/',
                                                    verbose_name='Image')),
            ],
            options={
                'abstract': False,
            },
            bases=(models.Model, ),
        ),
        migrations.CreateModel(
            name='StepsContent',
            fields=[
                ('contentitem_ptr',
                 models.OneToOneField(
                     auto_created=True,
                     on_delete=django.db.models.deletion.CASCADE,
                     parent_link=True,
                     primary_key=True,
                     serialize=False,
                     to='fluent_contents.ContentItem')),
                ('title', models.CharField(blank=True,
                                           max_length=40,
                                           null=True)),
                ('sub_title',
                 models.CharField(blank=True, max_length=70, null=True)),
                ('action_text',
                 models.CharField(blank=True,
                                  default='Start your own project',
                                  max_length=40,
                                  null=True)),
                ('action_link',
                 models.CharField(blank=True,
                                  default=b'/start-project',
                                  max_length=100,
                                  null=True)),
            ],
            options={
                'db_table': 'contentitem_cms_stepscontent',
                'verbose_name': 'Steps',
            },
            bases=('fluent_contents.contentitem', ),
            managers=[
                ('objects', ContentItemManager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.CreateModel(
            name='StepTranslation',
            fields=[
                ('id',
                 models.AutoField(auto_created=True,
                                  primary_key=True,
                                  serialize=False,
                                  verbose_name='ID')),
                ('language_code',
                 models.CharField(db_index=True,
                                  max_length=15,
                                  verbose_name='Language')),
                ('header',
                 models.CharField(max_length=100, verbose_name='Header')),
                ('text', models.CharField(max_length=400,
                                          verbose_name='Text')),
                ('master',
                 models.ForeignKey(editable=False,
                                   null=True,
                                   on_delete=django.db.models.deletion.CASCADE,
                                   related_name='translations',
                                   to='cms.Step')),
            ],
            options={
                'managed': True,
                'db_table': 'cms_step_translation',
                'db_tablespace': '',
                'default_permissions': (),
                'verbose_name': 'step Translation',
            },
        ),
        migrations.RemoveField(
            model_name='metric',
            name='block',
        ),
        migrations.RemoveField(
            model_name='metricscontent',
            name='contentitem_ptr',
        ),
        migrations.AlterUniqueTogether(
            name='metrictranslation',
            unique_together=set([]),
        ),
        migrations.RemoveField(
            model_name='metrictranslation',
            name='master',
        ),
        migrations.DeleteModel(name='Metric', ),
        migrations.DeleteModel(name='MetricsContent', ),
        migrations.DeleteModel(name='MetricTranslation', ),
        migrations.AddField(
            model_name='step',
            name='block',
            field=models.ForeignKey(
                on_delete=django.db.models.deletion.CASCADE,
                related_name='steps',
                to='cms.StepsContent'),
        ),
        migrations.AddField(
            model_name='slide',
            name='block',
            field=models.ForeignKey(
                on_delete=django.db.models.deletion.CASCADE,
                related_name='slides',
                to='cms.SlidesContent'),
        ),
        migrations.AlterUniqueTogether(
            name='steptranslation',
            unique_together=set([('language_code', 'master')]),
        ),
        migrations.AlterUniqueTogether(
            name='slidetranslation',
            unique_together=set([('language_code', 'master')]),
        )
    ]