Exemplo n.º 1
0
class Album(create_base_model()):
    name = models.CharField(_("name"), max_length=128)
    image = models.ForeignKey(MediaFile,
                              limit_choices_to={"type": "image"},
                              related_name="image")
    description = models.TextField(_("description"), blank=True)
    year = models.IntegerField()

    class Meta:
        verbose_name = _("album")
        verbose_name_plural = _('albums')
        app_label = "app"

    def __str__(self):
        return u"%d - %s" % (self.year, self.name)
Exemplo n.º 2
0
class AbstractPost(create_base_model(), ContentModelMixin):
    class Meta(object):
        abstract = True
        unique_together = (('blog', 'slug', 'date'), )
        ordering = '-date', '-time', '-id'

    blog = models.ForeignKey(
        'Blog',
        related_name='posts',
        verbose_name=_('blog'),
        on_delete=models.PROTECT,
    )
    title = models.CharField(_('title'), max_length=255)
    slug = models.SlugField(_('slug'))
    published = models.BooleanField(_('published'), default=True)
    date = models.DateField(
        _('date'),
        default=lambda: timezone.localtime(timezone.now()).date(),
    )
    time = models.TimeField(
        _('time'),
        default=lambda: timezone.localtime(timezone.now()).time(),
        null=True,
        blank=True)

    objects = PostManager()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return resolve_url("blogs:post_permalink", pk=self.pk)

    def get_pretty_url(self):
        return reverse("blogs:post",
                       args=[
                           self.blog.slug,
                           self.date.year,
                           self.date.month,
                           self.date.day,
                           self.slug,
                       ])

    def next(self):
        return self.blog.posts.after(self).last()

    def previous(self):
        return self.blog.posts.before(self).first()
Exemplo n.º 3
0
class SimplePage(create_base_model(inherit_from=models.Model)):
    """
    A simple wrapper on the feincms base model with some common fields
    set for use in implemented types.
    """
    published = models.BooleanField(_('published'), default=False)
    title = models.CharField(
        _('title'),
        max_length=100,
        help_text=_('This is used for the generated navigation too.'))

    class Meta(object):
        abstract = True

        verbose_name = _('simple page')
        verbose_name_plural = _('simple pages')

    objects = SimplePageManager()

    def __str__(self):
        return self.title
Exemplo n.º 4
0
    def bestsellers(self, queryset=None):
        queryset = queryset or self
        return queryset.filter(
            variations__orderitem__order__status__gte=Order.PAID, ).annotate(
                sold=Count('variations__orderitem')).order_by('-sold')

    def also_bought(self, product):
        return self.bestsellers(
            self.exclude(id=product.id).
            exclude(variations__orderitem__isnull=True).filter(
                variations__orderitem__order__items__product__product=product))


if settings.OPTIONS_PRODUCT_FEINCMS:
    from feincms.models import create_base_model
    Base = create_base_model(ProductBase)
else:
    Base = ProductBase


class Product(Base):
    """
    Default product model

    Knows how to determine its own price and the stock of all its variations.
    """

    is_active = models.BooleanField(_('is active'), default=True)
    is_featured = models.BooleanField(_('is featured'), default=False)
    name = models.CharField(_('name'), max_length=100)
    slug = models.SlugField(_('slug'), unique=True)
Exemplo n.º 5
0
        page = Page.objects.get(pk=page.pk)
        with_page = Page.objects.get(pk=with_page.pk)
        with_page.move_to(page, 'right')

        return Page.objects.get(pk=with_page.pk)

PageManager.add_to_active_filters( Q(active=True) )

# MARK: -
# ------------------------------------------------------------------------

try:
    # MPTT 0.4
    from mptt.models import MPTTModel
    mptt_register = False
    Base = create_base_model(MPTTModel)
except ImportError:
    # MPTT 0.3
    mptt_register = True


class Page(Base):

    active = models.BooleanField(_('active'), default=False)

    # structure and navigation
    title = models.CharField(_('title'), max_length=200,
        help_text=_('This is used for the generated navigation too.'))
    slug = models.SlugField(_('slug'), max_length=150)
    parent = models.ForeignKey('self', verbose_name=_('Parent'), blank=True, null=True, related_name='children')
    parent.parent_filter = True # Custom list_filter - see admin/filterspecs.py
Exemplo n.º 6
0
class BasePage(create_base_model(MPTTModel), ContentModelMixin):
    active = models.BooleanField(_('active'), default=True)

    # structure and navigation
    title = models.CharField(_('title'), max_length=200)
    slug = models.SlugField(_('slug'), max_length=150)
    parent = models.ForeignKey('self',
                               verbose_name=_('Parent'),
                               blank=True,
                               null=True,
                               related_name='children')
    parent.parent_filter = True  # Custom list_filter - see admin/filterspecs.py
    in_navigation = models.BooleanField(_('in navigation'), default=True)
    override_url = models.CharField(
        _('override URL'),
        max_length=255,
        blank=True,
        help_text=
        _('Override the target URL. Be sure to include slashes at the beginning and at the end if it is a local URL. This affects both the navigation and subpages\' URLs.'
          ))
    redirect_to = models.CharField(_('redirect to'),
                                   max_length=255,
                                   blank=True,
                                   help_text=_(
                                       'Target URL for automatic redirects'
                                       ' or the primary key of a page.'))
    _cached_url = models.CharField(_('Cached URL'),
                                   max_length=255,
                                   blank=True,
                                   editable=False,
                                   default='',
                                   db_index=True)

    cache_key_components = [
        lambda p: getattr(django_settings, 'SITE_ID', 0),
        lambda p: p._django_content_type.id, lambda p: p.id
    ]

    class Meta:
        ordering = ['tree_id', 'lft']
        abstract = True

    objects = PageManager()

    def __unicode__(self):
        return self.short_title()

    def is_active(self):
        """
        Check whether this page and all its ancestors are active
        """

        if not self.pk:
            return False

        pages = self.__class__.objects.active().filter(tree_id=self.tree_id,
                                                       lft__lte=self.lft,
                                                       rght__gte=self.rght)
        return pages.count() > self.level

    is_active.short_description = _('is active')

    def are_ancestors_active(self):
        """
        Check whether all ancestors of this page are active
        """

        if self.is_root_node():
            return True

        queryset = PageManager.apply_active_filters(self.get_ancestors())
        return queryset.count() >= self.level

    def short_title(self):
        """
        Title shortened for display.
        """
        from feincms.utils import shorten_string
        return shorten_string(self.title)

    short_title.admin_order_field = 'title'
    short_title.short_description = _('title')

    def __init__(self, *args, **kwargs):
        super(BasePage, 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_cached_url = self._cached_url

    @commit_on_success
    def save(self, *args, **kwargs):
        """
        Overridden save method which updates the ``_cached_url`` attribute of
        this page and all subpages. Quite expensive when called with a page
        high up in the tree.
        """

        cached_page_urls = {}

        # determine own URL
        if self.override_url:
            self._cached_url = self.override_url
        elif self.is_root_node():
            self._cached_url = u'/%s/' % self.slug
        else:
            self._cached_url = u'%s%s/' % (self.parent._cached_url, self.slug)

        cached_page_urls[self.id] = self._cached_url
        super(BasePage, self).save(*args, **kwargs)

        # Okay, we have changed the page -- remove the old stale entry from the cache
        self.invalidate_cache()

        # If our cached URL changed we need to update all descendants to
        # reflect the changes. Since this is a very expensive operation
        # on large sites we'll check whether our _cached_url actually changed
        # or if the updates weren't navigation related:
        if self._cached_url == self._original_cached_url:
            return

        pages = self.get_descendants().order_by('lft')

        for page in pages:
            if page.override_url:
                page._cached_url = page.override_url
            else:
                # cannot be root node by definition
                page._cached_url = u'%s%s/' % (
                    cached_page_urls[page.parent_id], page.slug)

            cached_page_urls[page.id] = page._cached_url
            super(BasePage, page).save()  # do not recurse

    save.alters_data = True

    @commit_on_success
    def delete(self, *args, **kwargs):
        super(BasePage, self).delete(*args, **kwargs)
        self.invalidate_cache()

    delete.alters_data = True

    # Remove the page from the url-to-page cache
    def invalidate_cache(self):
        ck = self.path_to_cache_key(self._original_cached_url)
        django_cache.delete(ck)

    @models.permalink
    def get_absolute_url(self):
        """
        Return the absolute URL of this page.
        """
        # result url never begins or ends with a slash
        url = self._cached_url.strip('/')
        if url:
            return ('feincms_handler', (url, ), {})
        return ('feincms_home', (), {})

    def get_navigation_url(self):
        """
        Return either ``redirect_to`` if it is set, or the URL of this page.
        """

        # :-( maybe this could be cleaned up a bit?
        if not self.redirect_to or REDIRECT_TO_RE.match(self.redirect_to):
            return self._cached_url
        return self.redirect_to

    def cache_key(self):
        """
        Return a string that may be used as cache key for the current page.
        The cache_key is unique for each content type and content instance.
        """
        return '-'.join(unicode(fn(self)) for fn in self.cache_key_components)

    def etag(self, request):
        """
        Generate an etag for this page.
        An etag should be unique and unchanging for as long as the page
        content does not change. Since we have no means to determine whether
        rendering the page now (as opposed to a minute ago) will actually
        give the same result, this default implementation returns None, which
        means "No etag please, thanks for asking".
        """
        return None

    def last_modified(self, request):
        """
        Generate a last modified date for this page.
        Since a standard page has no way of knowing this, we always return
        "no date" -- this is overridden by the changedate extension.
        """
        return None

    def get_redirect_to_target(self, request):
        """
        This might be overriden/extended by extension modules.
        """

        if not self.redirect_to:
            return u''

        # It might be an identifier for a different object
        match = REDIRECT_TO_RE.match(self.redirect_to)

        # It's not, oh well.
        if not match:
            return self.redirect_to

        matches = match.groupdict()
        model = get_model(matches['app_label'], matches['module_name'])

        if not model:
            return self.redirect_to

        try:
            instance = model._default_manager.get(pk=int(matches['pk']))
            return instance.get_absolute_url()
        except models.ObjectDoesNotExist:
            pass

        return self.redirect_to

    @classmethod
    def path_to_cache_key(cls, path):
        prefix = "%s-FOR-URL" % cls.__name__.upper()
        return path_to_cache_key(path.strip('/'), prefix=prefix)

    @classmethod
    def register_default_processors(cls, frontend_editing=False):
        """
        Register our default request processors for the out-of-the-box
        Page experience.
        """
        cls.register_request_processor(processors.redirect_request_processor,
                                       key='redirect')
        cls.register_request_processor(
            processors.extra_context_request_processor, key='extra_context')

        if frontend_editing:
            cls.register_request_processor(
                processors.frontendediting_request_processor,
                key='frontend_editing')
            cls.register_response_processor(
                processors.frontendediting_response_processor,
                key='frontend_editing')
Exemplo n.º 7
0
class Dummy(create_base_model()):
    """A fake class for holding content"""
Exemplo n.º 8
0
class MyModel(create_base_model()):
    pass
Exemplo n.º 9
0
        if hasattr(request, '_feincms_page'):
            return request._feincms_page

        return self.for_request(request)

PageManager.add_to_active_filters( Q(active=True) )

# MARK: -
# ------------------------------------------------------------------------

try:
    # MPTT 0.4
    from mptt.models import MPTTModel
    mptt_register = False
    Base = create_base_model(MPTTModel)
except ImportError:
    # MPTT 0.3
    mptt_register = True


class Page(Base):
    active = models.BooleanField(_('active'), default=False)

    # structure and navigation
    title = models.CharField(_('title'), max_length=200,
        help_text=_('This is used for the generated navigation too.'))
    slug = models.SlugField(_('slug'), max_length=150)
    parent = models.ForeignKey('self', verbose_name=_('Parent'), blank=True, null=True, related_name='children')
    parent.parent_filter = True # Custom list_filter - see admin/filterspecs.py
    in_navigation = models.BooleanField(_('in navigation'), default=False)
Exemplo n.º 10
0
class BasePage(create_base_model(MPTTModel), ContentModelMixin):
    active = models.BooleanField(_("active"), default=True)

    # structure and navigation
    title = models.CharField(
        _("title"),
        max_length=200,
        help_text=_("This title is also used for navigation menu items."),
    )
    slug = models.SlugField(
        _("slug"),
        max_length=150,
        help_text=_("This is used to build the URL for this page"),
    )
    parent = models.ForeignKey(
        "self",
        verbose_name=_("Parent"),
        blank=True,
        on_delete=models.CASCADE,
        null=True,
        related_name="children",
    )
    # Custom list_filter - see admin/filterspecs.py
    parent.parent_filter = True
    in_navigation = models.BooleanField(_("in navigation"), default=False)
    override_url = models.CharField(
        _("override URL"),
        max_length=255,
        blank=True,
        help_text=_(
            "Override the target URL. Be sure to include slashes at the "
            "beginning and at the end if it is a local URL. This "
            "affects both the navigation and subpages' URLs."),
    )
    redirect_to = models.CharField(
        _("redirect to"),
        max_length=255,
        blank=True,
        help_text=_("Target URL for automatic redirects"
                    " or the primary key of a page."),
    )
    _cached_url = models.CharField(
        _("Cached URL"),
        max_length=255,
        blank=True,
        editable=False,
        default="",
        db_index=True,
    )

    class Meta:
        ordering = ["tree_id", "lft"]
        abstract = True

    objects = PageManager()

    def __str__(self):
        return self.short_title()

    def is_active(self):
        """
        Check whether this page and all its ancestors are active
        """

        if not self.pk:
            return False

        # No need to hit DB if page itself is inactive
        if not self.active:
            return False

        pages = self.__class__.objects.active().filter(tree_id=self.tree_id,
                                                       lft__lte=self.lft,
                                                       rght__gte=self.rght)
        return pages.count() > self.level

    is_active.short_description = _("is active")

    def are_ancestors_active(self):
        """
        Check whether all ancestors of this page are active
        """

        if self.is_root_node():
            return True

        queryset = PageManager.apply_active_filters(self.get_ancestors())
        return queryset.count() >= self.level

    def short_title(self):
        """
        Title shortened for display.
        """
        return shorten_string(self.title)

    short_title.admin_order_field = "title"
    short_title.short_description = _("title")

    def __init__(self, *args, **kwargs):
        super(BasePage, 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_cached_url = self._cached_url

    def save(self, *args, **kwargs):
        """
        Overridden save method which updates the ``_cached_url`` attribute of
        this page and all subpages. Quite expensive when called with a page
        high up in the tree.
        """

        cached_page_urls = {}

        # determine own URL
        if self.override_url:
            self._cached_url = self.override_url
        elif self.is_root_node():
            self._cached_url = "/%s/" % self.slug
        else:
            self._cached_url = "%s%s/" % (self.parent._cached_url, self.slug)

        cached_page_urls[self.id] = self._cached_url
        super(BasePage, self).save(*args, **kwargs)

        # If our cached URL changed we need to update all descendants to
        # reflect the changes. Since this is a very expensive operation
        # on large sites we'll check whether our _cached_url actually changed
        # or if the updates weren't navigation related:
        if self._cached_url == self._original_cached_url:
            return

        pages = self.get_descendants().order_by("lft")

        for page in pages:
            if page.override_url:
                page._cached_url = page.override_url
            else:
                # cannot be root node by definition
                page._cached_url = "%s%s/" % (
                    cached_page_urls[page.parent_id],
                    page.slug,
                )

            cached_page_urls[page.id] = page._cached_url
            super(BasePage, page).save()  # do not recurse

    save.alters_data = True

    def delete(self, *args, **kwargs):
        if not settings.FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED:
            if self.template.singleton:
                raise PermissionDenied(
                    _("This %(page_class)s uses a singleton template, and "
                      "FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" %
                      {"page_class": self._meta.verbose_name}))
        super(BasePage, self).delete(*args, **kwargs)

    delete.alters_data = True

    def get_absolute_url(self):
        """
        Return the absolute URL of this page.
        """
        # result url never begins or ends with a slash
        url = self._cached_url.strip("/")
        if url:
            return reverse("feincms_handler", args=(url, ))
        return reverse("feincms_home")

    def get_navigation_url(self):
        """
        Return either ``redirect_to`` if it is set, or the URL of this page.
        """

        if self.redirect_to:
            return self.get_redirect_to_target()
        return self._cached_url

    def etag(self, request):
        """
        Generate an etag for this page.
        An etag should be unique and unchanging for as long as the page
        content does not change. Since we have no means to determine whether
        rendering the page now (as opposed to a minute ago) will actually
        give the same result, this default implementation returns None, which
        means "No etag please, thanks for asking".
        """
        return None

    def last_modified(self, request):
        """
        Generate a last modified date for this page.
        Since a standard page has no way of knowing this, we always return
        "no date" -- this is overridden by the changedate extension.
        """
        return None

    def get_redirect_to_page(self):
        """
        This might be overriden/extended by extension modules.
        """

        if not self.redirect_to:
            return None

        # It might be an identifier for a different object
        whereto = match_model_string(self.redirect_to)
        if not whereto:
            return None

        return get_model_instance(*whereto)

    def get_redirect_to_target(self, request=None):
        """
        This might be overriden/extended by extension modules.
        """

        target_page = self.get_redirect_to_page()
        if target_page is None:
            return self.redirect_to

        return target_page.get_absolute_url()

    @classmethod
    def register_default_processors(cls):
        """
        Register our default request processors for the out-of-the-box
        Page experience.
        """
        cls.register_request_processor(processors.redirect_request_processor,
                                       key="redirect")
        cls.register_request_processor(
            processors.extra_context_request_processor, key="extra_context")
Exemplo n.º 11
0
class FeinCMSDocument(create_base_model()):
    """
    A model which can have FeinCMS content chunks attached to it.

    See :py:meth:`feincmstools.base.FeinCMSDocument.content_types_by_region` for sample
    definition and a quick intro.

    See feincms.models.create_base_model for definitions of the register_* and
    create_content_type.

    1. Register regions OR templates. The former is simpler but the latter is
    more flexible, as you can define different regions for different templates.

        Page.register_regions(
            ('main', _('Main content area')),
        )

    OR

        Page.register_templates(
            {
                'key': 'base',
                'title': _('Standard template'),
                'path': 'feincms_base.html',
                'regions': (
                    ('main', _('Main content area')),
                    ('sidebar', _('Sidebar'), 'inherited'),
                    ),
            }, {
                'key': '2col',
                'title': _('Template with two columns'),
                'path': 'feincms_2col.html',
                'regions': (
                    ('col1', _('Column one')),
                    ('col2', _('Column two')),
                    ('sidebar', _('Sidebar'), 'inherited'),
                    ),
            }
        )

    FeinCMSTools registers the template config in
        cls.feincms_templates = [{...}, {...}, ...]
    or the regions in
        cls.feincms_regions = [(...), (...), ...]

    Where the list contents are the *args to the functions above.

    2. Register content types (use Content model subclasses for auto-templating, but any abstract model will work).

    In FeinCMS, you do this with successive calls to
    Page.create_content_type(SomeContent, regions=None, class_name=None, **kwargs)

    FeinCMSTools steps through the regions, and registers the content types in
    cls.content_types_by_region(region). Define content_types_by_region in subclasses.
    """

    # PUBLIC
    feincms_templates = None
    feincms_regions = None

    class Meta:
        abstract = True

    @classmethod
    def _get_content_type_class_name(cls, content_type):
        """
        Hook to allow overriding of class_name passed to create_content_type.

        Previous default retained for backwards compatibility.
        However, this produces db_table names such as:
            <app_name>_<base_name>_<base_name><content_type_name>
        But for longer class names, this becomes problematic, e.g.:
            >>> len("experiences_articletranslation_"
            ...     "articletranslationfullwidthcenteredtextblock")
            75
        This is problematic for database backends such as MySQL, which
        imposes a 64-character limit on table names.

        There may be other reasons for wanting to change the class/table name.

        Returning None from this method will cause FeinCMS to fallback
        onto the default configuration of using simply `content_type.__name__`

        If registering the same Content type against multiple FeinCMSDocument base
        classes in the same app, unique class_name values must be provided
        for each to avoid collisions.
        """
        if feincmstools_settings.USE_LEGACY_TABLE_NAMES:
            return "%s%s" % (cls.__name__, content_type.__name__)

    @classmethod
    def content_types_by_region(cls, region):
        """
        This should return the list of content types that
        are allowed in that region, grouped by section.

        This method should be overridden for the subclasses.

        :return: The content types defined for the given region.
        Each returned list is formatted ('category', [SomeContent,  ...]), thus:

        [
            (None, (TextileContent,)),
            ('Media resources', (OneOffImageContent, ReusableImageContent, VideoContent,)),
            ('Files', (OneOffFileContent, ReusableFileContent)),
        ]

        If category is ``None``, these content types will appear first in the menu.

        :rtype:
            ``list`` of ``tuples`` →
                category_name, ``str``, ``list`` of content_types registered under the given category in the given region.

        Which results in the following menu in the admin edit form:

            Textile
            (Media resources)
                One-off image
                Reusable image
                Video
            (Files)
                One-off file
                Reusable file

        .. note:: Because ``content_types_by_region`` is called from the metaclass,
        using python ``super`` leads to crashes. Explicitly call ``ParentClass.content_types_by_region``
        instead. See below for example.

        """
        return []

    def region_has_content(self, region):
        """
        Returns ``True`` if the model has a region named
        ``region`` containing some content.
        """
        if region in self.content._fetch_regions():
            return True
        return False

    @classmethod
    def get_used_content_types(cls):
        """
        :return: All Content models used by the class. Useful for migrations.
        :rtype: ``set``
        """
        lxr = cls._get_content_types_by_region()

        r = set()

        for reg, categories in lxr:
            for category, types in categories:
                r = r.union(types)
        return r

    #PRIVATE

    __metaclass__ = FeinCMSDocumentBase

    @classmethod
    def _get_content_types_by_region(cls):
        """
        :return: All content_types grouped by category, then into regions.
        :rtype: ``list`` of ``tuple``s
        """
        return [(r.key, cls.content_types_by_region(r.key)) for r in cls._feincms_all_regions]


    @classmethod
    def _register(cls):
        """
        Create the tables for the attached content_types.
        """
        if not cls._meta.abstract: # concrete subclasses only
            # register templates or regions
            cls._register_templates_or_regions()
            cls._register_content_types()

    @classmethod
    def _register_templates_or_regions(cls):
        if cls.feincms_templates:
            if (cls.feincms_regions):
                import warnings
                warnings.warn('In `%s`: `feincms_regions` is ignored as '
                    '`feincms_templates` takes precedence.'
                    % cls.__name__, RuntimeWarning
                )
            cls.register_templates(*cls.feincms_templates)
        else:
            if cls.feincms_regions:
                # auto-register FeinCMS regions
                cls.register_regions(*cls.feincms_regions)


    @classmethod
    def _register_content_types(cls):
        return create_content_types(cls, cls.content_types_by_region)

    def search_text(self):
        request = HttpRequest()
        template = Template('''{% load feincms_tags %}
            {% filter striptags %}
            {% feincms_render_region object "main" request %}
            {% endfilter %}
            ''')
        context = RequestContext(request)
        context['object'] = self
        #import pdb;pdb.set_trace()
        return template.render(context)
Exemplo n.º 12
0
class Page(create_base_model(MPTTModel)):
    active = models.BooleanField(_('active'), default=True)

    # structure and navigation
    title = models.CharField(_('title'), max_length=200)
    slug = models.SlugField(_('slug'), max_length=150)
    parent = models.ForeignKey('self',
                               verbose_name=_('Parent'),
                               blank=True,
                               null=True,
                               related_name='children')
    parent.parent_filter = True  # Custom list_filter - see admin/filterspecs.py
    in_navigation = models.BooleanField(_('in navigation'), default=True)
    override_url = models.CharField(
        _('override URL'),
        max_length=300,
        blank=True,
        help_text=
        _('Override the target URL. Be sure to include slashes at the beginning and at the end if it is a local URL. This affects both the navigation and subpages\' URLs.'
          ))
    redirect_to = models.CharField(
        _('redirect to'),
        max_length=300,
        blank=True,
        help_text=_('Target URL for automatic redirects.'))
    _cached_url = models.CharField(_('Cached URL'),
                                   max_length=300,
                                   blank=True,
                                   editable=False,
                                   default='',
                                   db_index=True)

    request_processors = SortedDict()
    response_processors = SortedDict()
    cache_key_components = [
        lambda p: django_settings.SITE_ID, lambda p: p._django_content_type.id,
        lambda p: p.id
    ]

    class Meta:
        ordering = ['tree_id', 'lft']
        verbose_name = _('page')
        verbose_name_plural = _('pages')

    objects = PageManager()

    def __unicode__(self):
        return self.short_title()

    def is_active(self):
        """
        Check whether this page and all its ancestors are active
        """

        if not self.pk:
            return False

        pages = Page.objects.active().filter(tree_id=self.tree_id,
                                             lft__lte=self.lft,
                                             rght__gte=self.rght)
        return pages.count() > self.level

    is_active.short_description = _('is active')

    def are_ancestors_active(self):
        """
        Check whether all ancestors of this page are active
        """

        if self.is_root_node():
            return True

        queryset = PageManager.apply_active_filters(self.get_ancestors())
        return queryset.count() >= self.level

    def active_children(self):
        """
        Returns a queryset describing all active children of the current page.
        This is different than page.get_descendants (from mptt) as it will
        additionally select only child pages that are active.
        """
        warnings.warn(
            'active_children is deprecated. Use self.children.active() instead.',
            DeprecationWarning,
            stacklevel=2)
        return Page.objects.active().filter(parent=self)

    def active_children_in_navigation(self):
        """
        Returns a queryset describing all active children that also have the
        in_navigation flag set. This might be used eg. in building navigation
        menues (only show a disclosure indicator if there actually is something
        to disclose).
        """
        warnings.warn(
            'active_children_in_navigation is deprecated. Use self.children.in_navigation() instead.',
            DeprecationWarning,
            stacklevel=2)
        return self.active_children().filter(in_navigation=True)

    def short_title(self):
        """
        Title shortened for display.
        """
        from feincms.utils import shorten_string
        return shorten_string(self.title)

    short_title.admin_order_field = 'title'
    short_title.short_description = _('title')

    def __init__(self, *args, **kwargs):
        super(Page, 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_cached_url = self._cached_url

    @commit_on_success
    def save(self, *args, **kwargs):
        """
        Overridden save method which updates the ``_cached_url`` attribute of
        this page and all subpages. Quite expensive when called with a page
        high up in the tree.
        """

        cached_page_urls = {}

        # determine own URL
        if self.override_url:
            self._cached_url = self.override_url
        elif self.is_root_node():
            self._cached_url = u'/%s/' % self.slug
        else:
            self._cached_url = u'%s%s/' % (self.parent._cached_url, self.slug)

        cached_page_urls[self.id] = self._cached_url
        super(Page, self).save(*args, **kwargs)

        # Okay, we changed the URL -- remove the old stale entry from the cache
        ck = path_to_cache_key(self._original_cached_url.strip('/'))
        django_cache.delete(ck)

        # If our cached URL changed we need to update all descendants to
        # reflect the changes. Since this is a very expensive operation
        # on large sites we'll check whether our _cached_url actually changed
        # or if the updates weren't navigation related:
        if self._cached_url == self._original_cached_url:
            return

        # TODO: Does not find everything it should when ContentProxy content
        # inheritance has been customized.
        pages = self.get_descendants().order_by('lft')

        for page in pages:
            if page.override_url:
                page._cached_url = page.override_url
            else:
                # cannot be root node by definition
                page._cached_url = u'%s%s/' % (
                    cached_page_urls[page.parent_id], page.slug)

            cached_page_urls[page.id] = page._cached_url
            super(Page, page).save()  # do not recurse

    @models.permalink
    def get_absolute_url(self):
        """
        Return the absolute URL of this page.
        """

        url = self._cached_url[1:-1]
        if url:
            return ('feincms_handler', (url, ), {})
        return ('feincms_home', (), {})

    def get_navigation_url(self):
        """
        Return either ``redirect_to`` if it is set, or the URL of this page.
        """

        return self.redirect_to or self._cached_url

    def get_siblings_and_self(page):
        """
        As the name says.
        """
        warnings.warn(
            'get_siblings_and_self is deprecated. You probably want self.parent.children.active() anyway.',
            DeprecationWarning,
            stacklevel=2)
        return page.get_siblings(include_self=True)

    def cache_key(self):
        """
        Return a string that may be used as cache key for the current page.
        The cache_key is unique for each content type and content instance.
        """
        return '-'.join(unicode(x(self)) for x in self.cache_key_components)

    def etag(self, request):
        """
        Generate an etag for this page.
        An etag should be unique and unchanging for as long as the page
        content does not change. Since we have no means to determine whether
        rendering the page now (as opposed to a minute ago) will actually
        give the same result, this default implementation returns None, which
        means "No etag please, thanks for asking".
        """
        return None

    def last_modified(self, request):
        """
        Generate a last modified date for this page.
        Since a standard page has no way of knowing this, we always return
        "no date" -- this is overridden by the changedate extension.
        """
        return None

    def setup_request(self, request):
        """
        Before rendering a page, run all registered request processors. A request
        processor may peruse and modify the page or the request. It can also return
        a HttpResponse for shortcutting the page rendering and returning that response
        immediately to the client.

        ``setup_request`` stores responses returned by request processors and returns
        those on every subsequent call to ``setup_request``. This means that
        ``setup_request`` can be called repeatedly during the same request-response
        cycle without harm - request processors are executed exactly once.
        """

        if hasattr(self, '_setup_request_result'):
            return self._setup_request_result
        else:
            # Marker -- setup_request has been successfully run before
            self._setup_request_result = None

        if not hasattr(request, '_feincms_extra_context'):
            request._feincms_extra_context = {}

        request._feincms_extra_context.update({
            'in_appcontent_subpage':
            False,  # XXX This variable name isn't accurate anymore.
            # We _are_ in a subpage, but it isn't necessarily
            # an appcontent subpage.
            'extra_path': '/',
        })

        if request.path != self.get_absolute_url():
            request._feincms_extra_context.update({
                'in_appcontent_subpage':
                True,
                'extra_path':
                re.sub('^' + re.escape(self.get_absolute_url()[:-1]), '',
                       request.path),
            })

        for fn in reversed(self.request_processors.values()):
            r = fn(self, request)
            if r:
                self._setup_request_result = r
                break

        return self._setup_request_result

    def finalize_response(self, request, response):
        """
        After rendering a page to a response, the registered response processors are
        called to modify the response, eg. for setting cache or expiration headers,
        keeping statistics, etc.
        """
        for fn in self.response_processors.values():
            fn(self, request, response)

    def get_redirect_to_target(self, request):
        """
        This might be overriden/extended by extension modules.
        """
        return self.redirect_to

    @classmethod
    def register_request_processor(cls, fn, key=None):
        """
        Registers the passed callable as request processor. A request processor
        always receives two arguments, the current page object and the request.
        """
        cls.request_processors[fn if key is None else key] = fn

    @classmethod
    def register_response_processor(cls, fn, key=None):
        """
        Registers the passed callable as response processor. A response processor
        always receives three arguments, the current page object, the request
        and the response.
        """
        cls.response_processors[fn if key is None else key] = fn

    @classmethod
    def register_request_processors(cls, *processors):
        """
        Registers all passed callables as request processors. A request processor
        always receives two arguments, the current page object and the request.
        """

        warnings.warn(
            "register_request_processors has been deprecated,"
            " use register_request_processor instead.",
            DeprecationWarning,
            stacklevel=2)

        for processor in processors:
            cls.register_request_processor(processor)

    @classmethod
    def register_response_processors(cls, *processors):
        """
        Registers all passed callables as response processors. A response processor
        always receives three arguments, the current page object, the request
        and the response.
        """

        warnings.warn(
            "register_response_processors has been deprecated,"
            " use register_response_processor instead.",
            DeprecationWarning,
            stacklevel=2)

        for processor in processors:
            cls.register_response_processor(processor)

    @classmethod
    def register_extension(cls, register_fn):
        register_fn(cls, PageAdmin)

    require_path_active_request_processor = _LegacyProcessorDescriptor(
        'require_path_active_request_processor')
    redirect_request_processor = _LegacyProcessorDescriptor(
        'redirect_request_processor')
    frontendediting_request_processor = _LegacyProcessorDescriptor(
        'frontendediting_request_processor')
    etag_request_processor = _LegacyProcessorDescriptor(
        'etag_request_processor')
    etag_response_processor = _LegacyProcessorDescriptor(
        'etag_response_processor')
    debug_sql_queries_response_processor = _LegacyProcessorDescriptor(
        'debug_sql_queries_response_processor')
Exemplo n.º 13
0
    def bestsellers(self, queryset=None):
        queryset = queryset or self
        return queryset.filter(
            variations__orderitem__order__status=Order.COMPLETED,
            ).annotate(sold=Count('variations__orderitem')).order_by('-sold')

    def also_bought(self, product):
        return self.bestsellers(
            self.exclude(id=product.id).exclude(variations__orderitem__isnull=True
                ).filter(variations__orderitem__order__items__product__product=product))


if plata.settings.PLATA_PRODUCT_OPTIONS_FEINCMS:
    from feincms.models import create_base_model
    Base = create_base_model(ProductBase)
else:
    Base = ProductBase

class Product(Base):
    """
    Default product model

    Knows how to determine its own price and the stock of all its variations.
    """

    is_active = models.BooleanField(_('is active'), default=True)
    is_featured = models.BooleanField(_('is featured'), default=False)
    name = models.CharField(_('name'), max_length=100)
    slug = models.SlugField(_('slug'), unique=True)
    ordering = models.PositiveIntegerField(_('ordering'), default=0)