Exemplo n.º 1
0
class Product(TranslatableModel):
    name = TranslatedField(any_language=True)
    slug = TranslatedField(any_language=True)
    description = TranslatedField(any_language=True)

    translations = TranslatedFields(
        name=models.CharField(max_length=200, db_index=True),
        slug=models.SlugField(max_length=200, db_index=True),
        description=models.TextField(blank=True)
    )
   # master = models.ForeignKey(Category, related_name='products', null=True, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    #description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField()
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-created',)
        #index_together = (('id', 'slug'),)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_detail', args=[self.id, self.slug])
Exemplo n.º 2
0
class ShippingRegion(PolymorphicTranslatableShuupModel):
    name = TranslatedField(any_language=True)
    description = TranslatedField()
    priority = models.IntegerField(verbose_name=_("priority"),
                                   default=0,
                                   help_text=_("A higher number means this region is most "
                                               "important than other with a lower priority"))

    base_translations = TranslatedFields(
        name=models.CharField(max_length=100, verbose_name=_("name")),
        description=models.CharField(max_length=120, blank=True, verbose_name=_("description"))
    )

    class Meta:
        verbose_name = _("shipping region")
        verbose_name_plural = _("shipping regions")

    def __str__(self):
        return self.name

    def is_compatible_with(self, source):
        """
        Returns true if this region is compatible for given source.

        This method should especially check the source's delivery address
        and return if this region is compatible

        :type source: shuup.core.order_creator.OrderSource
        :rtype: bool
        :return: whether this region is available for a source
        """
        return False
Exemplo n.º 3
0
class AbstractTranslatableEntryBase(TranslatableModel,
                                    AbstractSharedEntryBaseMixin):
    """
    The translatable abstract entry base model.
    """
    # Update tagging marker
    is_translatable_model = True

    title = TranslatedField(any_language=True)
    slug = TranslatedField()

    # Correct ordering of base classes should avoid getting the TranslatableModel.manager
    # to override the base classes. However, this caused other errors (django-parler not receiving abstract=True),
    # so kept the ordering in human-logical form, and restore the manager here.
    objects = TranslatableEntryManager()

    class Meta:
        abstract = True

    @property
    def default_url(self):
        """
        Make sure that the URL is translated in the current language.
        """
        with switch_language(self):
            return super(AbstractTranslatableEntryBase, self).default_url
Exemplo n.º 4
0
    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,
        ), {})
Exemplo n.º 5
0
    class Commodity(CMSPageReferenceMixin, TranslatableModelMixin,
                    BaseProduct):
        """
        Generic Product Commodity to be used whenever the merchant does not require product specific
        attributes and just required a placeholder field to add arbitrary data.
        """
        # common product fields
        product_code = models.CharField(_("Product code"),
                                        max_length=255,
                                        unique=True)
        unit_price = MoneyField(_("Unit price"),
                                decimal_places=3,
                                help_text=_("Net price for this product"))

        # controlling the catalog
        order = models.PositiveIntegerField(verbose_name=_("Sort by"),
                                            db_index=True)
        cms_pages = models.ManyToManyField(
            'cms.Page',
            through=ProductPage,
            help_text=_("Choose list view this product shall appear on."))
        sample_image = image.FilerImageField(
            verbose_name=_("Sample Image"),
            blank=True,
            null=True,
            help_text=_("Sample image used in the catalog's list view."))
        show_breadcrumb = models.BooleanField(
            _("Show Breadcrumb"),
            default=True,
            help_text=_(
                "Shall the detail page show the product's breadcrumb."))
        placeholder = PlaceholderField("Commodity Details")

        # translatable fields for the catalog's list- and detail views
        product_name = TranslatedField()
        slug = TranslatedField()
        caption = TranslatedField()

        # filter expression used to search for a product item using the Select2 widget
        lookup_fields = (
            'product_code__startswith',
            'product_name__icontains',
        )

        objects = ProductManager()

        class Meta:
            app_label = app_settings.APP_LABEL
            ordering = ('order', )
            verbose_name = _("Commodity")
            verbose_name_plural = _("Commodities")

        def __str__(self):
            return self.product_code

        def get_price(self, request):
            return self.unit_price
Exemplo n.º 6
0
class ApiScope(AutoFilledIdentifier, ImmutableFields, TranslatableModel):
    immutable_fields = ['api', 'specifier', 'identifier']

    identifier = models.CharField(
        max_length=150,
        unique=True,
        editable=False,
        verbose_name=_("identifier"),
        help_text=_(
            "The scope identifier as known by the API application "
            "(i.e. the Resource Server).  Generated automatically from "
            "the API identifier and the scope specifier."))
    api = models.ForeignKey(Api,
                            related_name='scopes',
                            on_delete=models.CASCADE,
                            verbose_name=_("API"),
                            help_text=_("The API that this scope is for."))
    specifier = models.CharField(
        max_length=30,
        blank=True,
        validators=[alphanumeric_validator],
        verbose_name=_("specifier"),
        help_text=_("If there is a need for multiple scopes per API, "
                    "this can specify what kind of scope this is about, "
                    "e.g. \"readonly\".  For general API scope "
                    "just leave this empty."))
    name = TranslatedField()
    description = TranslatedField()
    allowed_apps = models.ManyToManyField(
        Client,
        related_name='granted_api_scopes',
        verbose_name=_("allowed applications"),
        help_text=_("Select client applications which are allowed "
                    "to get access to this API scope."))

    objects = ApiScopeQuerySet.as_manager()

    class Meta:
        unique_together = [('api', 'specifier')]
        verbose_name = _("API scope")
        verbose_name_plural = _("API scopes")

    @property
    def relative_identifier(self):
        return '{api_name}{suffix}'.format(
            api_name=self.api.name,
            suffix=('.' + self.specifier if self.specifier else ''))

    def _generate_identifier(self):
        return '{api_identifier}{suffix}'.format(
            api_identifier=self.api.identifier,
            suffix=('.' + self.specifier if self.specifier else ''))
Exemplo n.º 7
0
class SmartCard(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):
    product_name = models.CharField(max_length=255,
                                    verbose_name=_("Product Name"))
    slug = models.SlugField(verbose_name=_("Slug"))
    unit_price = MoneyField(_("Unit price"),
                            decimal_places=3,
                            help_text=_("Net price for this product"))
    description = TranslatedField()

    # product properties
    manufacturer = models.ForeignKey(Manufacturer,
                                     verbose_name=_("Manufacturer"))
    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"))

    # controlling the catalog
    order = models.PositiveIntegerField(verbose_name=_("Sort by"),
                                        db_index=True)
    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."))
    images = models.ManyToManyField('filer.Image', through=ProductImage)

    objects = ProductManager()

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = (
        'product_code__startswith',
        'product_name__icontains',
    )

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")
        ordering = ('order', )

    objects = ProductManager()

    def __str__(self):
        return self.product_name

    @property
    def sample_image(self):
        return self.images.first()

    def get_price(self, request):
        return self.unit_price
Exemplo n.º 8
0
    def contribute_translations(cls, shared_model):
        """
        Add the proxy attributes to the shared model.
        """
        # Link the translated fields model to the shared model.
        shared_model._translations_model = cls
        shared_model._translations_field = cls.master.field.rel.related_name

        # Assign the proxy fields
        for name in cls.get_translated_fields():
            try:
                # Check if the field already exists.
                # Note that the descriptor even proxies this request, so it should return our field.
                field = getattr(shared_model, name)
            except AttributeError:
                # Add the proxy field for the shared field.
                TranslatedField().contribute_to_class(shared_model, name)
            else:
                if not isinstance(field,
                                  (models.Field, TranslatedFieldDescriptor)):
                    raise TypeError(
                        "The model '{0}' already has a field named '{1}'".
                        format(shared_model.__name__, 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('DoesNotExist', (
            TranslationDoesNotExist,
            shared_model.DoesNotExist,
            cls.DoesNotExist,
        ), {})
Exemplo n.º 9
0
class FaqCategory(FaqBaseModel):
    """
    Topic of the FAQ
    """
    # Be compatible with django-orderable table layout,
    # unfortunately, there isn't a good canonical version of it yet.
    order = models.PositiveIntegerField(db_index=True, blank=True, null=True)

    title = TranslatedField(any_language=True)
    translations = TranslatedFields(
        title = models.CharField(_("title"), max_length=200),
        slug = models.SlugField(_("slug")),
    )

    objects = FaqCategoryManager()

    class Meta:
        verbose_name = _("FAQ Category")
        verbose_name_plural = _("FAQ Categories")
        ordering = ('order', 'creation_date')

    def __unicode__(self):
        # self.title is configured with any_language=True, so always returns a value.
        return self.title

    def get_relative_url(self):
        return u'{0}/'.format(self.slug)

    @property
    def faq_questions(self):
        """
        Fetch the active FAQ questions in this category.
        """
        return self.questions.active_translations()
Exemplo n.º 10
0
class HtmlPage(Page):
    """
    The ``HtmlPage`` is the base for all page types that display HTML.
    This is a proxy model, which adds translatable SEO fields and a customizable title.

    .. versionchanged 0.9: This model used to be abstract, now it's a proxy model because all fields are translated.
    """
    # Just to be explicit
    meta_keywords = TranslatedField()
    meta_description = TranslatedField()
    meta_title = TranslatedField()

    # SEO fields, the underlying HtmlPageTranslation model can be created dynamically.
    seo_translations = TranslatedFields(
        meta_keywords = models.CharField(_('keywords'), max_length=255, blank=True, null=True),
        meta_description = models.CharField(_('description'), max_length=255, blank=True, null=True),
        meta_title = models.CharField(_('page title'), max_length=255, blank=True, null=True, help_text=_("When this field is not filled in, the menu title text will be used.")),
    )

    class Meta:
        app_label = 'fluent_pages'
        proxy = True
        verbose_name_plural = _('Pages')

    @property
    def meta_robots(self):
        """
        The value for the ``<meta name="robots" content=".."/>`` tag.
        It defaults to ``noindex`` when :attr:`in_sitemaps` is ``False``.
        """
        # Also exclude from crawling if removed from sitemaps.
        if not self.in_sitemaps:
            return 'noindex'
        else:
            return None

    def delete(self, *args, **kwargs):
        # Fix deleting pages, the Django 1.6 collector doesn't delete the HtmlPageTranslation model,
        # because the FK points to a proxy model. This is a workaround for:
        # https://code.djangoproject.com/ticket/18491
        # https://code.djangoproject.com/ticket/16128
        # By using .all() we avoid the get_query_set/get_queryset() difference.
        self.seo_translations.all().delete()  # Accesses RelatedManager

        # Continue regular delete.
        super(HtmlPage, self).delete(*args, **kwargs)
Exemplo n.º 11
0
class AnyLanguageModel(TranslatableModel):
    shared = models.CharField(max_length=200, default='')
    tr_title = TranslatedField(any_language=True)

    translations = TranslatedFields(tr_title=models.CharField(max_length=200))

    def __str__(self):
        return self.tr_title
Exemplo n.º 12
0
class Product(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):
    """
    Base class to describe a polymorphic product. Here we declare common fields available in all of
    our different product types. These common fields are also used to build up the view displaying
    a list of all products.
    """
    product_name = models.CharField(
        _("Product Name"),
        max_length=255,
    )

    slug = models.SlugField(
        _("Slug"),
        unique=True,
    )

    caption = TranslatedField()

    # common product properties
    manufacturer = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
        verbose_name=_("Manufacturer"),
    )

    # controlling the catalog
    order = models.PositiveIntegerField(
        _("Sort by"),
        db_index=True,
    )

    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."),
    )

    images = models.ManyToManyField(
        'filer.Image',
        through=ProductImage,
    )

    class Meta:
        ordering = ('order',)
        verbose_name = _("Product")
        verbose_name_plural = _("Products")

    objects = ProductManager()

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = ['product_name__icontains']

    def __str__(self):
        return self.product_name

    @property
    def sample_image(self):
        return self.images.first()
Exemplo n.º 13
0
class CleanFieldModel(TranslatableModel):
    shared = CleanCharField(max_length=200, default='')
    tr_title = TranslatedField()

    def __str__(self):
        return self.tr_title

    def clean(self):
        self.shared += "_cleanshared"
Exemplo n.º 14
0
class PersonTitle(TranslatableModel):
    """
    PersonTitle define i18n enabled people titles and their abbreviations
    Instances of this models should only be created by CMS administrators
    """

    title = TranslatedField()
    abbreviation = TranslatedField()

    class Meta:
        verbose_name = _("person title")

    def __str__(self):
        """Human representation of a person title"""
        return "{model}: {title} ({abbreviation})".format(
            model=self._meta.verbose_name.title(),
            title=self.title,
            abbreviation=self.abbreviation,
        )
Exemplo n.º 15
0
class Category(TranslatableModel):
    name = TranslatedField(any_language=True)
    slug = TranslatedField(any_language=True)

    translations = TranslatedFields(
        name=models.CharField(max_length=200, db_index=True),
        slug=models.SlugField(max_length=200, db_index=True, unique=True),
    )

    class Meta:
        #ordering = ('name',)
        verbose_name = "category"
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_list_by_category', args=[self.slug])
Exemplo n.º 16
0
    def contribute_translations(cls, shared_model):
        """
        Add the proxy attributes to the shared model.
        """
        # Link the translated fields model to the shared model.
        shared_model._translations_model = cls
        shared_model._translations_field = cls.master.field.rel.related_name

        # Assign the proxy fields
        for name in cls.get_translated_fields():
            try:
                # Check if the field already exists.
                # Note that the descriptor even proxies this request, so it should return our field.
                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,
        ), {})
Exemplo n.º 17
0
class FaqQuestion(TagsMixin, FaqBaseModel):
    """
    Category in the FAQ.
    """
    # This is a separate model instead of using django-categories because:
    # - content needs to be placed on the category.
    # - the title and slug can be translated!

    # Be compatible with django-orderable table layout,
    # unfortunately, there isn't a good canonical version of it yet.
    order = models.PositiveIntegerField(db_index=True, blank=True, null=True)

    title = TranslatedField(any_language=True)
    translations = TranslatedFields(
        title=models.CharField(_("title"), max_length=200),
        slug=models.SlugField(_("slug")),
    )
    contents = PlaceholderField("faq_answer", verbose_name=_("answer"))
    contentitem_set = ContentItemRelation(
    )  # this makes sure the admin can find all deleted objects too.

    # Organisation
    category = models.ForeignKey(FaqCategory,
                                 verbose_name=_("Category"),
                                 related_name='questions')

    objects = FaqQuestionManager()

    class Meta:
        verbose_name = _("FAQ Question")
        verbose_name_plural = _("FAQ Questions")
        ordering = ('order', 'creation_date')

    def __str__(self):
        # self.title is configured with any_language=True, so always returns a value.
        return self.title

    def get_relative_url(self):
        """
        Return the link path from the archive page.
        """
        # Return the link style, using the permalink style setting.
        return u'{0}{1}/'.format(self.category.get_relative_url(), self.slug)

    def similar_objects(self, num=None, **filters):
        """
        Find similar objects using related tags.
        """
        #TODO: filter appsettings.FLUENT_FAQ_FILTER_SITE_ID:
        #    filters.setdefault('parent_site', self.parent_site_id)

        # FIXME: Using super() doesn't work, calling directly.
        return TagsMixin.similar_objects(self, num=num, **filters)
