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])
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
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
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, ), {})
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
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 ''))
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
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, ), {})
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()
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)
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
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()
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"
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, )
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])
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, ), {})
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)
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})
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__))
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__))
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"), )
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 = ", ".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", )
class TranslationRelated(TranslatableModel): shared = models.CharField(max_length=200) translation_relations = TranslatedField()
class AbstractModel(TranslatableModel): # Already declared, but not yet linkable to a TranslatedFieldsModel tr_title = TranslatedField(any_language=True) class Meta: abstract = True
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(",")]
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)
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 = ", ".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", )
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')
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"),