Compare commits

..

No commits in common. "master" and "0.3.0" have entirely different histories.

15 changed files with 34 additions and 145 deletions

View File

@ -1,18 +0,0 @@
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,12 +1,7 @@
0.3.0 0.3.0
===== =====
- lots of tests - lots of tests
- new escape_html option -
- csrf token
- autolockarticles management command
- null editors
- fix revert
0.2.0 0.2.0
=================== ===================

View File

@ -13,7 +13,7 @@ might not be for you.
Requirements Requirements
============ ============
django-markupwiki depends on django >= 1.2, django-markupfield >= 1.0.0b and django-markupwiki depends on django >= 1.2a, django-markupfield >= 1.0.0a2 and
libraries for whichever markup options you wish to include. libraries for whichever markup options you wish to include.
@ -84,9 +84,7 @@ to customize the behavior.
``MARKUPWIKI_MARKUP_TYPE_EDITABLE`` ``MARKUPWIKI_MARKUP_TYPE_EDITABLE``
if False user won't have option to change markup type (default: True) if False user won't have option to change markup type (default: True)
``MARKUPWIKI_MARKUP_TYPES`` ``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:: Example::

BIN
example/example Normal file

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,32 +12,6 @@
{% endblock %} {% endblock %}
{% block article_body %} {% 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> <table>
<thead> <tr> <thead> <tr>
<th>Version</th> <th>Version</th>
@ -72,5 +46,4 @@
<span>Compare Selected Versions</span> <span>Compare Selected Versions</span>
</button> </button>
</form> </form>
{% endblock %} {% endblock %}

View File

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

View File

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