Beispiel #1
0
class Article(models.Model):

    objects = managers.ArticleManager()

    current_revision = models.OneToOneField(
        "ArticleRevision",
        verbose_name=_("current revision"),
        blank=True,
        null=True,
        related_name="current_set",
        on_delete=models.CASCADE,
        help_text=
        _("The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field."
          ),
    )

    created = models.DateTimeField(
        auto_now_add=True,
        verbose_name=_("created"),
    )
    modified = models.DateTimeField(
        auto_now=True,
        verbose_name=_("modified"),
        help_text=_("Article properties last modified"),
    )

    owner = models.ForeignKey(
        django_settings.AUTH_USER_MODEL,
        verbose_name=_("owner"),
        blank=True,
        null=True,
        related_name="owned_articles",
        help_text=
        _("The owner of the article, usually the creator. The owner always has both read and write access."
          ),
        on_delete=models.SET_NULL,
    )

    group = models.ForeignKey(
        settings.GROUP_MODEL,
        verbose_name=_("group"),
        blank=True,
        null=True,
        help_text=
        _("Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system."
          ),
        on_delete=models.SET_NULL,
    )

    group_read = models.BooleanField(default=True,
                                     verbose_name=_("group read access"))
    group_write = models.BooleanField(default=True,
                                      verbose_name=_("group write access"))
    other_read = models.BooleanField(default=True,
                                     verbose_name=_("others read access"))
    other_write = models.BooleanField(default=True,
                                      verbose_name=_("others write access"))

    # PERMISSIONS
    def can_read(self, user):
        return permissions.can_read(self, user)

    def can_write(self, user):
        return permissions.can_write(self, user)

    def can_delete(self, user):
        return permissions.can_delete(self, user)

    def can_moderate(self, user):
        return permissions.can_moderate(self, user)

    def can_assign(self, user):
        return permissions.can_assign(self, user)

    def ancestor_objects(self):
        """NB! This generator is expensive, so use it with care!!"""
        for obj in self.articleforobject_set.filter(is_mptt=True):
            yield from obj.content_object.get_ancestors()

    def descendant_objects(self):
        """NB! This generator is expensive, so use it with care!!"""
        for obj in self.articleforobject_set.filter(is_mptt=True):
            yield from obj.content_object.get_descendants()

    def get_children(self, max_num=None, user_can_read=None, **kwargs):
        """NB! This generator is expensive, so use it with care!!"""
        cnt = 0
        for obj in self.articleforobject_set.filter(is_mptt=True):
            if user_can_read:
                objects = (obj.content_object.get_children().filter(
                    **kwargs).can_read(user_can_read))
            else:
                objects = obj.content_object.get_children().filter(**kwargs)
            for child in objects.order_by(
                    "articles__article__current_revision__title"):
                cnt += 1
                if max_num and cnt > max_num:
                    return
                yield child

    # All recursive permission methods will use descendant_objects to access
    # generic relations and check if they are using MPTT and have
    # INHERIT_PERMISSIONS=True
    def set_permissions_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.article.group_read = self.group_read
                descendant.article.group_write = self.group_write
                descendant.article.other_read = self.other_read
                descendant.article.other_write = self.other_write
                descendant.article.save()

    def set_group_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.article.group = self.group
                descendant.article.save()

    def set_owner_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.article.owner = self.owner
                descendant.article.save()

    def add_revision(self, new_revision, save=True):
        """
        Sets the properties of a revision and ensures its the current
        revision.
        """
        assert self.id or save, (
            "Article.add_revision: Sorry, you cannot add a"
            "revision to an article that has not been saved "
            "without using save=True")
        if not self.id:
            self.save()
        revisions = self.articlerevision_set.all()
        try:
            new_revision.revision_number = revisions.latest(
            ).revision_number + 1
        except ArticleRevision.DoesNotExist:
            new_revision.revision_number = 0
        new_revision.article = self
        new_revision.previous_revision = self.current_revision
        if save:
            new_revision.clean()
            new_revision.save()
        self.current_revision = new_revision
        if save:
            self.save()

    def add_object_relation(self, obj):
        return ArticleForObject.objects.get_or_create(
            article=self,
            content_type=ContentType.objects.get_for_model(obj),
            object_id=obj.id,
            is_mptt=isinstance(obj, MPTTModel),
        )

    @classmethod
    def get_for_object(cls, obj):
        return ArticleForObject.objects.get(
            object_id=obj.id,
            content_type=ContentType.objects.get_for_model(obj),
        ).article

    def __str__(self):
        if self.current_revision:
            return self.current_revision.title
        obj_name = _("Article without content (%(id)d)") % {"id": self.id}
        return str(obj_name)

    class Meta:
        permissions = (
            ("moderate", _("Can edit all articles and lock/unlock/restore")),
            ("assign", _("Can change ownership of any article")),
            ("grant", _("Can assign permissions to other users")),
        )

    def render(self, preview_content=None, user=None):
        if not self.current_revision:
            return ""
        if preview_content:
            content = preview_content
        else:
            content = self.current_revision.content
        return mark_safe(
            article_markdown(content,
                             self,
                             preview=preview_content is not None,
                             user=user))

    def get_cache_key(self):
        """Returns per-article cache key."""
        lang = translation.get_language()

        key_raw = "wiki-article-{id}-{lang}".format(
            id=self.current_revision.id if self.current_revision else self.id,
            lang=lang)
        # https://github.com/django-wiki/django-wiki/issues/1065
        return slugify(key_raw, allow_unicode=True)

    def get_cache_content_key(self, user=None):
        """Returns per-article-user cache key."""
        key_raw = "{key}-{user}".format(
            key=self.get_cache_key(),
            user=user.get_username() if user else "-anonymous")
        # https://github.com/django-wiki/django-wiki/issues/1065
        return slugify(key_raw, allow_unicode=True)

    def get_cached_content(self, user=None):
        """Returns cached version of rendered article.

        The cache contains one "per-article" entry plus multiple
        "per-article-user" entries. The per-article-user entries contain the
        rendered article, the per-article entry contains list of the
        per-article-user keys. The rendered article in cache (per-article-user)
        is used only if the key is in the per-article entry. To delete
        per-article invalidates all article cache entries."""

        if user and user.is_anonymous:
            user = None

        cache_key = self.get_cache_key()
        cache_content_key = self.get_cache_content_key(user)

        cached_items = cache.get(cache_key, list())

        if cache_content_key in cached_items:
            cached_content = cache.get(cache_content_key)
            if cached_content is not None:
                return mark_safe(cached_content)

        cached_content = self.render(user=user)
        cached_items.append(cache_content_key)
        cache.set(cache_key, cached_items, settings.CACHE_TIMEOUT)
        cache.set(cache_content_key, cached_content, settings.CACHE_TIMEOUT)

        return mark_safe(cached_content)

    def clear_cache(self):
        cache.delete(self.get_cache_key())

    def get_url_kwargs(self):
        urlpaths = self.urlpath_set.all()
        if urlpaths.exists():
            return {"path": urlpaths[0].path}
        return {"article_id": self.id}

    def get_absolute_url(self):
        return reverse("wiki:get", kwargs=self.get_url_kwargs())
Beispiel #2
0
class Article(models.Model):

    objects = managers.ArticleManager()

    current_revision = models.OneToOneField(
        'ArticleRevision',
        verbose_name=_('current revision'),
        blank=True,
        null=True,
        related_name='current_set',
        help_text=
        _('The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'
          ),
    )

    created = models.DateTimeField(
        auto_now_add=True,
        verbose_name=_('created'),
    )
    modified = models.DateTimeField(
        auto_now=True,
        verbose_name=_('modified'),
        help_text=_('Article properties last modified'))

    owner = models.ForeignKey(
        compat.USER_MODEL,
        verbose_name=_('owner'),
        blank=True,
        null=True,
        related_name='owned_articles',
        help_text=
        _('The owner of the article, usually the creator. The owner always has both read and write access.'
          ),
        on_delete=models.SET_NULL)

    group = models.ForeignKey(
        settings.GROUP_MODEL,
        verbose_name=_('group'),
        blank=True,
        null=True,
        help_text=
        _('Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system.'
          ),
        on_delete=models.SET_NULL)

    group_read = models.BooleanField(default=True,
                                     verbose_name=_('group read access'))
    group_write = models.BooleanField(default=True,
                                      verbose_name=_('group write access'))
    other_read = models.BooleanField(default=True,
                                     verbose_name=_('others read access'))
    other_write = models.BooleanField(default=True,
                                      verbose_name=_('others write access'))

    # PERMISSIONS
    def can_read(self, user):
        return permissions.can_read(self, user)

    def can_write(self, user):
        return permissions.can_write(self, user)

    def can_delete(self, user):
        return permissions.can_delete(self, user)

    def can_moderate(self, user):
        return permissions.can_moderate(self, user)

    def can_assign(self, user):
        return permissions.can_assign(self, user)

    def ancestor_objects(self):
        """NB! This generator is expensive, so use it with care!!"""
        for obj in self.articleforobject_set.filter(is_mptt=True):
            for ancestor in obj.content_object.get_ancestors():
                yield ancestor

    def descendant_objects(self):
        """NB! This generator is expensive, so use it with care!!"""
        for obj in self.articleforobject_set.filter(is_mptt=True):
            for descendant in obj.content_object.get_descendants():
                yield descendant

    def get_children(self, max_num=None, user_can_read=None, **kwargs):
        """NB! This generator is expensive, so use it with care!!"""
        cnt = 0
        for obj in self.articleforobject_set.filter(is_mptt=True):
            if user_can_read:
                objects = obj.content_object.get_children().filter(
                    **kwargs).can_read(user_can_read)
            else:
                objects = obj.content_object.get_children().filter(**kwargs)
            for child in objects.order_by(
                    'articles__article__current_revision__title'):
                cnt += 1
                if max_num and cnt > max_num:
                    return
                yield child

    # All recursive permission methods will use descendant_objects to access
    # generic relations and check if they are using MPTT and have
    # INHERIT_PERMISSIONS=True
    def set_permissions_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.article.group_read = self.group_read
                descendant.article.group_write = self.group_write
                descendant.article.other_read = self.other_read
                descendant.article.other_write = self.other_write
                descendant.article.save()

    def set_group_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.article.group = self.group
                descendant.article.save()

    def set_owner_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.article.owner = self.owner
                descendant.article.save()

    def add_revision(self, new_revision, save=True):
        """
        Sets the properties of a revision and ensures its the current
        revision.
        """
        assert self.id or save, (
            'Article.add_revision: Sorry, you cannot add a'
            'revision to an article that has not been saved '
            'without using save=True')
        if not self.id:
            self.save()
        revisions = self.articlerevision_set.all()
        try:
            new_revision.revision_number = revisions.latest(
            ).revision_number + 1
        except ArticleRevision.DoesNotExist:
            new_revision.revision_number = 0
        new_revision.article = self
        new_revision.previous_revision = self.current_revision
        if save:
            new_revision.save()
        self.current_revision = new_revision
        if save:
            self.save()

    def add_object_relation(self, obj):
        content_type = ContentType.objects.get_for_model(obj)
        is_mptt = isinstance(obj, MPTTModel)
        rel = ArticleForObject.objects.get_or_create(article=self,
                                                     content_type=content_type,
                                                     object_id=obj.id,
                                                     is_mptt=is_mptt)
        return rel

    @classmethod
    def get_for_object(cls, obj):
        return ArticleForObject.objects.get(
            object_id=obj.id,
            content_type=ContentType.objects.get_for_model(obj)).article

    def __str__(self):
        if self.current_revision:
            return self.current_revision.title
        obj_name = _('Article without content (%(id)d)') % {'id': self.id}
        return str(obj_name)

    class Meta:
        app_label = settings.APP_LABEL
        permissions = (
            ("moderate", _("Can edit all articles and lock/unlock/restore")),
            ("assign", _("Can change ownership of any article")),
            ("grant", _("Can assign permissions to other users")),
        )

    def render(self, preview_content=None):
        if not self.current_revision:
            return ""
        if preview_content:
            content = preview_content
        else:
            content = self.current_revision.content
        return mark_safe(
            article_markdown(content,
                             self,
                             preview=preview_content is not None))

    def get_cache_key(self):
        return "wiki:article:%d" % (self.current_revision.id
                                    if self.current_revision else self.id)

    def get_cached_content(self):
        """Returns cached """
        cache_key = self.get_cache_key()
        cached_content = cache.get(cache_key)
        if cached_content is None:
            cached_content = self.render()
            cache.set(cache_key, cached_content, settings.CACHE_TIMEOUT)
        return cached_content

    def clear_cache(self):
        cache.delete(self.get_cache_key())

    def get_absolute_url(self):
        urlpaths = self.urlpath_set.all()
        if urlpaths.exists():
            return urlpaths[0].get_absolute_url()
        else:
            return reverse('wiki:get', kwargs={'article_id': self.id})
