Compare commits

..

19 Commits

Author SHA1 Message Date
James Turk
2aff42e4a9 urls 2013-12-04 14:48:15 -05:00
James Turk
74a4f00ac3 fix url tag 2013-12-04 14:35:21 -05:00
James Turk
6cc6f46348 markupfield dep 2013-12-04 14:22:00 -05:00
James Turk
236239e774 test on new python/django combos 2013-12-04 14:19:17 -05:00
James Turk
a20417af0b fix travis settings 2012-03-23 16:21:02 -04:00
James Turk
b674c84144 start testing w/ django 1.4 2012-03-23 15:59:15 -04:00
James Turk
56f37a55d1 update changelog 2012-03-21 15:28:17 -04:00
James Turk
2c3e259b36 travis ci 2012-03-21 15:23:29 -04:00
James Turk
d40076186b Merge pull request #1 from jcarbaugh/master
fix redirect_to and make revert work
2012-01-05 08:47:01 -08:00
Jeremy Carbaugh
89713013dc make revert feature work 2012-01-05 11:43:00 -05:00
Jeremy Carbaugh
9160ed2fd9 set blank=True on Article.redirect_to 2012-01-05 10:46:32 -05:00
Jeremy Carbaugh
0864edd921 allow null editors 2011-04-29 11:02:32 -04:00
Jeremy Carbaugh
e41f77fa72 update README to add new setting 2011-04-13 14:11:34 -04:00
Jeremy Carbaugh
aff4f04d5e add autolockarticles management command 2011-04-13 14:00:49 -04:00
Jeremy Carbaugh
a255c92198 add csrf token 2011-04-13 14:00:28 -04:00
James Turk
e9703a0e93 add ESCAPE_HTML option 2011-02-22 12:03:04 -05:00
James Turk
84ea94c78c fix to settings for test 2011-02-09 11:50:55 -05:00
James Turk
20a483e754 MARKUPWIKI_EDITOR/MODERATOR_TEST_FUNC options 2011-02-09 11:50:49 -05:00
James Turk
85f2bac6fd use new escape_html option 2010-09-28 10:39:45 -04:00
15 changed files with 145 additions and 34 deletions

18
.travis.yml Normal file
View File

@ -0,0 +1,18 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
env:
- DJANGO_PACKAGE="Django<1.5"
- DJANGO_PACKAGE="Django>=1.5,<1.6"
- DJANGO_PACKAGE="Django>=1.6,<1.7"
install: pip install $DJANGO_PACKAGE markdown docutils django-markupfield --use-mirrors
script: django-admin.py test --settings=example.settings --pythonpath=.
matrix:
exclude:
- python: "3.3"
env: DJANGO_PACKAGE="Django<1.5"
notifications:
email:
- james.p.turk@gmail.com

View File

@ -1,7 +1,12 @@
0.3.0
=====
- lots of tests
-
- new escape_html option
- csrf token
- autolockarticles management command
- null editors
- fix revert
0.2.0
===================

View File

@ -13,7 +13,7 @@ might not be for you.
Requirements
============
django-markupwiki depends on django >= 1.2a, django-markupfield >= 1.0.0a2 and
django-markupwiki depends on django >= 1.2, django-markupfield >= 1.0.0b and
libraries for whichever markup options you wish to include.
@ -84,7 +84,9 @@ to customize the behavior.
``MARKUPWIKI_MARKUP_TYPE_EDITABLE``
if False user won't have option to change markup type (default: True)
``MARKUPWIKI_MARKUP_TYPES``
a tuple of string and callable pairs the callable is used to 'render' a markup type.
a tuple of string and callable pairs the callable is used to 'render' a markup type.
``MARKUPWIKI_AUTOLOCK_TIMEDELTA``
a datetime.timedelta object that defines the age at which articles get automatically locked by the *autolockarticles* management command.
Example::

Binary file not shown.

View File

