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)
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()
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
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)
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
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')
class Dummy(create_base_model()): """A fake class for holding content"""
class MyModel(create_base_model()): pass
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)
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")
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)
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')
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)