Skip to content

Instantly share code, notes, and snippets.

@evilchili
Created October 30, 2013 19:24
Show Gist options
  • Save evilchili/7238593 to your computer and use it in GitHub Desktop.
Save evilchili/7238593 to your computer and use it in GitHub Desktop.
monkeypatching pagemanager.published to honor protected assets' users/groups
from copy import deepcopy
from django.db import models
from django.db.models import Q
from mezzanine.pages.models import Page
from mezzanine.core.models import RichText
from django.contrib.auth.models import Group, User
from mezzanine.pages.managers import PageManager
from django.utils.timezone import now
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
#import logging
class ProtectedPage(Page, RichText):
"""
Create a new content type that behaves just like a RichText Page, except that it
adds a join to the django.contrib.auth Group and User fields.
"""
groups = models.ManyToManyField(Group, null=True)
users = models.ManyToManyField(User, null=True)
class ProtectedAsset(Page):
"""
A file available for download, but restricted to one or more users or groups.
"""
groups = models.ManyToManyField(Group, null=True, verbose_name="Permitted Groups")
users = models.ManyToManyField(User, null=True, verbose_name="Permitted Users")
path = models.FileField(upload_to="assets", verbose_name="File")
icon = models.ImageField(upload_to="icons", null=True, blank=True, verbose_name="Icon")
always_visible = models.BooleanField(default=False)
def save(self, *args, **kwargs):
"""
Override the default save method to enforce setting the slug to the filefield's url.
"""
# WAT: This doesn't include the upload_to subdirectory when the asset is created. :(
self.slug = self.path.url
super(ProtectedAsset, self).save(*args, **kwargs)
#
# MONKEY-PATCHES
#
original_get_absolute_url = deepcopy(Page.get_absolute_url)
def monkey_page_absolute_url(self):
"""
Monkeypatch Page.get_absolute_url to add support for Assets.
"""
if self.content_model == "protectedasset":
return self.slug
return original_get_absolute_url(self)
Page.get_absolute_url = monkey_page_absolute_url
protected_ctypes = ContentType.objects.filter(name__startswith="protected")
def monkey_pagemanager_published(self, for_user=None, include_login_required=False):
"""
Some models subclass Page and add the users and/or groups fields. For those that do,
we filter the set of "published" Page items (which include these subclass models)
by checking the items' users/groups against the current request user.
Note that this routine does not call super(PageManager, self).published().
"""
from mezzanine.core.models import CONTENT_STATUS_PUBLISHED
# if the user is a staff member, we don't need to filter results; they see everything.
if for_user is not None and for_user.is_staff:
return self.all()
# generate a QuerySet object containing all published Page items. This is the same
# behaviour as PublishedManager.publish(), except that we specifically ignore anything
# with a non-page content model.
published = self.filter(
Q(content_model__in=["page", "richtextpage", "link"]),
Q(publish_date__lte=now()) | Q(publish_date__isnull=True),
Q(expiry_date__gte=now()) | Q(expiry_date__isnull=True),
Q(status=CONTENT_STATUS_PUBLISHED),
)
# if the user is logged in we must consider those items which subclass Page with users/groups.
if for_user is not None and not for_user.is_anonymous():
# Query for each subclass's published items. We must do this because justing using self
# here will return Page items for each member, which will not have the subclasses's
# users/groups fields.
for ctype in protected_ctypes:
#logging.info("Checking for published %s items" % ctype)
items = ctype.model_class().objects.filter(
Q(groups__in=for_user.groups.all()),
Q(publish_date__lte=now()) | Q(publish_date__isnull=True),
Q(expiry_date__gte=now()) | Q(expiry_date__isnull=True),
Q(status=CONTENT_STATUS_PUBLISHED),
)
# Now look up the pages items for the subclasses's published items, by id.
items_pages = self.filter(id__in=[x.id for x in items.all()])
# add this subclass's page items to the original published queryset.
# NB: HOW COOLS IS THIS? BITWISE OPERATIONS ON QUERYSETS! <3 <3
published = published | items_pages
# Now we have a single queryset, published, which contains all published items. The last
# task is to honor the login_required bits like in the original PageManager.pulished().
unauthenticated = for_user and not for_user.is_authenticated()
if (
unauthenticated and
not include_login_required and
not settings.PAGES_PUBLISHED_INCLUDE_LOGIN_REQUIRED
):
published = published.exclude(login_required=True)
return published
PageManager.published = monkey_pagemanager_published
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment