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())
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})
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))