Beispiel #3
0
class Article(models.Model):
    
    objects = managers.ArticleManager()
    
    current_revision = models.OneToOneField('ArticleRevision', 
                                            verbose_name=_(u'current revision'),
                                            blank=True, null=True, related_name='current_set',
                                            help_text=_(u'The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'),
                                            )
    
    created = models.DateTimeField(auto_now_add=True, verbose_name=_(u'created'),)
    modified = models.DateTimeField(auto_now=True, verbose_name=_(u'modified'),
                                    help_text=_(u'Article properties last modified'))

    owner = models.ForeignKey(User, verbose_name=_('owner'),
                              blank=True, null=True, related_name='owned_articles',
                              help_text=_(u'The owner of the article, usually the creator. The owner always has both read and write access.'),)
    
    group = models.ForeignKey(Group, verbose_name=_('group'),
                              blank=True, null=True,
                              help_text=_(u'Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system.'),)
    
    group_read = models.BooleanField(default=True, verbose_name=_(u'group read access'))
    group_write = models.BooleanField(default=True, verbose_name=_(u'group write access'))
    other_read = models.BooleanField(default=True, verbose_name=_(u'others read access'))
    other_write = models.BooleanField(default=True, verbose_name=_(u'others write access'))
    
    # TODO: Do not use kwargs, it can lead to dangerous situations with bad
    # permission checking patterns. Also, since there are no other keywords,
    # it doesn't make much sense.
    def can_read(self, user=None):
        # Deny reading access to deleted articles if user has no delete access
        if self.current_revision and self.current_revision.deleted and not self.can_delete(user):
            return False
        
        # Check access for other users...
        if user.is_anonymous() and not settings.ANONYMOUS:
            return False
        elif self.other_read:
            return True
        elif user.is_anonymous():
            return  False
        if user == self.owner:
            return True
        if self.group_read:
            if self.group and user.groups.filter(id=self.group.id).exists():
                return True
        if self.can_moderate(user):
            return True
        return False
    
    def can_write(self, user=None):
        # Check access for other users...
        if user.is_anonymous() and not settings.ANONYMOUS_WRITE:
            return False
        elif self.other_write:
            return True
        elif user.is_anonymous():
            return  False
        if user == self.owner:
            return True
        if self.group_write:
            if self.group and user and user.groups.filter(id=self.group.id).exists():
                return True
        if self.can_moderate(user):
            return True
        return False
    
    def can_delete(self, user):
        return permissions.can_delete(self, user)
    def can_moderate(self, user):
        return permissions.can_moderate(self, user)
    def can_assign(self, user):
        return permissions.can_assign(self, user)
    
    def descendant_objects(self):
        """NB! This generator is expensive, so use it with care!!"""
        for obj in self.articleforobject_set.filter(is_mptt=True):
            for descendant in obj.content_object.get_descendants():
                yield descendant
    
    def get_children(self, max_num=None, user_can_read=None, **kwargs):
        """NB! This generator is expensive, so use it with care!!"""
        cnt = 0
        for obj in self.articleforobject_set.filter(is_mptt=True):
            if user_can_read:
                objects = obj.content_object.get_children().filter(**kwargs).can_read(user_can_read)
            else:
                objects = obj.content_object.get_children().filter(**kwargs)
            for child in objects.order_by('articles__article__current_revision__title'):
                cnt += 1
                if max_num and cnt > max_num: return
                yield child

    # All recursive permission methods will use descendant_objects to access
    # generic relations and check if they are using MPTT and have INHERIT_PERMISSIONS=True
    def set_permissions_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.group_read = self.group_read
                descendant.group_write = self.group_write
                descendant.other_read = self.other_read
                descendant.other_write = self.other_write
                descendant.save()
    
    def set_group_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.group = self.group
                descendant.save()

    def set_owner_recursive(self):
        for descendant in self.descendant_objects():
            if descendant.INHERIT_PERMISSIONS:
                descendant.owner = self.owner
                descendant.save()
    
    def add_revision(self, new_revision, save=True):
        """
        Sets the properties of a revision and ensures its the current
        revision.
        """
        assert self.id or save, ('Article.add_revision: Sorry, you cannot add a' 
                                 'revision to an article that has not been saved '
                                 'without using save=True')
        if not self.id: self.save()
        revisions = self.articlerevision_set.all()
        try:
            new_revision.revision_number = revisions.latest().revision_number + 1
        except ArticleRevision.DoesNotExist:
            new_revision.revision_number = 0
        new_revision.article = self
        new_revision.previous_revision = self.current_revision
        if save: new_revision.save()
        self.current_revision = new_revision
        if save: self.save()
    
    def add_object_relation(self, obj):
        content_type = ContentType.objects.get_for_model(obj)
        is_mptt = isinstance(obj, MPTTModel)
        rel = ArticleForObject.objects.get_or_create(article=self,
                                                     content_type=content_type,
                                                     object_id=obj.id,
                                                     is_mptt=is_mptt)
        return rel
    
    @classmethod
    def get_for_object(cls, obj):
        return ArticleForObject.objects.get(object_id=obj.id, content_type=ContentType.objects.get_for_model(obj)).article
    
    def __unicode__(self):
        if self.current_revision:
            return self.current_revision.title
        obj_name = _(u'Article without content (%(id)d)') % {'id': self.id}
        return unicode(obj_name)
    
    class Meta:
        app_label = settings.APP_LABEL
        permissions = (
            ("moderate", "Can edit all articles and lock/unlock/restore"),
            ("assign", "Can change ownership of any article"),
            ("grant", "Can assign permissions to other users"),
        )
    
    def render(self, preview_content=None):
        if not self.current_revision:
            return ""
        if preview_content:
            content = preview_content
        else:
            content = self.current_revision.content
        extensions = plugin_registry.get_markdown_extensions()
        extensions += settings.MARKDOWN_EXTENSIONS
        return mark_safe(article_markdown(content, self, extensions=extensions))