class Displayable(Slugged, MetaData, TimeStamped): """ Abstract model that provides features of a visible page on the website such as publishing fields. Basis of Mezzanine pages, blog posts, and Cartridge products. """ status = models.IntegerField( _("Status"), choices=CONTENT_STATUS_CHOICES, default=CONTENT_STATUS_PUBLISHED, help_text=_("With Draft chosen, will only be shown for admin users " "on the site.")) publish_date = models.DateTimeField( _("Published from"), help_text=_("With Published chosen, won't be shown until this time"), blank=True, null=True, db_index=True) expiry_date = models.DateTimeField( _("Expires on"), help_text=_("With Published chosen, won't be shown after this time"), blank=True, null=True) short_url = models.URLField(blank=True, null=True) in_sitemap = models.BooleanField(_("Show in sitemap"), default=True) objects = DisplayableManager() search_fields = {"keywords": 10, "title": 5} class Meta: abstract = True def save(self, *args, **kwargs): """ Set default for ``publish_date``. We can't use ``auto_now_add`` on the field as it will be blank when a blog post is created from the quick blog form in the admin dashboard. """ if self.publish_date is None: self.publish_date = now() super(Displayable, self).save(*args, **kwargs) def get_admin_url(self): return admin_url(self, "change", self.id) def publish_date_since(self): """ Returns the time since ``publish_date``. """ return timesince(self.publish_date) publish_date_since.short_description = _("Published from") def get_absolute_url(self): """ Raise an error if called on a subclass without ``get_absolute_url`` defined, to ensure all search results contains a URL. """ name = self.__class__.__name__ raise NotImplementedError("The model %s does not have " "get_absolute_url defined" % name) def get_absolute_url_with_host(self): """ Returns host + ``get_absolute_url`` - used by the various ``short_url`` mechanics below. Technically we should use ``self.site.domain``, here, however if we were to invoke the ``short_url`` mechanics on a list of data (eg blog post list view), we'd trigger a db query per item. Using ``current_request`` should provide the same result, since site related data should only be loaded based on the current host anyway. """ req = current_request() abs_url = self.get_absolute_url() build = req.build_absolute_uri(abs_url) return build def set_short_url(self): """ Generates the ``short_url`` attribute if the model does not already have one. Used by the ``set_short_url_for`` template tag and ``TweetableAdmin``. If no sharing service is defined (bitly is the one implemented, but others could be by overriding ``generate_short_url``), the ``SHORT_URL_UNSET`` marker gets stored in the DB. In this case, ``short_url`` is temporarily (eg not persisted) set to host + ``get_absolute_url`` - this is so that we don't permanently store ``get_absolute_url``, since it may change over time. """ if self.short_url == SHORT_URL_UNSET: self.short_url = self.get_absolute_url_with_host() elif not self.short_url: self.short_url = self.generate_short_url() self.save() def generate_short_url(self): """ Returns a new short URL generated using bit.ly if credentials for the service have been specified. """ from mezzanine.conf import settings if settings.BITLY_ACCESS_TOKEN: url = "https://api-ssl.bit.ly/v3/shorten?%s" % urlencode( { "access_token": settings.BITLY_ACCESS_TOKEN, "uri": self.get_absolute_url_with_host(), }) response = loads(urlopen(url).read().decode("utf-8")) if response["status_code"] == 200: return response["data"]["url"] return SHORT_URL_UNSET def _get_next_or_previous_by_publish_date(self, is_next, **kwargs): """ Retrieves next or previous object by publish date. We implement our own version instead of Django's so we can hook into the published manager and concrete subclasses. """ arg = "publish_date__gt" if is_next else "publish_date__lt" order = "publish_date" if is_next else "-publish_date" lookup = {arg: self.publish_date} concrete_model = base_concrete_model(Displayable, self) try: queryset = concrete_model.objects.published except AttributeError: queryset = concrete_model.objects.all try: return queryset(**kwargs).filter(**lookup).order_by(order)[0] except IndexError: pass def get_next_by_publish_date(self, **kwargs): """ Retrieves next object by publish date. """ return self._get_next_or_previous_by_publish_date(True, **kwargs) def get_previous_by_publish_date(self, **kwargs): """ Retrieves previous object by publish date. """ return self._get_next_or_previous_by_publish_date(False, **kwargs)
class Displayable(Slugged, MetaData, TimeStamped): """ Abstract model that provides features of a visible page on the website such as publishing fields. Basis of Mezzanine pages, blog posts, and Cartridge products. """ status = models.IntegerField( _("Status"), choices=CONTENT_STATUS_CHOICES, default=CONTENT_STATUS_PUBLISHED, help_text=_("With Draft chosen, will only be shown for admin users " "on the site.")) publish_date = models.DateTimeField( _("Published from"), help_text=_("With Published chosen, won't be shown until this time"), blank=True, null=True) expiry_date = models.DateTimeField( _("Expires on"), help_text=_("With Published chosen, won't be shown after this time"), blank=True, null=True) short_url = models.URLField(blank=True, null=True) in_sitemap = models.BooleanField(_("Show in sitemap"), default=True) objects = DisplayableManager() search_fields = {"keywords": 10, "title": 5} class Meta: abstract = True def save(self, *args, **kwargs): """ Set default for ``publish_date``. We can't use ``auto_now_add`` on the field as it will be blank when a blog post is created from the quick blog form in the admin dashboard. """ if self.publish_date is None: self.publish_date = now() super(Displayable, self).save(*args, **kwargs) def get_admin_url(self): return admin_url(self, "change", self.id) def publish_date_since(self): """ Returns the time since ``publish_date``. """ return timesince(self.publish_date) publish_date_since.short_description = _("Published from") def get_absolute_url(self): """ Raise an error if called on a subclass without ``get_absolute_url`` defined, to ensure all search results contains a URL. """ name = self.__class__.__name__ raise NotImplementedError("The model %s does not have " "get_absolute_url defined" % name) def set_short_url(self): """ Sets the ``short_url`` attribute using the bit.ly credentials if they have been specified, and saves it. Used by the ``set_short_url_for`` template tag, and ``TweetableAdmin``. """ if not self.short_url: from mezzanine.conf import settings settings.use_editable() parts = (self.site.domain, self.get_absolute_url()) self.short_url = "http://%s%s" % parts if settings.BITLY_ACCESS_TOKEN: url = "https://api-ssl.bit.ly/v3/shorten?%s" % urlencode( { "access_token": settings.BITLY_ACCESS_TOKEN, "uri": self.short_url, }) response = loads(urlopen(url).read().decode("utf-8")) if response["status_code"] == 200: self.short_url = response["data"]["url"] self.save() return "" def _get_next_or_previous_by_publish_date(self, is_next, **kwargs): """ Retrieves next or previous object by publish date. We implement our own version instead of Django's so we can hook into the published manager and concrete subclasses. """ arg = "publish_date__gt" if is_next else "publish_date__lt" order = "publish_date" if is_next else "-publish_date" lookup = {arg: self.publish_date} concrete_model = base_concrete_model(Displayable, self) try: queryset = concrete_model.objects.published except AttributeError: queryset = concrete_model.objects.all try: return queryset(**kwargs).filter(**lookup).order_by(order)[0] except IndexError: pass def get_next_by_publish_date(self, **kwargs): """ Retrieves next object by publish date. """ return self._get_next_or_previous_by_publish_date(True, **kwargs) def get_previous_by_publish_date(self, **kwargs): """ Retrieves previous object by publish date. """ return self._get_next_or_previous_by_publish_date(False, **kwargs)
class Displayable(Slugged, MetaData): """ Abstract model that provides features of a visible page on the website such as publishing fields. Basis of Mezzanine pages, blog posts, and Cartridge products. """ status = models.IntegerField( _("Status"), choices=CONTENT_STATUS_CHOICES, default=CONTENT_STATUS_PUBLISHED, help_text=_("With Draft chosen, will only be shown for admin users " "on the site.")) publish_date = models.DateTimeField( _("Published from"), help_text=_("With Published chosen, won't be shown until this time"), blank=True, null=True) expiry_date = models.DateTimeField( _("Expires on"), help_text=_("With Published chosen, won't be shown after this time"), blank=True, null=True) short_url = models.URLField(blank=True, null=True) in_sitemap = models.BooleanField(_("Show in sitemap"), default=True) theme_color = models.CharField( "Theme Colour", max_length=255, default='grey', choices=THEME_CHOICES, help_text='Select a Theme Colour for this page') objects = DisplayableManager() search_fields = {"keywords": 10, "title": 5} class Meta: abstract = True def save(self, *args, **kwargs): """ Set default for ``publish_date``. We can't use ``auto_now_add`` on the field as it will be blank when a blog post is created from the quick blog form in the admin dashboard. """ if self.publish_date is None: self.publish_date = now() super(Displayable, self).save(*args, **kwargs) def get_admin_url(self): return admin_url(self, "change", self.id) def publish_date_since(self): """ Returns the time since ``publish_date``. """ return timesince(self.publish_date) publish_date_since.short_description = _("Published from") def get_absolute_url(self): """ Raise an error if called on a subclass without ``get_absolute_url`` defined, to ensure all search results contains a URL. """ name = self.__class__.__name__ raise NotImplementedError("The model %s does not have " "get_absolute_url defined" % name) def _get_next_or_previous_by_publish_date(self, is_next, **kwargs): """ Retrieves next or previous object by publish date. We implement our own version instead of Django's so we can hook into the published manager and concrete subclasses. """ arg = "publish_date__gt" if is_next else "publish_date__lt" order = "publish_date" if is_next else "-publish_date" lookup = {arg: self.publish_date} concrete_model = base_concrete_model(Displayable, self) try: queryset = concrete_model.objects.published except AttributeError: queryset = concrete_model.objects.all try: return queryset(**kwargs).filter(**lookup).order_by(order)[0] except IndexError: pass def get_next_by_publish_date(self, **kwargs): """ Retrieves next object by publish date. """ return self._get_next_or_previous_by_publish_date(True, **kwargs) def get_previous_by_publish_date(self, **kwargs): """ Retrieves previous object by publish date. """ return self._get_next_or_previous_by_publish_date(False, **kwargs)
class Displayable(Slugged, MetaData): """ Abstract model that provides features of a visible page on the website such as publishing fields. Basis of Mezzanine pages and blog posts. """ status = models.IntegerField(_("Status"), choices=CONTENT_STATUS_CHOICES, default=CONTENT_STATUS_PUBLISHED) publish_date = models.DateTimeField( _("Published from"), help_text=_("With published checked, won't be shown until this time"), blank=True, null=True) expiry_date = models.DateTimeField( _("Expires on"), help_text=_("With published checked, won't be shown after this time"), blank=True, null=True) short_url = models.URLField(blank=True, null=True) objects = DisplayableManager() search_fields = {"keywords": 10, "title": 5} class Meta: abstract = True def save(self, *args, **kwargs): """ Set default for ``publsh_date`` and ``description`` if none given. """ if self.publish_date is None: # publish_date will be blank when a blog post is created # from the quick blog form in the admin dashboard. self.publish_date = datetime.now() self.description = strip_tags(self.description_from_content()) super(Displayable, self).save(*args, **kwargs) def description_from_content(self): """ Returns the first block or sentence of the first content-like field. """ description = "" # Use the first RichTextField, or TextField if none found. for field_type in (RichTextField, models.TextField): if not description: for field in self._meta.fields: if isinstance(field, field_type) and \ field.name != "description": description = getattr(self, field.name) if description: break # Fall back to the title if description couldn't be determined. if not description: description = self.title # Strip everything after the first block or sentence. ends = ("</p>", "<br />", "<br/>", "<br>", "</ul>", "\n", ". ", "! ", "? ") for end in ends: pos = description.lower().find(end) if pos > -1: description = TagCloser(description[:pos]).html break else: description = truncatewords_html(description, 100) return description def admin_link(self): return "<a href='%s'>%s</a>" % (self.get_absolute_url(), ugettext("View on site")) admin_link.allow_tags = True admin_link.short_description = ""
class Displayable(Slugged): """ Abstract model that provides features of a visible page on the website such as publishing fields and meta data. """ status = models.IntegerField(_("Status"), choices=CONTENT_STATUS_CHOICES, default=CONTENT_STATUS_DRAFT) publish_date = models.DateTimeField( _("Published from"), help_text=_("With published checked, won't be shown until this time"), blank=True, null=True) expiry_date = models.DateTimeField( _("Expires on"), help_text=_("With published checked, won't be shown after this time"), blank=True, null=True) description = models.TextField(_("Description"), blank=True) keywords = KeywordsField(verbose_name=_("Keywords")) short_url = models.URLField(blank=True, null=True) site = models.ForeignKey(Site, editable=False) objects = DisplayableManager() search_fields = {"keywords": 10, "title": 5} class Meta: abstract = True def save(self, update_site=True, *args, **kwargs): """ Set default for ``publsh_date`` and ``description`` if none given. Unless the ``update_site`` argument is ``False``, set the site to the current site. """ if self.publish_date is None: # publish_date will be blank when a blog post is created # from the quick blog form in the admin dashboard. self.publish_date = datetime.now() if not self.description: self.description = strip_tags(self.description_from_content()) if update_site: self.site = Site.objects.get_current() super(Displayable, self).save(*args, **kwargs) def description_from_content(self): """ Returns the first paragraph of the first content-like field. """ description = "" # Use the first RichTextField, or TextField if none found. for field_type in (RichTextField, models.TextField): if not description: for field in self._meta.fields: if isinstance(field, field_type) and \ field.name != "description": description = getattr(self, field.name) if description: break # Fall back to the title if description couldn't be determined. if not description: description = self.title # Strip everything after the first paragraph or sentence. for end in ("</p>", "<br />", "\n", ". "): if end in description: description = description.split(end)[0] + end break else: description = truncatewords_html(description, 100) return description def admin_link(self): return "<a href='%s'>%s</a>" % (self.get_absolute_url(), ugettext("View on site")) admin_link.allow_tags = True admin_link.short_description = ""
class Displayable(Slugged, MetaData): """ Abstract model that provides features of a visible page on the website such as publishing fields. Basis of Mezzanine pages, blog posts, and Cartridge products. """ status = models.IntegerField( _("Status"), choices=CONTENT_STATUS_CHOICES, default=CONTENT_STATUS_PUBLISHED, help_text=_("With Draft chosen, will only be shown for admin users " "on the site.")) publish_date = models.DateTimeField( _("Published from"), help_text=_("With Published chosen, won't be shown until this time"), blank=True, null=True) expiry_date = models.DateTimeField( _("Expires on"), help_text=_("With Published chosen, won't be shown after this time"), blank=True, null=True) short_url = models.URLField(blank=True, null=True) objects = DisplayableManager() search_fields = {"keywords": 10, "title": 5} class Meta: abstract = True def save(self, *args, **kwargs): """ Set default for ``publish_date``. We can't use ``auto_add`` on the field as it will be blank when a blog post is created from the quick blog form in the admin dashboard. """ if self.publish_date is None: self.publish_date = now() super(Displayable, self).save(*args, **kwargs) def get_admin_url(self): return admin_url(self, "change", self.id) def publish_date_since(self): """ Returns the time since ``publish_date``. """ return timesince(self.publish_date) publish_date_since.short_description = _("Published from") def get_absolute_url(self): """ Raise an error if called on a subclass without ``get_absolute_url`` defined, to ensure all search results contains a URL. """ name = self.__class__.__name__ raise NotImplementedError("The model %s does not have " "get_absolute_url defined" % name)
class Product(Displayable, Priced, RichText, AdminThumbMixin): """ Container model for a product that stores information common to all of its variations such as the product's title and description. """ content_model = models.CharField(editable=False, max_length=50, null=True) available = models.BooleanField(_("Available for purchase"), default=False) image = CharField(_("Image"), max_length=100, blank=True, null=True) categories = models.ManyToManyField("Category", blank=True, verbose_name=_("Product categories")) date_added = models.DateTimeField(_("Date added"), auto_now_add=True, null=True) related_products = models.ManyToManyField( "self", verbose_name=_("Related products"), blank=True) upsell_products = models.ManyToManyField("self", verbose_name=_("Upsell products"), blank=True) rating = RatingField(verbose_name=_("Rating")) objects = DisplayableManager() admin_thumb_field = "image" search_fields = {"variations__sku": 100} class Meta: verbose_name = _("Product") verbose_name_plural = _("Products") @classmethod def get_content_models(cls): """ Return all ``Product`` subclasses. """ is_product_subclass = lambda cls: issubclass(cls, Product) cmp = lambda a, b: (int(b is Product) - int(a is Product) or a._meta. verbose_name < b._meta.verbose_name) return sorted(filter(is_product_subclass, models.get_models()), cmp) def get_content_model(self): """ Provides a generic method of retrieving the instance of the custom product's model, if there is one. """ return getattr(self, self.content_model, None) def save(self, *args, **kwargs): """ Copies the price fields to the default variation when ``SHOP_USE_VARIATIONS`` is False, and the product is updated via the admin change list. """ updating = self.id is not None super(Product, self).save(*args, **kwargs) if updating and not settings.SHOP_USE_VARIATIONS: default = self.variations.get(default=True) self.copy_price_fields_to(default) else: self.content_model = self._meta.object_name.lower() @models.permalink def get_absolute_url(self): return ("shop_product", (), {"slug": self.slug}) def copy_default_variation(self): """ Copies the price and image fields from the default variation when the product is updated via the change view. """ default = self.variations.get(default=True) default.copy_price_fields_to(self) if default.image: self.image = default.image.file.name self.save()