Exemplo n.º 18
0
class AbstractCategory(MPTTModel, TranslatableModel):
    """
    Base class for categories.
    """

    title = TranslatedField(any_language=True)
    slug = TranslatedField()  # Explicitly added, but not needed

    parent = TreeForeignKey(
        "self",
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        related_name="children",
        verbose_name=_("Parent"),
    )
    site = models.ForeignKey(
        Site,
        on_delete=models.CASCADE,
        editable=False,
        blank=True,
        null=True,
        default=_get_current_site,
    )

    objects = CategoryManager()

    class Meta:
        abstract = True
        ordering = ("tree_id", "lft")

    def __str__(self):
        return self.safe_translation_getter("title", any_language=True)

    def get_absolute_url(self):
        # Make sure i18n_patterns() generate the proper URL, when used.
        with switch_language(self):
            return reverse("category_detail", kwargs={"slug": self.slug})
Exemplo n.º 19
0
class Product(BaseProduct, TranslatableModel):
    product_name = models.CharField(max_length=255,
                                    verbose_name=_("Product Name"))
    slug = models.SlugField(verbose_name=_("Slug"), unique=True)
    description = TranslatedField()

    # common product properties
    manufacturer = models.ForeignKey(Manufacturer,
                                     verbose_name=_("Manufacturer"))

    # controlling the catalog
    order = models.PositiveIntegerField(verbose_name=_("Sort by"),
                                        db_index=True)
    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."))
    images = models.ManyToManyField('filer.Image', through=ProductImage)

    class Meta:
        ordering = ('order', )

    objects = ProductManager()

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = ('product_name__icontains', )

    def __str__(self):
        return self.product_name

    def get_absolute_url(self):
        # sorting by highest level, so that the canonical URL associates with the most generic category
        cms_page = self.cms_pages.order_by('depth').last()
        if cms_page is None:
            return urljoin('category-not-assigned', self.slug)
        return urljoin(cms_page.get_absolute_url(), self.slug)

    @property
    def sample_image(self):
        return self.images.first()

    def get_product_markedness(self, extra):
        """
        Get the markedness of a product.
        Raises `Product.objects.DoesNotExists` if there is no markedness for the given `extra`.
        """
        msg = "Method get_product_markedness(extra) must be implemented by subclass: `{}`"
        raise NotImplementedError(msg.format(self.__class__.__name__))
Exemplo n.º 20
0
class Product(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):
    """
    Base class to describe a polymorphic product. Here we declare common fields available in all of
    our different product types. These common fields are also used to build up the view displaying
    a list of all products.
    """
    product_name = models.CharField(max_length=255,
                                    verbose_name=_("Product Name"))
    slug = models.SlugField(verbose_name=_("Slug"), unique=True)
    caption = TranslatedField()

    # common product properties
    manufacturer = models.ForeignKey(Manufacturer,
                                     verbose_name=_("Manufacturer"))

    # controlling the catalog
    order = models.PositiveIntegerField(verbose_name=_("Sort by"),
                                        db_index=True)
    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."))
    images = models.ManyToManyField('filer.Image', through=ProductImage)

    class Meta:
        ordering = ('order', )

    objects = ProductManager()

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = ('product_name__icontains', )

    def __str__(self):
        return self.product_name

    @property
    def sample_image(self):
        return self.images.first()

    def get_product_variant(self, extra):
        """
        Get a variant of the product or itself, if the product has no flavors.
        Raises `Product.objects.DoesNotExists` if there is no variant for the given `extra`.
        """
        msg = "Method get_product_variant(extra) must be implemented by subclass: `{}`"
        raise NotImplementedError(msg.format(self.__class__.__name__))