@ -1,3 +1,5 @@
import os
DEBUG = True
TEMPLATE_DEBUG = DEBUG
@ -27,7 +29,7 @@ MIDDLEWARE_CLASSES = (
ROOT_URLCONF = 'example.urls'
TEMPLATE_DIRS = ( 'templates', )
TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates'), )
INSTALLED_APPS = (
'django.contrib.auth',
@ -37,3 +39,5 @@ INSTALLED_APPS = (
'django.contrib.messages',
'markupwiki',
)
SITE_ID = 1

View File

@ -1,4 +1,4 @@
from django.conf.urls.defaults import *
from django.conf.urls import *
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin

View File

View File

@ -0,0 +1,22 @@
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Min
from markupwiki.models import Article, PUBLIC, LOCKED, DELETED
import datetime
class Command(BaseCommand):
help = 'Auto-locks articles based on time and other factors'
def handle(self, *args, **options):
''' Lock any public article that was created earlier than
MARKUPWIKI_AUTOLOCK_TIMEDELTA ago.
'''
timedelta = getattr(settings, 'MARKUPWIKI_AUTOLOCK_TIMEDELTA', None)
if timedelta is not None:
ts = datetime.datetime.now() - timedelta
qs = Article.objects.filter(status=PUBLIC).annotate(
timestamp=Min('versions__timestamp')).filter(timestamp__lte=ts)
qs.update(status=LOCKED)

View File

@ -8,9 +8,17 @@ from markupfield.fields import MarkupField
from markupfield import markup
from markupwiki.utils import wikify_markup_wrapper
DEFAULT_MARKUP_TYPE = getattr(settings, 'MARKUPWIKI_DEFAULT_MARKUP_TYPE', 'markdown')
DEFAULT_MARKUP_TYPE = getattr(settings, 'MARKUPWIKI_DEFAULT_MARKUP_TYPE',
'markdown')
WRITE_LOCK_SECONDS = getattr(settings, 'MARKUPWIKI_WRITE_LOCK_SECONDS', 300)
MARKUP_TYPES = getattr(settings, 'MARKUPWIKI_MARKUP_TYPES', markup.DEFAULT_MARKUP_TYPES)
MARKUP_TYPES = getattr(settings, 'MARKUPWIKI_MARKUP_TYPES',
markup.DEFAULT_MARKUP_TYPES)
ESCAPE_HTML = getattr(settings, 'MARKUPWIKI_ESCAPE_HTML',
True)
EDITOR_TEST_FUNC = getattr(settings, 'MARKUPWIKI_EDITOR_TEST_FUNC',
lambda u: u.is_authenticated())
MODERATOR_TEST_FUNC = getattr(settings, 'MARKUPWIKI_MODERATOR_TEST_FUNC',
lambda u: u.is_staff)
# add make_wiki_links to MARKUP_TYPES
WIKI_MARKUP_TYPES = []
@ -26,9 +34,9 @@ ARTICLE_STATUSES = (
class Article(models.Model):
title = models.CharField(max_length=200)
creator = models.ForeignKey(User, related_name='wiki_articles')
creator = models.ForeignKey(User, related_name='wiki_articles', blank=True, null=True)
status = models.IntegerField(choices=ARTICLE_STATUSES, default=PUBLIC)
redirect_to = models.ForeignKey('self', null=True)
redirect_to = models.ForeignKey('self', blank=True, null=True)
def __unicode__(self):
return self.title
@ -42,6 +50,11 @@ class Article(models.Model):
if '/' in self.title:
return self.title.rsplit('/',1)[0]
# def save(self, **kwargs):
# if self.creator is not None and self.creator.is_anonymous():
# self.creator = None
# super(Article, self).save(**kwargs)
def get_absolute_url(self):
return reverse('view_article', args=[self.title])
@ -56,28 +69,33 @@ class Article(models.Model):
def is_editable_by_user(self, user):
if self.status in (LOCKED, DELETED):
return user.is_staff
return MODERATOR_TEST_FUNC(user)
else:
return user.is_authenticated()
return EDITOR_TEST_FUNC(user)
def get_write_lock(self, user, release=False):
def get_write_lock(self, user_or_request, release=False):
if hasattr(user_or_request, 'session'):
lock_id = user_or_request.session.session_key
else:
lock_id = user_or_request.id
cache_key = 'markupwiki_articlelock_%s' % self.id
lock = cache.get(cache_key)
if lock:
if release:
cache.delete(cache_key)
return lock == user.id
return lock == lock_id
if not release:
cache.set(cache_key, user.id, WRITE_LOCK_SECONDS)
cache.set(cache_key, lock_id, WRITE_LOCK_SECONDS)
return True
class ArticleVersion(models.Model):
article = models.ForeignKey(Article, related_name='versions')
author = models.ForeignKey(User, related_name='article_versions')
author = models.ForeignKey(User, related_name='article_versions', blank=True, null=True)
number = models.PositiveIntegerField()
body = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
markup_choices=WIKI_MARKUP_TYPES)
markup_choices=WIKI_MARKUP_TYPES,
escape_html=ESCAPE_HTML)
comment = models.CharField(max_length=200, blank=True)
timestamp = models.DateTimeField(auto_now_add=True)
@ -89,6 +107,11 @@ class ArticleVersion(models.Model):
def __unicode__(self):
return '%s rev #%s' % (self.article, self.number)
# def save(self, **kwargs):
# if self.author is not None and self.author.is_anonymous():
# self.author = None
# super(ArticleVersion, self).save(**kwargs)
def get_absolute_url(self):
return reverse('article_version', args=[self.article.title, self.number])

View File

@ -6,7 +6,7 @@
{% if article and mod_form %}
<div class="article_moderation">
<form method="POST" action="{% url update_article_status article.title %}">
<form method="POST" action="{% url "update_article_status" article.title %}">
<ul>
<li>{{mod_form.status.label_tag}} {{ mod_form.status }}</li>
<li>
@ -16,7 +16,7 @@
</li>
</ul>
</form>
<form method="POST" action="{% url rename_article article.title %}">
<form method="POST" action="{% url "rename_article" article.title %}">
<ul>
{{ rename_form.as_ul}}
<li>
@ -31,7 +31,7 @@
<h2 class="article_title">
{% block article_title %}
{% if article.section_name %}<a href="{% url view_article article.section_name %}">{{article.section_name}}</a> / {% endif %}
{% if article.section_name %}<a href="{% url "view_article" article.section_name %}">{{article.section_name}}</a> / {% endif %}
{{article.display_title}}
{% if article.is_deleted %} [deleted] {% endif %}
@ -42,10 +42,10 @@
<div class="article_meta">
{% block article_meta %}
{% if article.editable %}
<a href="{% url edit_article article.title %}">edit article</a> |
<a href="{% url "edit_article" article.title %}">edit article</a> |
{% endif %}
{% if article %}
<a href="{% url article_history article.title %}">view history</a>
<a href="{% url "article_history" article.title %}">view history</a>
{% endif %}
{% endblock %}
</div>

View File

@ -10,13 +10,14 @@
{% block article_meta %}
{% if article %}
<a href="{% url view_article article.title %}">view article</a> |
<a href="{% url article_history article.title %}">view history</a>
<a href="{% url "view_article" article.title %}">view article</a> |
<a href="{% url "article_history" article.title %}">view history</a>
{% endif %}
{% endblock %}
{% block article_body %}
<form method="POST" action=".">
{% csrf_token %}
<ul>
<li>{{form.body}}</li>
<li>{{form.comment.label_tag}} {{ form.comment }} </li>

View File

@ -12,6 +12,32 @@
{% endblock %}
{% block article_body %}
<form action="{% url revert article.title %}" method="post">
{% csrf_token %}
<label for="revert-version">Revert to</label>
<select name="revision" id="revert-version">
{% for version in versions reversed %}
{% if not forloop.first %}
<option value="{{ version.number }}">
{% if version.number == 0 %}
Initial
{% else %}
{{ version.number }}
{% endif %}
</option>
{% endif %}
{% endfor %}
</select>
<button class="compareBtn" type="submit">
<span>Revert to version</span>
</button>
</form>
<table>
<thead> <tr>
<th>Version</th>
@ -46,4 +72,5 @@
<span>Compare Selected Versions</span>
</button>
</form>
{% endblock %}

View File

@ -1,4 +1,4 @@
from django.conf.urls.defaults import *
from django.conf.urls import *
from markupwiki.feeds import LatestEditsFeed, LatestArticleEditsFeed
WIKI_REGEX = r'^(?P<title>.+)'
@ -12,5 +12,6 @@ urlpatterns = patterns('markupwiki.views',
url(WIKI_REGEX + '/history/$', 'article_history', name='article_history'),
url(WIKI_REGEX + '/history/(?P<n>\d+)/$', 'view_article', name='article_version'),
url(WIKI_REGEX + '/diff/$', 'article_diff', name='article_diff'),
url(WIKI_REGEX + '/revert/$', 'revert', name='revert'),
url(WIKI_REGEX + '/$', 'view_article', name='view_article'),
)

View File

@ -8,10 +8,16 @@ from django.contrib import messages
from django.http import Http404
from django.template import RequestContext
from django.utils.functional import wraps
from markupwiki.models import Article, PUBLIC, DELETED, LOCKED
from markupwiki.models import Article, ArticleVersion, PUBLIC, DELETED, LOCKED
from markupwiki.forms import ArticleForm, StaffModerationForm, ArticleRenameForm
CREATE_MISSING_ARTICLE = getattr(settings, 'MARKUPWIKI_CREATE_MISSING_ARTICLES', True)
CREATE_MISSING_ARTICLE = getattr(settings,
'MARKUPWIKI_CREATE_MISSING_ARTICLES', True)
EDITOR_TEST_FUNC = getattr(settings, 'MARKUPWIKI_EDITOR_TEST_FUNC',
lambda u: u.is_authenticated())
MODERATOR_TEST_FUNC = getattr(settings, 'MARKUPWIKI_MODERATOR_TEST_FUNC',
lambda u: u.is_staff)
def title_check(view):
def new_view(request, title, *args, **kwargs):
@ -71,7 +77,7 @@ def view_article(request, title, n=None):
context_instance=RequestContext(request))
@title_check
@login_required
@user_passes_test(EDITOR_TEST_FUNC)
def edit_article(request, title):
''' edit (or create) an article
@ -109,11 +115,13 @@ def edit_article(request, title):
form = ArticleForm()
elif request.method == 'POST':
form = ArticleForm(request.POST)
user = None if request.user.is_anonymous() else request.user
if form.is_valid():
if not article:
# if article doesn't exist create it and start num at 0
article = Article.objects.create(title=title,
creator=request.user)
creator=user)
num = 0
else:
if not article.get_write_lock(request.user):
@ -127,11 +135,11 @@ def edit_article(request, title):
# create a new version attached to article specified in name
version = form.save(False)
version.article = article
version.author = request.user
version.author = user
version.number = num
version.save()
article.get_write_lock(request.user, release=True)
article.get_write_lock(user or request, release=True)
# redirect to view article on save
return redirect(article)
@ -142,7 +150,7 @@ def edit_article(request, title):
@require_POST
@user_passes_test(lambda u: u.is_staff)
@user_passes_test(MODERATOR_TEST_FUNC)
@title_check
def article_status(request, title):
''' POST-only view to update article status (staff-only)
@ -154,14 +162,14 @@ def article_status(request, title):
return redirect(article)
@require_POST
@user_passes_test(lambda u: u.is_staff)
@user_passes_test(MODERATOR_TEST_FUNC)
@title_check
def revert(request, title):
''' POST-only view to revert article to a specific revision
'''
article = get_object_or_404(Article, title=title)
revision_id = int(request.POST['revision'])
revision = get_object_or_404(revision, number=revision_id)
revision = get_object_or_404(article.versions, number=revision_id)
ArticleVersion.objects.create(article=article, author=request.user,
number=article.versions.latest().number,
comment='reverted to r%s' % revision_id,
@ -170,7 +178,7 @@ def revert(request, title):
return redirect(article)
@require_POST
@user_passes_test(lambda u: u.is_staff)
@user_passes_test(MODERATOR_TEST_FUNC)
@title_check
def rename(request, title):
''' POST-only view to rename article '''