Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-wiki
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
django-wiki
Commits
832d7903
Commit
832d7903
authored
Aug 22, 2012
by
benjaoming
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Loads of changes in permission system. Many aspects are now configurable.
parent
495d70ee
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
223 additions
and
131 deletions
+223
-131
django_notify/views.py
+2
-1
wiki/conf/settings.py
+11
-0
wiki/core/permissions.py
+15
-0
wiki/decorators.py
+26
-20
wiki/forms.py
+17
-13
wiki/managers.py
+1
-1
wiki/models/article.py
+36
-15
wiki/models/pluginbase.py
+28
-17
wiki/plugins/attachments/markdown_extensions.py
+1
-1
wiki/plugins/attachments/models.py
+4
-1
wiki/plugins/attachments/templates/wiki/plugins/attachments/history.html
+1
-1
wiki/plugins/attachments/templates/wiki/plugins/attachments/index.html
+4
-4
wiki/plugins/attachments/templates/wiki/plugins/attachments/search.html
+1
-1
wiki/plugins/attachments/views.py
+24
-20
wiki/plugins/images/models.py
+3
-0
wiki/plugins/images/templates/wiki/plugins/images/index.html
+1
-1
wiki/plugins/images/views.py
+3
-5
wiki/plugins/links/views.py
+3
-1
wiki/plugins/notifications/TODO.md
+1
-0
wiki/templates/wiki/base.html
+3
-1
wiki/templates/wiki/deleted.html
+2
-2
wiki/templates/wiki/edit.html
+3
-1
wiki/templates/wiki/includes/revision_info.html
+1
-1
wiki/templatetags/wiki_tags.py
+13
-2
wiki/views/article.py
+19
-22
No files found.
django_notify/views.py
View file @
832d7903
...
...
@@ -16,7 +16,8 @@ def get_notifications(request, latest_id=None, is_viewed=False, max_results=10):
if
not
latest_id
is
None
:
notifications
=
notifications
.
filter
(
latest_id__gt
=
latest_id
)
notifications
=
notifications
.
prefetch_related
(
'subscription'
)
notifications
=
notifications
[:
max_results
]
from
django.contrib.humanize.templatetags.humanize
import
naturaltime
...
...
wiki/conf/settings.py
View file @
832d7903
...
...
@@ -28,15 +28,26 @@ LOG_IPS_USERS = getattr(django_settings, 'WIKI_LOG_IPS_USERS', False)
# PERMISSIONS AND ACCOUNT HANDLING #
####################################
# NB! None of these callables need to handle anonymous users as they are treated
# in separate settings...
# A function returning True/False if a user has permission to assign
# permissions on an article
# Relevance: changing owner and group membership
CAN_ASSIGN
=
getattr
(
django_settings
,
'WIKI_CAN_ASSIGN'
,
lambda
article
,
user
:
user
.
has_perm
(
'wiki.assign'
))
# A function returning True/False if the owner of an article has permission to change
# the group to a user's own groups
# Relevance: changing group membership
CAN_ASSIGN_OWNER
=
getattr
(
django_settings
,
'WIKI_ASSIGN_OWNER'
,
lambda
article
,
user
:
False
)
# A function returning True/False if a user has permission to change
# read/write access for groups and others
CAN_CHANGE_PERMISSIONS
=
getattr
(
django_settings
,
'WIKI_CAN_CHANGE_PERMISSIONS'
,
lambda
article
,
user
:
article
.
owner
==
user
or
user
.
has_perm
(
'wiki.assign'
))
# Specifies if a user has access to soft deletion of articles
CAN_DELETE
=
getattr
(
django_settings
,
'WIKI_CAN_DELETE'
,
lambda
article
,
user
:
article
.
can_write
(
user
=
user
))
# A function returning True/False if a user has permission to change
# moderate, ie. lock articles and permanently delete content.
CAN_MODERATE
=
getattr
(
django_settings
,
'WIKI_CAN_MODERATE'
,
lambda
article
,
user
:
user
.
has_perm
(
'wiki.moderate'
))
...
...
wiki/core/permissions.py
0 → 100644
View file @
832d7903
from
wiki.conf
import
settings
# Article settings.
def
can_assign
(
article
,
user
):
return
not
user
.
is_anonymous
()
and
settings
.
CAN_ASSIGN
(
article
,
user
)
def
can_assign_owner
(
article
,
user
):
return
not
user
.
is_anonymous
()
and
settings
.
CAN_ASSIGN_OWNER
(
article
,
user
)
def
can_change_permissions
(
article
,
user
):
return
not
user
.
is_anonymous
()
and
settings
.
CAN_CHANGE_PERMISSIONS
(
article
,
user
)
def
can_delete
(
article
,
user
):
return
not
user
.
is_anonymous
()
and
settings
.
CAN_DELETE
(
article
,
user
)
def
can_moderate
(
article
,
user
):
return
not
user
.
is_anonymous
()
and
settings
.
CAN_MODERATE
(
article
,
user
)
def
can_admin
(
article
,
user
):
return
not
user
.
is_anonymous
()
and
settings
.
CAN_ADMIN
(
article
,
user
)
wiki/decorators.py
View file @
832d7903
...
...
@@ -20,7 +20,17 @@ def json_view(func):
return
response
return
wrap
def
get_article
(
func
=
None
,
can_read
=
True
,
can_write
=
False
,
deleted_contents
=
False
,
not_locked
=
False
):
def
response_forbidden
(
request
,
article
,
urlpath
):
if
request
.
user
.
is_anonymous
():
return
redirect
(
django_settings
.
LOGIN_URL
)
else
:
c
=
RequestContext
(
request
,
{
'article'
:
article
,
'urlpath'
:
urlpath
})
return
HttpResponseForbidden
(
render_to_string
(
"wiki/permission_denied.html"
,
context_instance
=
c
))
def
get_article
(
func
=
None
,
can_read
=
True
,
can_write
=
False
,
deleted_contents
=
False
,
not_locked
=
False
,
can_delete
=
False
,
can_moderate
=
False
):
"""View decorator for processing standard url keyword args: Intercepts the
keyword args path or article_id and looks up an article, calling the decorated
func with this ID.
...
...
@@ -88,20 +98,6 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals
else
:
raise
TypeError
(
'You should specify either article_id or path'
)
if
can_read
and
not
article
.
can_read
(
user
=
request
.
user
):
if
request
.
user
.
is_anonymous
():
return
redirect
(
django_settings
.
LOGIN_URL
)
else
:
c
=
RequestContext
(
request
,
{
'urlpath'
:
urlpath
})
return
HttpResponseForbidden
(
render_to_string
(
"wiki/permission_denied.html"
,
context_instance
=
c
))
if
can_write
and
not
article
.
can_write
(
user
=
request
.
user
):
if
request
.
user
.
is_anonymous
():
return
redirect
(
django_settings
.
LOGIN_URL
)
else
:
c
=
RequestContext
(
request
,
{
'urlpath'
:
urlpath
})
return
HttpResponseForbidden
(
render_to_string
(
"wiki/permission_denied.html"
,
context_instance
=
c
))
# If the article has been deleted, show a special page.
if
not
deleted_contents
and
article
.
current_revision
and
article
.
current_revision
.
deleted
:
if
urlpath
:
...
...
@@ -109,11 +105,20 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals
else
:
return
redirect
(
'wiki:deleted'
,
article_id
=
article
.
id
)
# The article is locked and this request is invalid because the view specified
# not to be requested for locked contents
if
article
.
current_revision
.
locked
and
not_locked
:
c
=
RequestContext
(
request
,
{
'urlpath'
:
urlpath
})
return
HttpResponseForbidden
(
render_to_string
(
"wiki/permission_denied.html"
,
context_instance
=
c
))
return
response_forbidden
(
request
,
article
,
urlpath
)
if
can_read
and
not
article
.
can_read
(
user
=
request
.
user
):
return
response_forbidden
(
request
,
article
,
urlpath
)
if
can_write
and
not
article
.
can_write
(
user
=
request
.
user
):
return
response_forbidden
(
request
,
article
,
urlpath
)
if
can_delete
and
not
article
.
can_delete
(
request
.
user
):
return
response_forbidden
(
request
,
article
,
urlpath
)
if
can_moderate
and
not
article
.
can_moderate
(
request
.
user
):
return
response_forbidden
(
request
,
article
,
urlpath
)
kwargs
[
'urlpath'
]
=
urlpath
...
...
@@ -124,5 +129,6 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals
else
:
return
lambda
func
:
get_article
(
func
,
can_read
=
can_read
,
can_write
=
can_write
,
deleted_contents
=
deleted_contents
,
not_locked
=
not_locked
)
not_locked
=
not_locked
,
can_delete
=
can_delete
,
can_moderate
=
can_moderate
)
wiki/forms.py
View file @
832d7903
...
...
@@ -14,6 +14,7 @@ from wiki.core.diff import simple_merge
from
django.forms.widgets
import
HiddenInput
from
wiki.core.plugins.base
import
PluginSettingsFormMixin
from
django.contrib.auth.models
import
User
from
wiki.core
import
permissions
class
SpamProtectionMixin
():
...
...
@@ -24,7 +25,10 @@ class SpamProtectionMixin():
current_revision can be any object inheriting from models.BaseRevisionMixin
"""
ipaddress
=
request
.
META
.
get
(
'REMOTE_ADDR'
,
None
)
if
not
ipaddress
==
"127.0.0.1"
:
raise
forms
.
ValidationError
(
_
(
'Only localhost... muahahaha'
))
# TODO: Finish this stuff and integrate it in forms....
class
CreateRootForm
(
forms
.
Form
):
...
...
@@ -269,25 +273,25 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm):
kwargs
[
'instance'
]
=
article
kwargs
[
'initial'
]
=
{
'locked'
:
article
.
current_revision
.
locked
}
super
(
PermissionsForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
can_change_groups
=
True
self
.
can_change_groups
=
False
self
.
can_assign
=
False
if
request
.
user
.
has_perm
(
"wiki.assign"
):
if
permissions
.
can_assign
(
article
,
request
.
user
):
self
.
can_assign
=
True
self
.
fields
[
'group'
]
.
queryset
=
models
.
Group
.
objects
.
all
()
elif
permissions
.
can_assign_owner
(
article
,
request
.
user
):
self
.
fields
[
'group'
]
.
queryset
=
models
.
Group
.
objects
.
filter
(
user
=
request
.
user
)
self
.
can_change_groups
=
True
else
:
self
.
fields
[
'group'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'group_read'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'group_write'
]
.
widget
=
forms
.
HiddenInput
()
if
not
self
.
can_assign
:
self
.
fields
[
'owner_username'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'recursive'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'locked'
]
.
widget
=
forms
.
HiddenInput
()
groups
=
models
.
Group
.
objects
.
filter
(
user
=
request
.
user
)
self
.
fields
[
'group'
]
.
queryset
=
groups
# Sanity: If somehow the article belongs to a group that the
# owner is not a member of, don't let the owner make any decisions
# for group permissions.
if
article
.
group
and
not
request
.
user
in
article
.
group
.
user_set
.
all
():
self
.
can_change_groups
=
False
self
.
fields
[
'group'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'group_read'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'group_write'
]
.
widget
=
forms
.
HiddenInput
()
self
.
fields
[
'owner_username'
]
.
initial
=
article
.
owner
.
username
if
article
.
owner
else
""
...
...
wiki/managers.py
View file @
832d7903
...
...
@@ -103,7 +103,7 @@ class ArticleManager(models.Manager):
class
ArticleFkManager
(
models
.
Manager
):
def
get_empty_query_set
(
self
):
return
ArticleFkEmptyQuerySet
()
return
ArticleFkEmptyQuerySet
(
model
=
self
.
model
)
def
get_query_set
(
self
):
return
ArticleFkQuerySet
(
self
.
model
,
using
=
self
.
_db
)
def
active
(
self
):
...
...
wiki/models/article.py
View file @
832d7903
...
...
@@ -7,7 +7,7 @@ from django.utils.safestring import mark_safe
from
django.utils.translation
import
ugettext_lazy
as
_
from
wiki.conf
import
settings
from
wiki.core
import
article_markdown
from
wiki.core
import
article_markdown
,
permissions
from
wiki.core.plugins
import
registry
as
plugin_registry
from
wiki
import
managers
from
mptt.models
import
MPTTModel
...
...
@@ -39,36 +39,57 @@ class Article(models.Model):
other_read
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
u'others read access'
))
other_write
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
u'others write access'
))
def
can_read
(
self
,
user
=
None
,
group
=
None
):
is_other
=
(
user
and
not
user
.
is_anonymous
()
)
or
settings
.
ANONYMOUS
if
is_other
and
self
.
other_read
:
# 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
group
==
self
.
group
:
return
True
if
self
.
group
and
user
and
user
.
groups
.
filter
(
id
=
self
.
group
.
id
):
if
self
.
group
and
user
.
groups
.
filter
(
id
=
self
.
group
.
id
):
return
True
if
user
and
user
.
has_perm
(
'wiki_moderator'
):
if
self
.
can_moderate
(
user
):
return
True
return
False
def
can_write
(
self
,
user
=
None
,
group
=
None
):
is_other
=
(
user
and
not
user
.
is_anonymous
()
)
or
settings
.
ANONYMOUS_WRITE
if
is_other
and
self
.
other_write
:
def
can_write
(
self
,
user
=
None
):
# Deny writing 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_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
group
==
self
.
group
:
return
True
if
self
.
group
and
user
and
user
.
groups
.
filter
(
id
=
self
.
group
.
id
):
return
True
if
user
and
user
.
has_perm
(
'wiki_moderator'
):
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
):
...
...
@@ -148,7 +169,7 @@ class Article(models.Model):
class
Meta
:
app_label
=
settings
.
APP_LABEL
permissions
=
(
(
"moderat
or
"
,
"Can edit all articles and lock/unlock/restore"
),
(
"moderat
e
"
,
"Can edit all articles and lock/unlock/restore"
),
(
"assign"
,
"Can change ownership of any article"
),
(
"grant"
,
"Can assign permissions to other users"
),
)
...
...
wiki/models/pluginbase.py
View file @
832d7903
...
...
@@ -42,6 +42,16 @@ class ArticlePlugin(models.Model):
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
# Permission methods - you should override these, if they don't fit your logic.
def
can_read
(
self
,
**
kwargs
):
return
self
.
article
.
can_read
(
**
kwargs
)
def
can_write
(
self
,
**
kwargs
):
return
self
.
article
.
can_write
(
**
kwargs
)
def
can_delete
(
self
,
user
):
return
self
.
article
.
can_delete
(
user
)
def
can_moderate
(
self
,
user
):
return
self
.
article
.
can_moderate
(
user
)
def
purge
(
self
):
"""Remove related contents completely, ie. media files."""
pass
...
...
@@ -52,8 +62,15 @@ class ArticlePlugin(models.Model):
class
ReusablePlugin
(
ArticlePlugin
):
"""Extend from this model if you have a plugin that may be related to many
articles. Please note that the ArticlePlugin.article ForeignKey STAYS! This
is in order to maintain an explicit set of permissions. If you do not like this,
you can override can_read and can_write."""
is in order to maintain an explicit set of permissions.
In general, it's quite complicated to maintain plugin content that's shared
between different articles. The best way to go is to avoid this. For inspiration,
look at wiki.plugins.attachments
You might have to override the permission methods (can_read, can_write etc.)
if you have certain needs for logic in your reusable plugin.
"""
# The article on which the plugin was originally created.
# Used to apply permissions.
ArticlePlugin
.
article
.
on_delete
=
models
.
SET_NULL
...
...
@@ -64,17 +81,17 @@ class ReusablePlugin(ArticlePlugin):
articles
=
models
.
ManyToManyField
(
Article
,
related_name
=
'shared_plugins_set'
)
# Permission methods - you may override these, if they don't fit your logic.
# Since the article relation may be None, we have to check for this
# before handling permissions....
def
can_read
(
self
,
**
kwargs
):
if
self
.
article
:
return
self
.
article
.
can_read
(
**
kwargs
)
return
False
return
self
.
article
.
can_read
(
**
kwargs
)
if
self
.
article
else
False
def
can_write
(
self
,
**
kwargs
):
if
self
.
article
:
return
self
.
article
.
can_write
(
**
kwargs
)
return
False
return
self
.
article
.
can_write
(
**
kwargs
)
if
self
.
article
else
False
def
can_delete
(
self
,
user
):
return
self
.
article
.
can_delete
(
user
)
if
self
.
article
else
False
def
can_moderate
(
self
,
user
):
return
self
.
article
.
can_moderate
(
user
)
if
self
.
article
else
False
def
save
(
self
,
*
args
,
**
kwargs
):
# Automatically make the original article the first one in the added set
...
...
@@ -151,12 +168,6 @@ class RevisionPlugin(ArticlePlugin):
'If you need to do a roll-back, simply change the value of this field.'
),
)
# Permissions... overwrite if necessary
def
can_read
(
self
,
**
kwargs
):
return
self
.
article
.
can_read
(
**
kwargs
)
def
can_write
(
self
,
**
kwargs
):
return
self
.
article
.
can_write
(
**
kwargs
)
def
add_revision
(
self
,
new_revision
,
save
=
True
):
"""
Sets the properties of a revision and ensures its the current
...
...
wiki/plugins/attachments/markdown_extensions.py
View file @
832d7903
...
...
@@ -26,7 +26,7 @@ class AttachmentPreprocessor(markdown.preprocessors.Preprocessor):
attachment_id
=
m
.
group
(
'id'
)
.
strip
()
try
:
attachment
=
models
.
Attachment
.
objects
.
get
(
articles
=
self
.
markdown
.
article
,
id
=
attachment_id
)
id
=
attachment_id
,
current_revision__deleted
=
False
)
url
=
reverse
(
'wiki:attachments_download'
,
kwargs
=
{
'article_id'
:
self
.
markdown
.
article
.
id
,
'attachment_id'
:
attachment
.
id
,})
line
=
line
.
replace
(
m
.
group
(
1
),
u"""<span class="attachment"><a href="
%
s" title="
%
s">
%
s</a>"""
%
...
...
wiki/plugins/attachments/models.py
View file @
832d7903
...
...
@@ -28,7 +28,10 @@ class Attachment(ReusablePlugin):
if
not
settings
.
ANONYMOUS
and
(
not
user
or
user
.
is_anonymous
()):
return
False
return
ReusablePlugin
.
can_write
(
self
,
**
kwargs
)
def
can_delete
(
self
,
user
):
return
self
.
can_write
(
user
=
user
)
class
Meta
:
verbose_name
=
_
(
u'attachment'
)
verbose_name_plural
=
_
(
u'attachments'
)
...
...
wiki/plugins/attachments/templates/wiki/plugins/attachments/history.html
View file @
832d7903
...
...
@@ -23,7 +23,7 @@
{% if revision.deleted %}
<span
class=
"badge badge-important"
>
{% trans "deleted" %}
</span>
{% endif %}
</td>
<td>
{% i
f revision.user %}{{ revision.user }}{% else %}{% if user|is_moderator %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif
%}
{% i
nclude "wiki/includes/revision_info.html" with revision=attachment.current_revision hidedate=1 hidenumber=1
%}
</td>
<td>
{{ revision.description|default:_("
<em>
No description
</em>
")|safe }}
</td>
<td>
{{ revision.get_filename }}
</td>
...
...
wiki/plugins/attachments/templates/wiki/plugins/attachments/index.html
View file @
832d7903
...
...
@@ -6,7 +6,7 @@
{% block wiki_contents_tab %}
<div
class=
"row-fluid"
>
<div
class=
"span
7
"
>
<div
class=
"span
8
"
>
<p
class=
"lead"
>
{% trans "The following files are available for this article. Copy the markdown tag to directly refer to a file from the article text." %}
</p>
{% for attachment in attachments %}
<table
class=
"table table-bordered table-striped"
style=
"width: 100%;"
>
...
...
@@ -26,7 +26,7 @@
<th>
{% trans "Markdown tag" %}
</th>
<th>
{% trans "Uploaded by" %}
</th>
<th>
{% trans "Size" %}
</th>
<td
style=
"text-align: right;"
rowspan=
"2"
>
<td
style=
"text-align: right;
white-space: nowrap;
"
rowspan=
"2"
>
{% if attachment|can_write:user %}
<p>
{% if not attachment.current_revision.deleted %}
...
...
@@ -59,7 +59,7 @@
<tr>
<td><code>
[attachment:{{ attachment.id }}]
</code></td>
<td>
{% i
f attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif
%}
{% i
nclude "wiki/includes/revision_info.html" with revision=attachment.current_revision hidedate=1 hidenumber=1
%}
</td>
<td>
{{ attachment.current_revision.get_size|filesizeformat }}
</td>
</tr>
...
...
@@ -69,7 +69,7 @@
{% endfor %}
</div>
{% if article|can_write:user %}
<div
class=
"span
5
"
style=
"min-width: 330px;"
>
<div
class=
"span
4
"
style=
"min-width: 330px;"
>
<div
class=
"accordion"
id=
"accordion_upload"
>
<div
class=
"accordion-group"
>
...
...
wiki/plugins/attachments/templates/wiki/plugins/attachments/search.html
View file @
832d7903
...
...
@@ -40,7 +40,7 @@
{% if attachment.current_revision.deleted %}
<span
class=
"badge badge-important"
>
{% trans "deleted" %}
</span>
{% endif %}
</td>
<td>
{% i
f attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif
%}
{% i
nclude "wiki/includes/revision_info.html" with revision=attachment.current_revision hidedate=1 hidenumber=1
%}
</td>
<td>
{{ attachment.current_revision.file.size|filesizeformat }}
</td>
<td
style=
"text-align: right"
>
...
...
wiki/plugins/attachments/views.py
View file @
832d7903
# -*- coding: utf-8 -*-
from
django.conf
import
settings
as
django_settings
from
django.contrib
import
messages
from
django.db
import
transaction
from
django.db.models
import
Q
...
...
@@ -11,9 +10,8 @@ from django.views.generic.base import TemplateView, View
from
django.views.generic.edit
import
FormView
from
django.views.generic.list
import
ListView
from
wiki.conf
import
settings
as
wiki_settings
from
wiki.core.http
import
send_file
from
wiki.decorators
import
get_article
from
wiki.decorators
import
get_article
,
response_forbidden
from
wiki.plugins.attachments
import
models
,
settings
,
forms
from
wiki.views.mixins
import
ArticleMixin
...
...
@@ -25,7 +23,7 @@ class AttachmentView(ArticleMixin, FormView):
@method_decorator
(
get_article
(
can_read
=
True
))
def
dispatch
(
self
,
request
,
article
,
*
args
,
**
kwargs
):
if
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
article
.
can_moderate
(
request
.
user
):
self
.
attachments
=
models
.
Attachment
.
objects
.
filter
(
articles
=
article
)
.
order_by
(
'current_revision__deleted'
,
'original_filename'
)
else
:
self
.
attachments
=
models
.
Attachment
.
objects
.
active
()
.
filter
(
articles
=
article
)
...
...
@@ -36,8 +34,9 @@ class AttachmentView(ArticleMixin, FormView):
# WARNING! The below decorator silences other exceptions that may occur!
@transaction.commit_manually
def
form_valid
(
self
,
form
):
if
self
.
request
.
user
.
is_anonymous
()
and
not
settings
.
ANONYMOUS
:
return
redirect
(
django_settings
.
LOGIN_URL
)
if
(
self
.
request
.
user
.
is_anonymous
()
and
not
settings
.
ANONYMOUS
or
not
self
.
article
.
can_write
(
self
.
request
.
user
)):
return
response_forbidden
(
self
.
request
,
self
.
article
,
self
.
urlpath
)
try
:
attachment_revision
=
form
.
save
(
commit
=
False
)
...
...
@@ -74,7 +73,7 @@ class AttachmentHistoryView(ArticleMixin, TemplateView):
@method_decorator
(
get_article
(
can_read
=
True
))
def
dispatch
(
self
,
request
,
article
,
attachment_id
,
*
args
,
**
kwargs
):
if
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
article
.
can_moderate
(
request
.
user
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
else
:
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
.
objects
.
active
(),
id
=
attachment_id
,
articles
=
article
)
...
...
@@ -92,11 +91,14 @@ class AttachmentReplaceView(ArticleMixin, FormView):
form_class
=
forms
.
AttachmentForm
template_name
=
"wiki/plugins/attachments/replace.html"
@method_decorator
(
get_article
(
can_
read
=
True
))
@method_decorator
(
get_article
(
can_
write
=
True
))
def
dispatch
(
self
,
request
,
article
,
attachment_id
,
*
args
,
**
kwargs
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
.
objects
.
active
(),
id
=
attachment_id
,
articles
=
article
)
if
not
self
.
attachment
.
can_write
(
user
=
request
.
user
):
return
redirect
(
wiki_settings
.
LOGIN_URL
)
if
self
.
request
.
user
.
is_anonymous
()
and
not
settings
.
ANONYMOUS
:
return
response_forbidden
(
request
,
article
,
kwargs
.
get
(
'urlpath'
,
None
))
if
article
.
can_moderate
(
request
.
user
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
else
:
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
.
objects
.
active
(),
id
=
attachment_id
,
articles
=
article
)
return
super
(
AttachmentReplaceView
,
self
)
.
dispatch
(
request
,
article
,
*
args
,
**
kwargs
)
def
form_valid
(
self
,
form
):
...
...
@@ -138,7 +140,10 @@ class AttachmentDownloadView(ArticleMixin, View):
@method_decorator
(
get_article
(
can_read
=
True
))
def
dispatch
(
self
,
request
,
article
,
attachment_id
,
*
args
,
**
kwargs
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
if
article
.
can_moderate
(
request
.
user
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
else
:
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
.
objects
.
active
(),
id
=
attachment_id
,
articles
=
article
)
revision_id
=
kwargs
.
get
(
'revision_id'
,
None
)
if
revision_id
:
self
.
revision
=
get_object_or_404
(
models
.
AttachmentRevision
,
id
=
revision_id
,
attachment__articles
=
article
)
...
...
@@ -162,7 +167,7 @@ class AttachmentChangeRevisionView(ArticleMixin, View):
@method_decorator
(
get_article
(
can_write
=
True
))
def
dispatch
(
self
,
request
,
article
,
attachment_id
,
revision_id
,
*
args
,
**
kwargs
):
if
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
article
.
can_moderate
(
request
.
user
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
else
:
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
.
objects
.
active
(),
id
=
attachment_id
,
articles
=
article
)
...
...
@@ -188,8 +193,9 @@ class AttachmentAddView(ArticleMixin, View):
return
super
(
AttachmentAddView
,
self
)
.
dispatch
(
request
,
article
,
*
args
,
**
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
attachment
.
articles
.
add
(
self
.
article
)
self
.
attachment
.
save
()
if
self
.
attachment
.
articles
.
filter
(
id
=
self
.
article
.
id
):
self
.
attachment
.
articles
.
add
(
self
.
article
)
self
.
attachment
.
save
()
messages
.
success
(
self
.
request
,
_
(
u'Added a reference to "
%(att)
s" from "
%(art)
s".'
)
%
{
'att'
:
self
.
attachment
.
original_filename
,
'art'
:
self
.
article
.
current_revision
.
title
})
...
...
@@ -203,10 +209,9 @@ class AttachmentDeleteView(ArticleMixin, FormView):
@method_decorator
(
get_article
(
can_write
=
True
))
def
dispatch
(
self
,
request
,
article
,
attachment_id
,
*
args
,
**
kwargs
):
if
request
.
user
.
has_perm
(
"wiki.moderator"
):
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
else
:
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
.
objects
.
active
(),
id
=
attachment_id
,
articles
=
article
)
self
.
attachment
=
get_object_or_404
(
models
.
Attachment
,
id
=
attachment_id
,
articles
=
article
)
if
not
self
.
attachment
.
can_delete
(
request
.
user
):
return
response_forbidden
(
request
,
article
,
kwargs
.
get
(
'urlpath'
,
None
))
return
super
(
AttachmentDeleteView
,
self
)
.
dispatch
(
request
,
article
,
*
args
,
**
kwargs
)
def
form_valid
(
self
,
form
):
...
...
@@ -254,7 +259,6 @@ class AttachmentSearchView(ArticleMixin, ListView):
qs
=
qs
.
filter
(
Q
(
original_filename__contains
=
self
.
query
)
|
Q
(
current_revision__description__contains
=
self
.
query
)
|
Q
(
article__current_revision__title__contains
=
self
.
query
))
qs
=
qs
.
exclude
(
articles
=
self
.
article
)
return
qs
def
get_context_data
(
self
,
**
kwargs
):
...
...
wiki/plugins/images/models.py
View file @
832d7903
...
...
@@ -33,6 +33,9 @@ class Image(RevisionPlugin):
return
False
return
RevisionPlugin
.
can_write
(
self
,
**
kwargs
)
def
can_delete
(
self
,
user
):
return
self
.
can_write
(
user
=
user
)
class
Meta
:
verbose_name
=
_
(
u'image'
)
verbose_name_plural
=
_
(
u'images'
)
...
...
wiki/plugins/images/templates/wiki/plugins/images/index.html
View file @
832d7903
...
...
@@ -45,7 +45,7 @@
{% trans "Remove image" %}
</a>
{% endif %}
{% if
user|is_moderato
r %}
{% if
article|can_moderate:use
r %}
<br
/>
<a
href=
"{% url 'wiki:images_purge' path=urlpath.path article_id=article.id image_id=image.id %}"
>
<span
class=
"icon-trash"
></span>
...
...
wiki/plugins/images/views.py
View file @
832d7903
from
django.contrib
import
messages
from
django.contrib.auth.decorators
import
permission_required
from
django.core.urlresolvers
import
reverse
from
django.shortcuts
import
get_object_or_404
,
redirect
from
django.utils.decorators
import
method_decorator
...
...
@@ -27,8 +26,8 @@ class ImageView(ArticleMixin, ListView):
return
super
(
ImageView
,
self
)
.
dispatch
(
request
,
article
,
*
args
,
**
kwargs
)
def
get_queryset
(
self
):
if
(
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
)
or
self
.
article
.
owner
==
self
.
request
.
user
):
if
(
self
.
article
.
can_moderate
(
self
.
request
.
user
)
or
self
.
article
.
can_delete
(
self
.
request
.
user
)
):
images
=
models
.
Image
.
objects
.
filter
(
article
=
self
.
article
)
else
:
images
=
models
.
Image
.
objects
.
filter
(
article
=
self
.
article
,
...
...
@@ -76,8 +75,7 @@ class PurgeView(ArticleMixin, FormView):
permanent
=
False
form_class
=
forms
.
PurgeForm
@method_decorator
(
permission_required
(
'wiki.moderator'
,
login_url
=
wiki_settings
.
LOGIN_URL
))
@method_decorator
(
get_article
(
can_write
=
True
))
@method_decorator
(
get_article
(
can_write
=
True
,
can_moderate
=
True
))
def
dispatch
(
self
,
request
,
article
,
*
args
,
**
kwargs
):
self
.
image
=
get_object_or_404
(
models
.
Image
,
article
=
article
,
id
=
kwargs
.
get
(
'image_id'
,
None
))
...
...
wiki/plugins/links/views.py
View file @
832d7903
...
...
@@ -4,11 +4,13 @@ from django.utils.decorators import method_decorator
class
QueryUrlPath
(
View
):
# TODO: get_article does not actually support JSON responses
@method_decorator
(
json_view
)
@method_decorator
(
get_article
(
can_read
=
True
))
def
dispatch
(
self
,
request
,
article
,
*
args
,
**
kwargs
):
max_num
=
kwargs
.
pop
(
'max_num'
,
20
)
# TODO: Move this import when
# TODO: Move this import when circularity issue is resolved
# https://github.com/benjaoming/django-wiki/issues/23
from
wiki
import
models
query
=
request
.
GET
.
get
(
'query'
,
None
)
...
...
wiki/plugins/notifications/TODO.md
0 → 100644
View file @
832d7903
*
Create signal for new articles and automatically subscribe users creating the article to notifications?
wiki/templates/wiki/base.html
View file @
832d7903
...
...
@@ -22,11 +22,13 @@
#id_title
{
font-size
:
20px
;
height
:
30px
;
padding
:
6px
;
width
:
98%
;}
#id_summary
{
width
:
98%
;
padding
:
6px
;}
.table
{
font-size
:
90%
;}
#article_edit_form
label
{
max-width
:
100px
;}
#article_edit_form
.controls
{
margin-left
:
120px
;}
.form-horizontal
label
{
font-size
:
16px
;
font-weight
:
normal
;
color
:
#777
;}
.settings-form
label
{
min-width
:
250px
;
font-size
:
inherit
;
font-weight
:
normal
;}
.settings-form
.controls
{
margin-left
:
270px
;}
.settings-form
select
{}
...
...
wiki/templates/wiki/deleted.html
View file @
832d7903
...
...
@@ -19,7 +19,7 @@
<div
class=
"row-fluid"
>
{% if not article.current_revision.locked or
user|is_moderato
r %}
{% if not article.current_revision.locked or
article|can_delete:use
r %}
<div
class=
"span6"
>
<div
class=
"well"
>
<h2>
{% trans "Restore" %}
</h2>
...
...
@@ -34,7 +34,7 @@
</div>
{% endif %}
{% if
user|is_moderato
r %}
{% if
article|can_moderate:use
r %}
<div
class=
"span6"
>
<div
class=
"well"
>
<h2>
{% trans "Purge deletion" %}
</h2>
...
...
wiki/templates/wiki/edit.html
View file @
832d7903
...
...
@@ -34,12 +34,14 @@
<a
class=
"btn btn-large btn-primary"
onclick=
"document.getElementById('article_edit_form').target=''; document.getElementById('article_edit_form').action='{% url 'wiki:edit' path=urlpath.path article_id=article.id %}'; $('#article_edit_form').submit();"
href=
"#"
>
<span
class=
"icon-ok"
></span>
{% trans "Save changes" %}
</
button
>
</
a
>
{% if article|can_delete:user %}
<a
href=
"{% url 'wiki:delete' path=urlpath.path article_id=article.id %}"
class=
"pull-right btn"
>
<span
class=
"icon-trash"
></span>
{% trans "Delete article" %}
</a>
{% endif %}
</div>
<div
class=
"modal hide fade"
id=
"previewModal"
style=
"width: 80%; min-height: 500px; margin-left: -40%;"
>
...
...
wiki/templates/wiki/includes/revision_info.html
View file @
832d7903
...
...
@@ -7,7 +7,7 @@
{% load wiki_tags i18n %}
{
{ revision.created }} (#{{ revision.revision_number }}) {% trans "by" %} {% if revision.user %}{{ revision.user }}{% else %}{% if user|is_moderato
r %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
{
% if not hidedate %}{{ revision.created }}{% endif %} {% if not hidenumber %}(#{{ revision.revision_number }}) {% trans "by" %}{% endif %} {% if revision.user %}{{ revision.user }}{% else %}{% if article|can_moderate:use
r %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
{% if revision == current_revision %}
<strong>
*
</strong>
{% endif %}
...
...
wiki/templatetags/wiki_tags.py
View file @
832d7903
...
...
@@ -63,9 +63,20 @@ def can_read(obj, user):
@register.filter
def
can_write
(
obj
,
user
):
"""Articles and plugins have a can_write method..."""
return
obj
.
can_write
(
**
{
'user'
:
user
})
return
obj
.
can_write
(
user
=
user
)
@register.filter
def
can_delete
(
obj
,
user
):
"""Articles and plugins have a can_delete method..."""
return
obj
.
can_delete
(
user
)
@register.filter
def
can_moderate
(
obj
,
user
):
"""Articles and plugins have a can_moderate method..."""
return
obj
.
can_moderate
(
user
)
@register.filter
def
is_moderator
(
user
):
"""Tells if a user is a moderator"""
return
user
.
has_perm
(
'wiki.moderat
or
'
)
return
user
.
has_perm
(
'wiki.moderat
e
'
)
wiki/views/article.py
View file @
832d7903
...
...
@@ -21,8 +21,7 @@ from django.core.urlresolvers import reverse
from
django.db
import
transaction
from
wiki.core.exceptions
import
NoRootURL
from
django_notify.decorators
import
disable_notify
from
django.http
import
HttpResponseForbidden
from
django.template.loader
import
render_to_string
from
wiki.core
import
permissions
class
ArticleView
(
ArticleMixin
,
TemplateView
):
...
...
@@ -83,14 +82,13 @@ class Create(FormView, ArticleMixin):
'other_read'
:
self
.
article
.
other_read
,
'other_write'
:
self
.
article
.
other_write
,
})
# TODO: Subscribe user to new article and send notifications that user was subscribed.
messages
.
success
(
self
.
request
,
_
(
u"New article '
%
s' created."
)
%
self
.
newpath
.
article
.
current_revision
.
title
)
transaction
.
commit
()
# TODO: Handle individual exceptions better and give good feedback.
except
Exception
,
e
:
transaction
.
rollback
()
if
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
self
.
request
.
user
.
is_superuser
(
):
messages
.
error
(
self
.
request
,
_
(
u"There was an error creating this article:
%
s"
)
%
str
(
e
))
else
:
messages
.
error
(
self
.
request
,
_
(
u"There was an error creating this article."
))
...
...
@@ -117,7 +115,7 @@ class Delete(FormView, ArticleMixin):
form_class
=
forms
.
DeleteForm
template_name
=
"wiki/delete.html"
@method_decorator
(
get_article
(
can_write
=
True
,
not_locked
=
True
))
@method_decorator
(
get_article
(
can_write
=
True
,
not_locked
=
True
,
can_delete
=
True
))
def
dispatch
(
self
,
request
,
article
,
*
args
,
**
kwargs
):
return
self
.
dispatch1
(
request
,
article
,
*
args
,
**
kwargs
)
...
...
@@ -147,7 +145,7 @@ class Delete(FormView, ArticleMixin):
def
get_form
(
self
,
form_class
):
form
=
super
(
Delete
,
self
)
.
get_form
(
form_class
)
if
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
self
.
article
.
can_delete
(
self
.
request
.
user
):
form
.
fields
[
'purge'
]
.
widget
=
forms
.
forms
.
CheckboxInput
()
return
form
...
...
@@ -179,7 +177,8 @@ class Delete(FormView, ArticleMixin):
cd
=
form
.
cleaned_data
cannot_delete_children
=
False
if
self
.
children_slice
and
not
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
):
can_moderate
=
self
.
article
.
can_moderate
(
self
.
request
.
user
)
if
self
.
children_slice
and
not
can_moderate
:
cannot_delete_children
=
True
if
self
.
cannot_delete_root
or
cannot_delete_children
:
...
...
@@ -189,7 +188,7 @@ class Delete(FormView, ArticleMixin):
# First, remove children
self
.
delete_children
(
purge
=
cd
[
'purge'
])
if
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
)
and
cd
[
'purge'
]:
if
can_moderate
and
cd
[
'purge'
]:
self
.
article
.
delete
()
messages
.
success
(
self
.
request
,
_
(
u'This article together with all its contents are now completely gone! Thanks!'
))
else
:
...
...
@@ -206,7 +205,7 @@ class Delete(FormView, ArticleMixin):
def
get_context_data
(
self
,
**
kwargs
):
cannot_delete_children
=
False
if
self
.
children_slice
and
not
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
self
.
children_slice
and
not
self
.
article
.
can_moderate
(
self
.
request
.
user
):
cannot_delete_children
=
True
kwargs
[
'delete_form'
]
=
kwargs
.
pop
(
'form'
,
None
)
...
...
@@ -336,7 +335,8 @@ class Deleted(Delete):
# Restore
if
(
request
.
GET
.
get
(
'restore'
,
False
)
and
(
not
article
.
current_revision
.
locked
or
request
.
user
.
has_perm
(
'wiki.moderator'
))):
(
not
article
.
current_revision
.
locked
and
article
.
can_delete
(
request
.
user
))
or
article
.
can_moderate
(
request
.
user
)):
self
.
delete_children
(
restore
=
True
)
revision
=
models
.
ArticleRevision
()
revision
.
inherit_predecessor
(
self
.
article
)
...
...
@@ -409,9 +409,13 @@ class Dir(ListView, ArticleMixin):
model
=
models
.
URLPath
paginate_by
=
30
@method_decorator
(
get_article
(
can_read
=
True
))
def
dispatch
(
self
,
request
,
article
,
*
args
,
**
kwargs
):
return
super
(
Dir
,
self
)
.
dispatch
(
request
,
article
,
*
args
,
**
kwargs
)
def
get_queryset
(
self
):
children
=
self
.
urlpath
.
get_children
()
.
can_read
(
self
.
request
.
user
)
.
select_related_common
()
.
order_by
(
'article__current_revision__title'
)
if
not
self
.
request
.
user
.
has_perm
(
'wiki.moderator'
):
if
not
self
.
article
.
can_moderate
(
self
.
request
.
user
):
children
=
children
.
active
()
return
children
...
...
@@ -431,14 +435,6 @@ class Dir(ListView, ArticleMixin):
return
kwargs
def
get_template_names
(
self
):
#WHY IS THIS CALLED???????
return
[
self
.
__class__
.
template_name
]
@method_decorator
(
get_article
(
can_read
=
True
))
def
dispatch
(
self
,
request
,
article
,
*
args
,
**
kwargs
):
return
super
(
Dir
,
self
)
.
dispatch
(
request
,
article
,
*
args
,
**
kwargs
)
class
Plugin
(
View
):
...
...
@@ -464,8 +460,7 @@ class Settings(ArticleMixin, TemplateView):
Return all settings forms that can be filled in
"""
settings_forms
=
[
F
for
F
in
plugin_registry
.
get_settings_forms
()]
if
(
self
.
request
.
user
.
has_perm
(
'wiki.assign'
)
or
self
.
article
.
owner
==
self
.
request
.
user
):
if
permissions
.
can_change_permissions
(
self
.
article
,
self
.
request
.
user
):
settings_forms
.
append
(
self
.
permission_form_class
)
settings_forms
.
sort
(
key
=
lambda
form
:
form
.
settings_order
)
for
i
in
range
(
len
(
settings_forms
)):
...
...
@@ -627,12 +622,14 @@ def root_create(request):
try
:
root
=
models
.
URLPath
.
root
()
if
not
root
.
article
:
# TODO: This is too dangerous... let's say there is no root.article and we end up here,
# then it might cascade to delete a lot of things on an existing installation.... / benjaoming
root
.
delete
()
raise
NoRootURL
return
redirect
(
'wiki:get'
,
path
=
root
.
path
)
except
NoRootURL
:
pass
if
not
request
.
user
.
has_perm
(
'wiki.add_article'
):
if
not
request
.
user
.
is_superuser
(
):
return
redirect
(
settings
.
LOGIN_URL
+
"?next="
+
reverse
(
"wiki:root_create"
))
if
request
.
method
==
'POST'
:
create_form
=
forms
.
CreateRootForm
(
request
.
POST
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment