Example #1
0
 def test_searchable_manager_search_fields(self):
     """
     Test that SearchableManager can get appropriate params.
     """
     manager = DisplayableManager()
     self.assertFalse(manager._search_fields)
     manager = DisplayableManager(search_fields={'foo': 10})
     self.assertTrue(manager._search_fields)
Example #2
0
class Category(Displayable, AdminThumbMixin):
    image = FileField(verbose_name=_("Image"),
                      upload_to=upload_to("article.Category.image",
                                          "category"),
                      format="Image",
                      max_length=255,
                      null=False,
                      blank=False)

    admin_thumb_field = "image"

    objects = DisplayableManager()
    search_fields = {"title": 10, "description": 5}

    @models.permalink
    def get_absolute_url(self):
        return ("category-detail", (), {"slug": self.slug})

    class Meta:
        verbose_name = _("Category")
        verbose_name_plural = _("Categories")
        ordering = ("title", )
        app_label = "article"

    @property
    def get_thumbnail(self):
        return self.image
Example #3
0
class Location(Displayable):
    location = GeopositionField("Location")

    objects = DisplayableManager()
    search_fields = {"title": 10, "description": 5}

    image = FileField(verbose_name=_("Image"),
                      upload_to=upload_to("article.Location.image", "location"),
                      format="Image", max_length=255, null=True, blank=True)

    def get_as_latLng(self):
        return unicode(self.location).split(',')

    def get_articles(self):
        return self.article_set.filter(is_topic=False)[:5]

    def get_topics(self):
        return self.article_set.filter(is_topic=True)[:5]

    def __unicode__(self):
        return u"%s (%s)" % (self.title, self.location)

    @models.permalink
    def get_absolute_url(self):
        return ("location-detail", (), {"slug": unicode(self.slug)})

    class Meta:
        app_label = "article"

    @property
    def get_thumbnail(self):
        return self.image
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.
    """

    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")
        unique_together = ("sku", "site")

    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)

    @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()
Example #5
0
class BaseProduct(Displayable):
    """
    Exists solely to store ``DisplayableManager`` as the main manager.
    If it's defined on ``Product``, a concrete model, then each
    ``Product`` subclass loses the custom manager.
    """

    objects = DisplayableManager()

    class Meta:
        abstract = True
Example #6
0
class Author(Displayable):
    image = FileField(verbose_name=_("Author's Image"), format="Image", max_length=255, null=True, blank=True)
    objects = DisplayableManager()

    class Meta:
        app_label = "article"

    @models.permalink
    def get_absolute_url(self):
        return ("author-detail", (), {"slug": unicode(self.slug)})

    @property
    def get_thumbnail(self):
        return self.image
Example #7
0
class Article(Displayable, Ownable, RichText, AdminThumbMixin):
    locations = models.ManyToManyField(Location, verbose_name=_("Locations"))
    is_topic = models.BooleanField(verbose_name=_("Is a topic?"), default=False)
    category_list = models.ManyToManyField(Category, verbose_name=_("Categories"),
                                           blank=False, null=False, related_name="articles")
    allow_comments = models.BooleanField(verbose_name=_("Allow comments"),
                                         default=True)
    comments = CommentsField(verbose_name=_("Comments"))
    featured_image = FileField(verbose_name=_("Featured Image"),
        format="Image", max_length=255, null=True, blank=True)

    author = models.ForeignKey("Author", related_name='articles')

    capsule_video = models.CharField(max_length=100, null=True, blank=True)

    featured_video = models.CharField(max_length=100, null=True, blank=True)

    featured_audio = models.CharField(max_length=100, null=True, blank=True)

    related_posts = models.ManyToManyField("self",
                                 verbose_name=_("Related Articles"), blank=True)
    types = models.ManyToManyField(Type, related_name="articles", verbose_name="Article Type")

    admin_thumb_field = "featured_image"

    objects = DisplayableManager()
    articles = ArticleManager()
    topics = TopicManager()

    class Meta:
        verbose_name = _("Article")
        verbose_name_plural = _("Articles")
        ordering = ("-publish_date",)
        app_label = "article"

    @models.permalink
    def get_absolute_url(self):
        name = "article-detail"
        if self.is_topic:
            name = "topic-detail"
        return (name, (), {"slug": self.slug})

    @property
    def is_video_article(self):
        return self.types.filter(title__iexact='Video').exists()

    @property
    def get_thumbnail(self):
        return self.featured_image
Example #8
0
class Product(Displayable, Priced, RichText):
    """
    Container model for a product that stores information common to
    all of its variations such as the product's title and description.
    """

    available = models.BooleanField(_("Available for purchase"), default=False)
    image = CharField(max_length=100, blank=True, null=True)
    categories = models.ManyToManyField("Category",
                                        blank=True,
                                        related_name="products")
    date_added = models.DateTimeField(_("Date added"),
                                      auto_now_add=True,
                                      null=True)
    related_products = models.ManyToManyField("self", blank=True)
    upsell_products = models.ManyToManyField("self", blank=True)
    rating = RatingField(verbose_name=_("Rating"))

    objects = DisplayableManager()

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")

    @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.
        """
        default = self.variations.get(default=True)
        for field in Priced._meta.fields:
            if not isinstance(field, models.AutoField):
                setattr(self, field.name, getattr(default, field.name))
        if default.image:
            self.image = default.image.file.name
        self.save()

    def admin_thumb(self):
        if self.image is None:
            return ""
        from mezzanine.core.templatetags.mezzanine_tags import thumbnail
        thumb_url = thumbnail(self.image, 24, 24)
        return "<img src='%s%s' />" % (settings.MEDIA_URL, thumb_url)

    admin_thumb.allow_tags = True
    admin_thumb.short_description = ""
Example #9
0
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)
Example #10
0
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)

    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)
Example #11
0
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.
        """
        return current_request().build_absolute_uri(self.get_absolute_url())

    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)
Example #12
0
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 selected, won't be shown until this time"),
        blank=True,
        null=True)
    expiry_date = models.DateTimeField(
        _("Expires on"),
        help_text=_("With published selected, won't be shown after this time"),
        blank=True,
        null=True)
    description = models.TextField(_("Description"), blank=True)
    keywords = models.ManyToManyField("Keyword",
                                      verbose_name=_("Keywords"),
                                      blank=True)
    _keywords = models.CharField(max_length=500, editable=False)
    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()
        if not self.description:
            self.description = strip_tags(self.description_from_content())
        super(Displayable, self).save(*args, **kwargs)

    def description_from_content(self):
        """
        Returns the first paragraph of the first content-like field.
        """
        description = ""
        # Get the value of the first HTMLField, or TextField if none found.
        for field_type in (HtmlField, 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 set_searchable_keywords(self):
        """
        Stores the keywords as a single string into the ``_keywords`` field
        for convenient access when searching.
        """
        keywords = " ".join([kw.title for kw in self.keywords.all()])
        if self._keywords != keywords:
            self._keywords = keywords
            self.save()

    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 = ""
Example #13
0
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_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)
    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 = ""
Example #14
0
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.
    """
    seller_id = models.IntegerField(blank=True, 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"

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")

    def setup(self, request):
        """
        Set order fields that are stored in the session, item_total
        and total based on the given cart, and copy the cart items
        to the order. Called in the final step of the checkout process
        prior to the payment handler being called.
        """
        self.seller_id = request.user.id

    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)

    @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()
Example #15
0
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)
Example #16
0
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()