Exemplo n.º 21
0
class Product(Item):
    long_name = TranslatedField()
    offer_description = TranslatedField()
    production_mode = models.ManyToManyField("LUT_ProductionMode",
                                             verbose_name=_("Production mode"),
                                             blank=True)
    permanences = models.ManyToManyField("Permanence",
                                         through="OfferItem",
                                         blank=True)
    is_into_offer = models.BooleanField(_("In offer"), default=True)
    likes = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   related_name="likes")
    is_updated_on = models.DateTimeField(_("Updated on"),
                                         auto_now=True,
                                         blank=True)

    @property
    def total_likes(self):
        """
        Likes for the product
        :return: Integer: Likes for the product
        """
        return self.likes.count()

    @transaction.atomic()
    def get_or_create_offer_item(self, permanence):

        from repanier.models.offeritem import OfferItem, OfferItemWoReceiver
        from repanier.models.box import BoxContent

        offer_item_qs = OfferItem.objects.filter(
            permanence_id=permanence.id, product_id=self.id).order_by("?")
        if not offer_item_qs.exists():
            OfferItemWoReceiver.objects.create(
                permanence_id=permanence.id,
                product_id=self.id,
                producer_id=self.producer_id,
            )
        clean_offer_item(permanence, offer_item_qs)
        if self.is_box:
            # Add box products
            for box_content in BoxContent.objects.filter(
                    box=self.id).order_by("?"):
                box_offer_item_qs = OfferItem.objects.filter(
                    permanence_id=permanence.id,
                    product_id=box_content.product_id).order_by("?")
                if not box_offer_item_qs.exists():
                    OfferItemWoReceiver.objects.create(
                        permanence_id=permanence.id,
                        product_id=box_content.product_id,
                        producer_id=box_content.product.producer_id,
                        is_box_content=True,
                    )
                else:
                    box_offer_item = box_offer_item_qs.first()
                    box_offer_item.is_box_content = True
                    box_offer_item.save(update_fields=["is_box_content"])
                clean_offer_item(permanence, box_offer_item_qs)

        offer_item = offer_item_qs.first()
        return offer_item

    def get_html_admin_is_into_offer(self):
        return mark_safe('<div id="is_into_offer_{}">{}</div>'.format(
            self.id, self.get_html_is_into_offer()))

    get_html_admin_is_into_offer.short_description = _("In offer")
    get_html_admin_is_into_offer.admin_order_field = "is_into_offer"

    def get_html_is_into_offer(self):
        from django.contrib.admin.templatetags.admin_list import _boolean_icon

        css_class = ' class = "repanier-a-info"'
        is_into_offer = self.is_into_offer
        switch_is_into_offer = reverse("is_into_offer", args=(self.id, ))
        javascript = """
                (function($) {{
                    var lien = '{LINK}';
                    $.ajax({{
                            url: lien,
                            cache: false,
                            async: true,
                            success: function (result) {{
                                $('#is_into_offer_{PRODUCT_ID}').html(result)
                            }}
                        }});
                }})(django.jQuery);
                """.format(LINK=switch_is_into_offer, PRODUCT_ID=self.id)
        # return false; http://stackoverflow.com/questions/1601933/how-do-i-stop-a-web-page-from-scrolling-to-the-top-when-a-link-is-clicked-that-t
        link = '<div id="is_into_offer_{}"><a href="#" onclick="{};return false;"{}>{}</a></div>'.format(
            self.id, javascript, css_class, _boolean_icon(is_into_offer))
        return mark_safe(link)

    def get_qty_display(self):
        if self.is_box:
            # To avoid unicode error in email_offer.send_open_order
            qty_display = BOX_UNICODE
        else:
            qty_display = self.get_display(
                qty=1,
                order_unit=self.order_unit,
                for_customer=False,
                without_price_display=True,
            )
        return qty_display

    def __str__(self):
        return super(Product, self).get_long_name_with_producer()

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        unique_together = (("producer", "reference"), )
Exemplo n.º 22
0
class Product(Item):
    long_name = TranslatedField()
    offer_description = TranslatedField()
    production_mode = models.ManyToManyField('LUT_ProductionMode',
                                             verbose_name=_("Production mode"),
                                             blank=True)
    permanences = models.ManyToManyField('Permanence',
                                         through='OfferItem',
                                         blank=True)
    contracts = models.ManyToManyField('Contract',
                                       through='ContractContent',
                                       blank=True)
    is_into_offer = models.BooleanField(_("In offer"), default=True)
    likes = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   related_name='likes')
    is_updated_on = models.DateTimeField(_("Updated on"),
                                         auto_now=True,
                                         blank=True)

    @property
    def total_likes(self):
        """
        Likes for the product
        :return: Integer: Likes for the product
        """
        return self.likes.count()

    @transaction.atomic()
    def get_or_create_offer_item(self, permanence, reset_add_2_stock=False):

        from repanier.models.offeritem import OfferItem, OfferItemWoReceiver
        from repanier.models.box import BoxContent

        offer_item_qs = OfferItem.objects.filter(
            permanence_id=permanence.id,
            product_id=self.id,
            permanences_dates=EMPTY_STRING).order_by('?')
        if not offer_item_qs.exists():
            OfferItemWoReceiver.objects.create(permanence_id=permanence.id,
                                               product_id=self.id,
                                               producer_id=self.producer_id,
                                               permanences_dates=EMPTY_STRING)
            clean_offer_item(permanence,
                             offer_item_qs,
                             reset_add_2_stock=reset_add_2_stock)
        else:
            offer_item = offer_item_qs.first()
            offer_item.contract = None
            offer_item.permanences_dates_order = 0
            if reset_add_2_stock:
                offer_item.may_order = True
            offer_item.save(update_fields=[
                "contract", "may_order", "permanences_dates_order"
            ])
        if self.is_box:
            # Add box products
            for box_content in BoxContent.objects.filter(
                    box=self.id).order_by('?'):
                box_offer_item_qs = OfferItem.objects.filter(
                    permanence_id=permanence.id,
                    product_id=box_content.product_id,
                    permanences_dates=EMPTY_STRING).order_by('?')
                if not box_offer_item_qs.exists():
                    OfferItemWoReceiver.objects.create(
                        permanence_id=permanence.id,
                        product_id=box_content.product_id,
                        producer_id=box_content.product.producer_id,
                        permanences_dates=EMPTY_STRING,
                        is_box_content=True)
                    clean_offer_item(permanence,
                                     box_offer_item_qs,
                                     reset_add_2_stock=reset_add_2_stock)
                else:
                    box_offer_item = box_offer_item_qs.first()
                    box_offer_item.is_box_content = True
                    box_offer_item.contract = None
                    box_offer_item.permanences_dates_order = 0
                    if reset_add_2_stock:
                        box_offer_item.may_order = True
                    box_offer_item.save(update_fields=[
                        "is_box_content", "contract", "may_order",
                        "permanences_dates_order"
                    ])

        offer_item = offer_item_qs.first()
        return offer_item

    def get_html_admin_is_into_offer(self, contract=None):
        return mark_safe("<div id=\"is_into_offer_{}\">{}</div>".format(
            self.id, self.get_html_is_into_offer(contract)))

    def get_html_is_into_offer(self, contract=None, contract_content=None):
        from django.contrib.admin.templatetags.admin_list import _boolean_icon
        css_class = ' class = "btn"'
        if contract is not None or contract_content is not None:
            css_class = EMPTY_STRING
            if contract_content is None:
                contract_content = ContractContent.objects.filter(
                    product=self, contract=contract).order_by('?').first()
            if contract_content is not None and contract_content.permanences_dates is not None:
                all_dates_str = sorted(
                    list(
                        filter(
                            None,
                            contract_content.permanences_dates.split(
                                settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
                is_into_offer = len(all_dates_str) > 0
                flexible_dates = contract_content.flexible_dates
            else:
                all_dates_str = []
                is_into_offer = False
                flexible_dates = False
            if contract.permanences_dates is not None:
                contract_all_dates_str = sorted(
                    list(
                        filter(
                            None,
                            contract.permanences_dates.split(
                                settings.DJANGO_SETTINGS_DATES_SEPARATOR))))
            else:
                contract_all_dates_str = []
            contract_dates_array = []
            month_save = None
            selected_dates_counter = 0
            for one_date_str in contract_all_dates_str:
                one_date = parse_date(one_date_str)
                if month_save != one_date.month:
                    month_save = one_date.month
                    new_line = "<br>"
                else:
                    new_line = EMPTY_STRING
                # Important : linked to django.utils.dateparse.parse_date format
                switch_is_into_offer = reverse('is_into_offer_content',
                                               args=(self.id, contract.id,
                                                     one_date_str))
                javascript = """
                    (function($) {{
                        var lien = '{LINK}';
                        $.ajax({{
                                url: lien,
                                cache: false,
                                async: true,
                                success: function (result) {{
                                    $('#is_into_offer_{PRODUCT_ID}').html(result)
                                }}
                            }});
                    }})(django.jQuery);
                """.format(LINK=switch_is_into_offer, PRODUCT_ID=self.id)
                if one_date_str in all_dates_str:
                    color = "green"
                    icon = " {}".format(_boolean_icon(True))
                    selected_dates_counter += 1
                else:
                    color = "red"
                    icon = " {}".format(_boolean_icon(False))
                link = """
                        {}<a href="#" onclick="{};return false;" style="color:{} !important;">{}{}</a>
                    """.format(
                    new_line, javascript, color, icon,
                    one_date.strftime(settings.DJANGO_SETTINGS_DAY_MONTH))
                contract_dates_array.append(link)
            contract_dates = ",&nbsp;".join(contract_dates_array)
            if selected_dates_counter > 1:
                switch_flexible_dates = reverse('flexible_dates',
                                                args=(self.id, contract.id))
                javascript = """
                                    (function($) {{
                                        var lien = '{LINK}';
                                        $.ajax({{
                                                url: lien,
                                                cache: false,
                                                async: true,
                                                success: function (result) {{
                                                    $('#is_into_offer_{PRODUCT_ID}').html(result)
                                                }}
                                            }});
                                    }})(django.jQuery);
                                """.format(LINK=switch_flexible_dates,
                                           PRODUCT_ID=self.id)
                if flexible_dates:
                    color = EMPTY_STRING
                    flexible_dates_display = " {}".format(_("Flexible dates"))
                else:
                    color = EMPTY_STRING
                    flexible_dates_display = " {} {}".format(
                        selected_dates_counter, _("Fixed dates"))
                flexible_dates_link = """
                        <a href="#" onclick="{};return false;" style="color:{} !important;">{}</a>
                    """.format(
                    javascript,
                    color,
                    flexible_dates_display,
                )
            else:
                flexible_dates_link = EMPTY_STRING
        else:
            is_into_offer = self.is_into_offer
            contract_dates = EMPTY_STRING
            flexible_dates_link = EMPTY_STRING
        switch_is_into_offer = reverse(
            'is_into_offer',
            args=(self.id, contract.id if contract is not None else 0))
        javascript = """
                (function($) {{
                    var lien = '{LINK}';
                    $.ajax({{
                            url: lien,
                            cache: false,
                            async: true,
                            success: function (result) {{
                                $('#is_into_offer_{PRODUCT_ID}').html(result)
                            }}
                        }});
                }})(django.jQuery);
                """.format(LINK=switch_is_into_offer, PRODUCT_ID=self.id)
        # return false; http://stackoverflow.com/questions/1601933/how-do-i-stop-a-web-page-from-scrolling-to-the-top-when-a-link-is-clicked-that-t
        link = "<div id=\"is_into_offer_{}\"><a href=\"#\" onclick=\"{};return false;\"{}>{}</a>{}{}</div>".format(
            self.id, javascript, css_class, _boolean_icon(is_into_offer),
            flexible_dates_link, contract_dates)
        return mark_safe(link)

    def get_qty_display(self):
        if self.is_box:
            # To avoid unicode error in email_offer.send_open_order
            qty_display = BOX_UNICODE
        else:
            qty_display = self.get_display(qty=1,
                                           order_unit=self.order_unit,
                                           for_customer=False,
                                           without_price_display=True)
        return qty_display

    def __str__(self):
        return super(Product, self).get_long_name_with_producer()

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        unique_together = (
            "producer",
            "reference",
        )
Exemplo n.º 23
0
class TranslationRelated(TranslatableModel):
    shared = models.CharField(max_length=200)
    translation_relations = TranslatedField()
Exemplo n.º 24
0
class AbstractModel(TranslatableModel):
    # Already declared, but not yet linkable to a TranslatedFieldsModel
    tr_title = TranslatedField(any_language=True)

    class Meta:
        abstract = True
Exemplo n.º 25
0
class Page(TimeStampedModel, TranslatableModel, OrderedModel):
    objects = PageManager()

    code_help = _("Assign a code to the page to be able to retrieve or \
                  link to it independently of the current language, \
                  slug or position in the page hierarchy")

    is_homepage = models.BooleanField(default=False,
                                      verbose_name=_('Is homepage?'))
    code = models.CharField(max_length=64,
                            null=True,
                            blank=True,
                            db_index=True,
                            verbose_name=_('Code'),
                            help_text=code_help)
    parent = models.ForeignKey("self",
                               null=True,
                               blank=True,
                               related_name="child_pages",
                               verbose_name=_('Parent page'))
    template = models.CharField(max_length=255,
                                null=True,
                                blank=True,
                                choices=getattr(settings, 'CMS_TEMPLATES',
                                                None),
                                verbose_name=_('Template'))
    created_by = models.ForeignKey(user_model,
                                   null=True,
                                   related_name='pages_created',
                                   on_delete=models.SET_NULL,
                                   verbose_name=_('Created by'))
    modified_by = models.ForeignKey(user_model,
                                    null=True,
                                    related_name='pages_modified',
                                    on_delete=models.SET_NULL,
                                    verbose_name=_('Modified by'))

    title = TranslatedField()
    slug = TranslatedField()
    content = TranslatedField()
    meta_title = TranslatedField()
    meta_keywords = TranslatedField()
    meta_description = TranslatedField()

    order_with_respect_to = 'parent'

    class Meta(OrderedModel.Meta):
        verbose_name = _("Page")
        verbose_name_plural = _("Pages")

    @property
    def path(self):
        return Page.objects.get_info(id=self.id).path

    def get_template_section_keys(self):
        from minicms.templatetags.minicms import ContentSectionNode
        template = get_template(self.template)
        nodes = template.template.nodelist.get_nodes_by_type(
            ContentSectionNode)
        return [n.name for n in nodes]

    def get_content_section(self, name):
        translation = self.get_translation(self.get_current_language())
        return translation.get_content_section(name)

    def __str__(self):
        try:
            title = self.title
        except Exception as e:
            title = '[untranslated]'
        key = self.code or self.pk
        return "%s (%s)" % (
            title,
            key,
        )

    def natural_key(self):
        return (self.code, ) if self.code else (self.id, )

    def meta_keywords_as_array(self):
        return [item.strip() for item in self.meta_keywords.split(",")]
Exemplo n.º 26
0
class UrlNode(
        with_metaclass(URLNodeMetaClass, PolymorphicMPTTModel,
                       TranslatableModel)):
    """
    The base class for all nodes; a mapping of an URL to content (e.g. a HTML page, text file, blog, etc..)
    """
    # Some publication states
    DRAFT = 'd'
    PUBLISHED = 'p'
    STATUSES = (
        (PUBLISHED, _('Published')),
        (DRAFT, _('Draft')),
    )

    title = TranslatedField(any_language=True)
    slug = TranslatedField()  # Explicitly added, but not needed
    parent = PageTreeForeignKey(
        'self',
        blank=True,
        null=True,
        related_name='children',
        verbose_name=_('parent'),
        help_text=_(
            'You can also change the parent by dragging the page in the list.')
    )
    parent_site = models.ForeignKey(Site,
                                    editable=False,
                                    default=_get_current_site)
    #children = a RelatedManager by 'parent'

    # Publication information
    status = models.CharField(_('status'),
                              max_length=1,
                              choices=STATUSES,
                              default=DRAFT,
                              db_index=True)
    publication_date = models.DateTimeField(
        _('publication date'),
        null=True,
        blank=True,
        db_index=True,
        help_text=_(
            '''When the page should go live, status must be "Published".'''))
    publication_end_date = models.DateTimeField(_('publication end date'),
                                                null=True,
                                                blank=True,
                                                db_index=True)
    in_navigation = models.BooleanField(
        _('show in navigation'),
        default=appsettings.FLUENT_PAGES_DEFAULT_IN_NAVIGATION,
        db_index=True)
    in_sitemaps = models.BooleanField(_('include in search engine sitemaps'),
                                      default=True,
                                      db_index=True)
    override_url = TranslatedField()

    # For tagging nodes and locating them in code. This should be avoided if possible,
    # but can be a last resort to link to pages (e.g. a "Terms of Service" page).
    key = models.SlugField(
        _("page identifier"),
        choices=appsettings.FLUENT_PAGES_KEY_CHOICES,
        blank=True,
        null=True,
        help_text=_(
            "A unique identifier that is used for linking to this page."))

    # Metadata
    author = models.ForeignKey(AUTH_USER_MODEL,
                               verbose_name=_('author'),
                               editable=False)
    creation_date = models.DateTimeField(_('creation date'),
                                         editable=False,
                                         auto_now_add=True)
    modification_date = models.DateTimeField(_('last modification'),
                                             editable=False,
                                             auto_now=True)

    # Caching
    _cached_url = TranslatedField()

    # Django settings
    objects = UrlNodeManager()
    _default_manager = UrlNodeManager()

    class Meta:
        app_label = 'fluent_pages'
        ordering = (
            'tree_id',
            'lft',
        )
        verbose_name = _('URL Node')
        verbose_name_plural = _(
            'URL Nodes'
        )  # Using Urlnode here makes it's way to the admin pages too.
        unique_together = (('parent_site', 'key'), )
        permissions = (
            ('change_shared_fields_urlnode', _("Can change Shared fields")
             ),  # The fields shared between languages.
            ('change_override_url_urlnode', _("Can change Override URL field")
             ),  # Fpr overriding URLs (e.g. '/' for homepage).
        )


#    class MPTTMeta:
#        order_insertion_by = 'title'

    def __str__(self):
        # This looks pretty nice on the delete page.
        # All other models derive from Page, so they get good titles in the breadcrumb.
        return u", ".join(itervalues(self.get_absolute_urls()))

    # ---- Extra properties ----

    def __init__(self, *args, **kwargs):
        super(UrlNode, self).__init__(*args, **kwargs)

        # Cache a copy of the loaded _cached_url value so we can reliably
        # determine whether it has been changed in the save handler:
        self._original_pub_date = self.publication_date if not self._deferred else None
        self._original_pub_end_date = self.publication_end_date if not self._deferred else None
        self._original_status = self.status if not self._deferred else None
        self._original_parent = self.parent_id if not self._deferred else None

        self._cached_ancestors = None
        self.is_current = None  # Can be defined by mark_current()
        self.is_onpath = None  # is an ancestor of the current node (part of the "menu trail").

    def get_absolute_url(self):
        """
        Return the URL to this page.
        """
        # cached_url always points to the URL within the URL config root.
        # when the application is mounted at a subfolder, or the 'cms.urls' config
        # is included at a sublevel, it needs to be prepended.
        return self.default_url

    def get_absolute_url_format(self):
        if self.is_file:
            url_format = '/{slug}'
        else:
            url_format = '/{slug}/'

        # Extra for django-slug-preview
        if self.parent_id:
            # TODO: optimize this call. In some cases this would also work..
            #       that is, unless get_absolute_url() is redefined or ABSOLUTE_URL_OVERRIDES was used.
            #parent_url = self.get_translation(self.get_current_language()).get_parent_cached_url(self)

            # Need to fetch the whole parent to make sure the URL matches the actual URL being used.
            parent = self.parent
            with switch_language(parent, self.get_current_language()):
                parent_url = parent.get_absolute_url()

            return parent_url.rstrip('/') + url_format
        else:
            return url_format

    @property
    def default_url(self):
        """
        The internal implementation of :func:`get_absolute_url`.
        This function can be used when overriding :func:`get_absolute_url` in the settings.
        For example::

            ABSOLUTE_URL_OVERRIDES = {
                'fluent_pages.Page': lambda o: "http://example.com" + o.default_url
            }
        """
        with switch_language(self):
            try:
                root = reverse('fluent-page').rstrip('/')
            except NoReverseMatch:
                raise ImproperlyConfigured(
                    "Missing an include for 'fluent_pages.urls' in the URLConf"
                )

            cached_url = self._cached_url  # May raise TranslationDoesNotExist
            if cached_url is None:
                return None  # translation is just created, but not yet filled in.

            return root + cached_url

    def get_absolute_urls(self):
        """
        Return all available URLs to this page.
        """
        result = {}
        for code, cached_url in self.translations.values_list(
                'language_code', '_cached_url'):
            with switch_language(self, code):
                root = reverse('fluent-page').rstrip('/')
                result[code] = root + cached_url

        return result

    @property
    def url(self):
        """
        The URL of the page, provided for template code.
        """
        # Mapped to property for templates.
        # Not done directly using url = property(get_absolute_url),
        # so get_absolute_url() can be overwritten.
        return self.get_absolute_url()

    @property
    def last_modified(self):
        """
        Return the last modification date of the page.
        Currently this is the last time the page was saved.
        This is implemented as separate property,
        to be extended to page content in the future.
        """
        return self.modification_date

    @property
    def breadcrumb(self):
        """
        Return the breadcrumb; all parent pages leading to the current page, including current page itself.
        """
        # Cache ancestors, we need them more often
        if not self._cached_ancestors:
            self._cached_ancestors = list(self.get_ancestors())

        nodes = self._cached_ancestors[:]
        nodes.append(self)
        return nodes

    @property
    def is_published(self):
        """
        Return whether the node is published.
        """
        return self.status == self.PUBLISHED

    @property
    def is_draft(self):
        """
        Return whether the node is still a draft.
        """
        return self.status == self.DRAFT

    @property
    def is_first_child(self):
        """
        Return ``True`` when the node is the first sibling.
        """
        return self.is_root_node() or (self.parent and
                                       (self.lft == self.parent.lft + 1))

    @property
    def is_last_child(self):
        """
        Return ``True`` when the node is the last sibling.
        """
        return self.is_root_node() or (self.parent and
                                       (self.rght + 1 == self.parent.rght))

    @property
    def is_file(self):
        """
        Return ``True`` when the node represents a file (can't have children, doesn't have a layout).
        """
        return self.plugin.is_file

    @property
    def can_have_children(self):
        """
        Return ``True`` when the node can have child nodes.
        """
        # Redefine the model constant 'can_have_children' as property
        # that access the plugin registration system,
        plugin = self.plugin
        return plugin.can_have_children and not plugin.is_file

    @property
    def plugin(self):
        """
        Access the parent plugin which renders this model.
        """
        from fluent_pages.extensions import page_type_pool
        if self.__class__ in (UrlNode, Page):
            # Also allow a non_polymorphic() queryset to resolve the plugin.
            # Corresponding page_type_pool method is still private on purpose.
            # Not sure the utility method should be public, or how it should be named.
            return page_type_pool._get_plugin_by_content_type(
                self.polymorphic_ctype_id)
        else:
            return page_type_pool.get_plugin_by_model(self.__class__)

    # ---- Custom behavior ----

    # This code runs in a transaction since it's potentially editing a lot of records (all descendant urls).
    @transaction_atomic
    def save(self, *args, **kwargs):
        """
        Save the model, and update caches.
        """
        parent_changed = self.parent_id != self._original_parent
        if parent_changed:
            self._mark_all_translations_dirty()

        super(UrlNode, self).save(*args,
                                  **kwargs)  # Already saves translated model.

        # Update state for next save (if object is persistent somewhere)
        self._original_parent = self.parent_id
        self._original_pub_date = self.publication_date
        self._original_pub_end_date = self.publication_end_date
        self._original_status = self.status

    def _mark_all_translations_dirty(self):
        # Update the cached_url of all translations.
        # This triggers _update_cached_url() in save_translation() later.

        # Find all translations that this object has, both in the database, and unsaved local objects.
        all_languages = self.get_available_languages(include_unsaved=True)
        parent_urls = dict(
            UrlNode_Translation.objects.filter(
                master=self.parent_id).values_list('language_code',
                                                   '_cached_url'))

        for language_code in all_languages:
            # Get the parent-url for the translation (fetched once to speed up)
            parent_url = parent_urls.get(language_code, None)
            if not parent_url:
                fallback = appsettings.FLUENT_PAGES_LANGUAGES.get_fallback_language(
                    language_code)
                parent_url = parent_urls.get(fallback, None)

            translation = self._get_translated_model(language_code)
            translation._fetched_parent_url = parent_url

    def save_translation(self, translation, *args, **kwargs):
        """
        Update the fields associated with the translation.
        This also rebuilds the decedent URLs when the slug changed.
        """
        # Skip objects from derived models
        if translation.related_name != 'translations':
            return super(UrlNode,
                         self).save_translation(translation, *args, **kwargs)

        # Make sure there is a slug!
        if not translation.slug and translation.title:
            translation.slug = slugify(translation.title)

        # Store this object
        self._make_slug_unique(translation)
        self._update_cached_url(translation)
        url_changed = translation.is_cached_url_modified
        super(UrlNode, self).save_translation(translation, *args, **kwargs)

        # Detect changes
        published_changed = self._original_pub_date != self.publication_date \
                         or self._original_pub_end_date != self.publication_end_date \
                         or self._original_status != self.status

        if url_changed or published_changed or translation._fetched_parent_url:
            self._expire_url_caches()

            if url_changed:
                # Performance optimisation: only traversing and updating many records when something changed in the URL.
                self._update_decendant_urls(translation)

    def delete(self, *args, **kwargs):
        super(UrlNode, self).delete(*args, **kwargs)
        self._expire_url_caches()

    # Following of the principles for "clean code"
    # the save() method is split in the 3 methods below,
    # each "do one thing, and only one thing".

    def _make_slug_unique(self, translation):
        """
        Check for duplicate slugs at the same level, and make the current object unique.
        """
        origslug = translation.slug
        dupnr = 1
        while True:
            others = UrlNode.objects.filter(
                parent=self.parent_id,
                translations__slug=translation.slug,
                translations__language_code=translation.language_code
            ).non_polymorphic()

            if appsettings.FLUENT_PAGES_FILTER_SITE_ID:
                others = others.parent_site(self.parent_site_id)

            if self.pk:
                others = others.exclude(pk=self.pk)

            if not others.count():
                break

            dupnr += 1
            translation.slug = "%s-%d" % (origslug, dupnr)

    def _update_cached_url(self, translation):
        """
        Update the URLs
        """
        # This block of code is largely inspired and based on FeinCMS
        # (c) Matthias Kestenholz, BSD licensed

        # determine own URL, taking translation language into account.
        if translation.override_url:
            translation._cached_url = translation.override_url
        else:
            if self.is_root_node():
                parent_url = '/'
            else:
                parent_url = translation.get_parent_cached_url(self)

            # The following shouldn't occur, it means a direct call to Page.objects.create()
            # attempts to add a child node to a file object instead of calling model.full_clean().
            # Make sure the URL space is kept clean.
            if not parent_url[-1] == '/':
                parent_url += '/'

            if self.is_file:
                translation._cached_url = u'{0}{1}'.format(
                    parent_url, translation.slug)
            else:
                translation._cached_url = u'{0}{1}/'.format(
                    parent_url, translation.slug)

    def _update_decendant_urls(self, translation):
        """
        Update the URLs of all decendant pages.
        The method is only called when the URL has changed.
        """
        # Fetch the language settings.
        # By using get_active_choices() instead of get_fallback_language()/get_fallback_languages(),
        # this code supports both django-parler 1.5 with multiple fallbacks, as the previously single fallback choice.
        current_language = translation.language_code
        active_choices = appsettings.FLUENT_PAGES_LANGUAGES.get_active_choices(
            current_language)
        fallback_languages = active_choices[1:]

        # Init the caches that are used for tracking generated URLs
        cached_page_urls = {
            current_language: {
                self.id: translation._cached_url.rstrip('/') +
                '/'  # ensure slash, even with is_file
            }
        }

        for lang in fallback_languages:
            fallback_url = self.safe_translation_getter('_cached_url',
                                                        language_code=lang)
            if not fallback_url:
                # The fallback language does not exist, mark explicitly as not available.
                # Can't generate any URLs for sub objects, if they need a fallback language.
                cached_page_urls[lang] = {
                    self.id: None,
                }
            else:
                cached_page_urls[lang] = {
                    self.id: fallback_url.rstrip('/') + '/'
                }

        # Update all sub objects.
        # even if can_have_children is false, ensure a consistent state for the URL structure
        subobjects = self.get_descendants().order_by('lft', 'tree_id')
        for subobject in subobjects:
            if subobject.has_translation(current_language):
                # Subobject has the current translation. Use that
                # If the level in between does not have that translation, will use the fallback instead.
                subobject.set_current_language(current_language)
                use_fallback_base = (subobject.parent_id
                                     not in cached_page_urls[current_language])
            else:
                # The subobject is not yet translated in the parent's language.
                # There is nothing to update here.
                continue

            # Set URL, using cache for parent URL.
            if subobject.override_url:
                # Sub object has an explicit URL, the assignment reaffirms this to ensure consistency
                subobject._cached_url = subobject.override_url
            else:
                # Construct the fallback URLs for all fallback languages (typically 1).
                # Even though a regular URL was found, construct it, in case sub-sub objects need it.
                fallback_base = None
                fallback_lang = None
                for lang in active_choices:
                    parent_url = cached_page_urls[lang][subobject.parent_id]
                    if parent_url is None:
                        # The parent didn't have a fallback for this language, hence the subobjects can't have it either.
                        # There is no base nor URL for the sub object in this language. (be explicit here, to detect KeyError)
                        cached_page_urls[lang][subobject.id] = None
                    else:
                        # There is a translation in this language, construct the fallback URL
                        cached_page_urls[lang][
                            subobject.id] = u'{0}{1}/'.format(
                                parent_url, subobject.slug)
                        if fallback_base is None and subobject.has_translation(
                                lang):
                            fallback_base = parent_url
                            fallback_lang = lang

                if use_fallback_base:
                    # Generate URLs using the fallback language in all path parts, no exception.
                    base = fallback_base
                    subobject.set_current_language(fallback_lang)
                else:
                    # Keep appending to the real translated URL
                    base = cached_page_urls[current_language][
                        subobject.parent_id]

                if base is None:
                    # The site doesn't have fallback languages.
                    # TODO: deside whether such objects should have NO url, or block moving/reparenting objects.
                    raise UrlNode_Translation.DoesNotExist(
                        "Tree node #{0} has no active ({1}) or fallback ({2}) language.\n"
                        "Can't generate URL to connect to parent {4}.\n"
                        "Available languages are: {3}".format(
                            subobject.id, current_language,
                            ','.join(fallback_languages),
                            ','.join(subobject.get_available_languages()),
                            subobject.parent_id))

                    # Alternative:
                    ## no base == no URL for sub object. (be explicit here)
                    #subobject._cached_url = None
                else:
                    subobject._cached_url = u'{0}{1}/'.format(
                        base, subobject.slug)

            if not use_fallback_base:
                cached_page_urls[current_language][
                    subobject.id] = subobject._cached_url

            # call base class, so this function doesn't recurse
            sub_translation = subobject.get_translation(
                subobject.get_current_language()
            )  # reads from _translations_cache!
            super(UrlNode, subobject).save_translation(sub_translation)
            subobject._expire_url_caches()

    def _expire_url_caches(self):
        """
        Reset all cache keys related to this model.
        """
        cachekeys = [
            # created by _get_pages_of_type()
            'fluent_pages.instance_of.{0}.{1}'.format(
                self.__class__.__name__,
                self.parent_site_id),  # urlresolvers._get_pages_of_type()
        ]
        for cachekey in cachekeys:
            cache.delete(cachekey)
Exemplo n.º 27
0
class Product(Item):
    long_name = TranslatedField()
    offer_description = TranslatedField()
    production_mode = models.ManyToManyField('LUT_ProductionMode',
                                             verbose_name=_("production mode"),
                                             blank=True)
    permanences = models.ManyToManyField('Permanence',
                                         through='OfferItem',
                                         blank=True)
    contracts = models.ManyToManyField('Contract',
                                       through='ContractContent',
                                       blank=True)
    is_into_offer = models.BooleanField(_("is_into_offer"), default=True)
    likes = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   related_name='likes')
    is_updated_on = models.DateTimeField(_("is_updated_on"),
                                         auto_now=True,
                                         blank=True)

    @property
    def total_likes(self):
        """
        Likes for the product
        :return: Integer: Likes for the product
        """
        return self.likes.count()

    @transaction.atomic()
    def get_or_create_offer_item(self, permanence):
        from repanier.models.offeritem import OfferItem

        offer_item_qs = OfferItem.objects.filter(
            permanence_id=permanence.id,
            product_id=self.id,
        ).order_by('?')
        if not offer_item_qs.exists():
            OfferItem.objects.create(
                permanence=permanence,
                product_id=self.id,
                producer=self.producer,
            )
            clean_offer_item(permanence, offer_item_qs)
        offer_item = offer_item_qs.first()
        return offer_item

    def get_is_into_offer(self, contract=None):
        return mark_safe('<div id="is_into_offer_%d">{}</div>'.format(
            self.get_is_into_offer_html(contract)))

    get_is_into_offer.short_description = (_("is into offer"))

    def get_is_into_offer_html(self, contract=None, contract_content=None):
        from django.contrib.admin.templatetags.admin_list import _boolean_icon
        css_class = ' class = "btn"'
        if contract is not None or contract_content is not None:
            css_class = EMPTY_STRING
            if contract_content is None:
                contract_content = ContractContent.objects.filter(
                    product=self, contract=contract).order_by('?').first()
            if contract_content is not None:
                all_dates = contract_content.all_dates
                is_into_offer = len(all_dates) > 0
            else:
                all_dates = []
                is_into_offer = False

            contract_icons = []
            month_save = None
            print(all_dates)
            for one_date in contract.all_dates:
                if month_save != one_date.month:
                    month_save = one_date.month
                    if month_save is not None:
                        new_line = "<br/>"
                    else:
                        new_line = EMPTY_STRING
                else:
                    new_line = EMPTY_STRING
                # Important : linked to django.utils.dateparse.parse_date format
                one_date_str = one_date.strftime("%Y-%m-%d")
                switch_is_into_offer = urlresolvers.reverse(
                    'is_into_offer_content',
                    args=(self.id, contract.id, one_date_str))
                javascript = """
                                    (function($) {{
                                        var lien = '{LINK}';
                                        $.ajax({{
                                                url: lien,
                                                cache: false,
                                                async: true,
                                                success: function (result) {{
                                                    $('#is_into_offer_{PRODUCT_ID}').html(result)
                                                }}
                                            }});
                                    }})(django.jQuery);
                                    """.format(LINK=switch_is_into_offer,
                                               PRODUCT_ID=self.id)
                color = "green" if one_date in all_dates else "red"
                link = '%s<a href="#" onclick="%s;return false;" style="color:%s !important;">%s</a>' % (
                    new_line, javascript, color,
                    one_date.strftime(settings.DJANGO_SETTINGS_DAY))
                contract_icons.append(link)
            contract_icon = ",&nbsp;".join(contract_icons)
        else:
            is_into_offer = self.is_into_offer
            contract_icon = EMPTY_STRING
        switch_is_into_offer = urlresolvers.reverse(
            'is_into_offer',
            args=(self.id, contract.id if contract is not None else 0))
        javascript = """
                (function($) {{
                    var lien = '{LINK}';
                    $.ajax({{
                            url: lien,
                            cache: false,
                            async: true,
                            success: function (result) {{
                                $('#is_into_offer_{PRODUCT_ID}').html(result)
                            }}
                        }});
                }})(django.jQuery);
                """.format(LINK=switch_is_into_offer, PRODUCT_ID=self.id)
        # return false; http://stackoverflow.com/questions/1601933/how-do-i-stop-a-web-page-from-scrolling-to-the-top-when-a-link-is-clicked-that-t
        link = '<div id="is_into_offer_%d"><a href="#" onclick="%s;return false;"%s>%s</a>%s</div>' % (
            self.id, javascript, css_class, _boolean_icon(is_into_offer),
            contract_icon)
        return link

    def __str__(self):
        return super(Product, self).get_long_name_with_producer()

    class Meta:
        verbose_name = _("product")
        verbose_name_plural = _("products")
        unique_together = (
            "producer",
            "reference",
        )
Exemplo n.º 28
0
class WeltladenProduct(CMSPageReferenceMixin, TranslatableModelMixin,
                       BaseProduct):
    product_name = models.CharField(
        max_length=255,
        verbose_name=_("Product Name"),
    )

    slug = models.SlugField(verbose_name=_("Slug"))

    caption = TranslatedField()
    short_description = TranslatedField()
    description = TranslatedField()
    ingredients = TranslatedField()

    # product properties
    manufacturer = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
        verbose_name=_("Manufacturer"),
        blank=True,
        null=True,
    )

    additional_manufacturers = models.ManyToManyField(
        Manufacturer,
        blank=True,
        verbose_name=_("Additional Manufacturers"),
        related_name="additional_manufacturers",
    )

    display_manufacturer_as_raw_material_supplier = models.BooleanField(
        _("Display manufacturer as raw material supplier"), default=False)

    supplier = models.ForeignKey(Supplier,
                                 verbose_name=_("Supplier"),
                                 on_delete=models.CASCADE)

    quality_labels = models.ManyToManyField(QualityLabel,
                                            blank=True,
                                            verbose_name=_("Quality labels"),
                                            related_name="quality_labels")

    origin_countries = CountryField(
        verbose_name=_("Origin countries"),
        blank_label=_('Select one or many'),
        multiple=True,
        blank=True,
    )

    # controlling the catalog
    order = models.PositiveIntegerField(
        _("Sort by"),
        db_index=True,
    )

    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."),
    )

    images = models.ManyToManyField(
        'filer.Image',
        through=ProductImage,
    )

    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Gross price for this product"),
    )

    vegan = models.BooleanField(_("Vegan"), default=False)

    lactose_free = models.BooleanField(_("Lactose free"), default=False)

    gluten_free = models.BooleanField(_("Gluten free"), default=False)

    tax_switch = models.BooleanField(
        _("Switch Tax"),
        default=True,
        help_text=_(
            "If switched on, then 20% tax item, if off then 10% tax item"))

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

    instagram_category = models.ForeignKey(InstagramCategory,
                                           on_delete=models.CASCADE,
                                           null=True,
                                           blank=True)

    weight = models.DecimalField(
        _("Weight"),
        help_text=_("Weight in kilograms (kg). max 99.99kg"),
        decimal_places=2,
        max_digits=4,
        default=0.0)

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        ordering = ['order']

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = ['product_code__startswith', 'product_name__icontains']

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

    objects = ProductManager()

    def __str__(self):
        return self.product_name

    @property
    def ordered_quality_labels(self):
        return self.quality_labels.all().order_by('ordering')

    @property
    def sample_image(self):
        return self.images.first()

    def get_weight(self):
        return self.weight

    def invalidate_cache(self):
        """
        Method ``ProductCommonSerializer.render_html()`` caches the rendered HTML snippets.
        Invalidate this HTML snippet after changing relevant parts of the product.
        """
        shop_app = apps.get_app_config('shop')
        if shop_app.cache_supporting_wildcard:
            cache.delete('product:{}|*'.format(self.id))

    def clean_fields(self, exclude=None):
        super().clean_fields(exclude=exclude)
        if WeltladenProduct.objects.filter(slug=self.slug).exclude(
                id=self.id).exists():
            raise ValidationError(_('Product slug already exits'),
                                  code='invalid')
Exemplo n.º 29
0
    our different product types. These common fields are also used to build up the view displaying
    a list of all products.
    """
    product_name = models.CharField(
        _("Product Name"),
        max_length=255,
    )

    slug = models.SlugField(
        _("Slug"),
        unique=True,
    )

    {%- if cookiecutter.use_i18n == 'y' %}

    caption = TranslatedField()
    {%- else %}

    caption = HTMLField(
        verbose_name=_("Caption"),
        blank=True,
        null=True,
        configuration='CKEDITOR_SETTINGS_CAPTION',
        help_text=_("Short description used in the catalog's list view of products."),
    )
    {%- endif %}

    # common product properties
    manufacturer = models.ForeignKey(
        Manufacturer,
        verbose_name=_("Manufacturer"),