Commit d238020a by Bridger Maxwell

Merge branch 'master' of git://github.com/benjaoming/django-wiki

Conflicts:
	wiki/conf/settings.py
parents c10ee829 d1f50d11
...@@ -5,11 +5,11 @@ Not implemented - will be ASAP ...@@ -5,11 +5,11 @@ Not implemented - will be ASAP
* Notification system **Almost done** (email notifications) * Notification system **Almost done** (email notifications)
* Simple user account handling: login/register etc. **Done** * Simple user account handling: login/register etc. **Done**
* Implement notifications, revision log messages and user messages thoroughly * Implement notifications, revision log messages and user messages thoroughly
* Attachment plugin **Almost done** (needs to be able to add attachments from other articles.. a simple search function) * Attachment plugin **Done**
* Image plugin * Image plugin
* Example plugin * Example plugin
* Bot editing detection. Don't let anyone edit more than once every other minute. * Bot editing detection. Don't let anyone edit more than once every other minute.
* Article deletion * Article deletion **Done**
* Key-value meta data * Key-value meta data
* Index views for urlpaths * Index views for urlpaths
* Searching * Searching
...@@ -17,6 +17,7 @@ Not implemented - will be ASAP ...@@ -17,6 +17,7 @@ Not implemented - will be ASAP
* View source for read-only articles + locked status * View source for read-only articles + locked status
* Global moderator permission **Almost done** (need to add grant form for users with *grant* permissions) * Global moderator permission **Almost done** (need to add grant form for users with *grant* permissions)
* Are you sure you wanna leave this page? * Are you sure you wanna leave this page?
* Special view for deleted articles w/ restore button
Ideas Ideas
===== =====
......
...@@ -4,6 +4,8 @@ from django.utils.translation import ugettext as _ ...@@ -4,6 +4,8 @@ from django.utils.translation import ugettext as _
import models import models
_disable_notifications = False
def notify(message, key, target_object=None, url=None): def notify(message, key, target_object=None, url=None):
""" """
Notify subscribing users of a new event. Key can be any kind of string, Notify subscribing users of a new event. Key can be any kind of string,
...@@ -22,6 +24,9 @@ def notify(message, key, target_object=None, url=None): ...@@ -22,6 +24,9 @@ def notify(message, key, target_object=None, url=None):
""" """
if _disable_notifications:
return 0
if target_object: if target_object:
if not isinstance(target_object, Model): if not isinstance(target_object, Model):
raise TypeError(_(u"You supplied a target_object that's not an instance of a django Model.")) raise TypeError(_(u"You supplied a target_object that's not an instance of a django Model."))
......
...@@ -3,6 +3,22 @@ from django.utils import simplejson as json ...@@ -3,6 +3,22 @@ from django.utils import simplejson as json
from django.http import HttpResponse from django.http import HttpResponse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
import django_notify
def disable_notify(func):
"""Disable notifications. Example:
@disable_notify
def your_function():
notify("no one will be notified", ...)
"""
def wrap(request, *args, **kwargs):
django_notify._disable_notifications = True
response = func(request, *args, **kwargs)
django_notify._disable_notifications = False
return response
return wrap
def login_required_ajax(func): def login_required_ajax(func):
"""Similar to login_required. But if the request is an ajax request, then """Similar to login_required. But if the request is an ajax request, then
it returns an error in json with a 403 status code.""" it returns an error in json with a 403 status code."""
...@@ -25,3 +41,4 @@ def json_view(func): ...@@ -25,3 +41,4 @@ def json_view(func):
response.write(data) response.write(data)
return response return response
return wrap return wrap
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'NotificationType'
db.create_table('notify_notificationtype', (
('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128, primary_key=True)),
('label', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'], null=True, blank=True)),
))
db.send_create_signal('django_notify', ['NotificationType'])
# Adding model 'Settings'
db.create_table('notify_settings', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('interval', self.gf('django.db.models.fields.SmallIntegerField')(default=0)),
))
db.send_create_signal('django_notify', ['Settings'])
# Adding model 'Subscription'
db.create_table('notify_subscription', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('settings', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_notify.Settings'])),
('notification_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_notify.NotificationType'])),
('object_id', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
('send_emails', self.gf('django.db.models.fields.BooleanField')(default=True)),
))
db.send_create_signal('django_notify', ['Subscription'])
# Adding model 'Notification'
db.create_table('notify_notification', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('subscription', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_notify.Subscription'], null=True, on_delete=models.SET_NULL, blank=True)),
('message', self.gf('django.db.models.fields.TextField')()),
('url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)),
('is_viewed', self.gf('django.db.models.fields.BooleanField')(default=False)),
('is_emailed', self.gf('django.db.models.fields.BooleanField')(default=False)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
))
db.send_create_signal('django_notify', ['Notification'])
def backwards(self, orm):
# Deleting model 'NotificationType'
db.delete_table('notify_notificationtype')
# Deleting model 'Settings'
db.delete_table('notify_settings')
# Deleting model 'Subscription'
db.delete_table('notify_subscription')
# Deleting model 'Notification'
db.delete_table('notify_notification')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'django_notify.notification': {
'Meta': {'object_name': 'Notification', 'db_table': "'notify_notification'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_emailed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'message': ('django.db.models.fields.TextField', [], {}),
'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Subscription']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
},
'django_notify.notificationtype': {
'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
},
'django_notify.settings': {
'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'django_notify.subscription': {
'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}),
'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"})
}
}
complete_apps = ['django_notify']
\ No newline at end of file
...@@ -22,7 +22,7 @@ class NotificationType(models.Model): ...@@ -22,7 +22,7 @@ class NotificationType(models.Model):
return self.key return self.key
class Meta: class Meta:
db_tablespace = settings.DB_TABLESPACE db_table = settings.DB_TABLE_PREFIX + '_notificationtype'
verbose_name = _(u'type') verbose_name = _(u'type')
verbose_name_plural = _(u'types') verbose_name_plural = _(u'types')
...@@ -36,7 +36,7 @@ class Settings(models.Model): ...@@ -36,7 +36,7 @@ class Settings(models.Model):
return _(u"Settings for %s") % self.user.username return _(u"Settings for %s") % self.user.username
class Meta: class Meta:
db_tablespace = settings.DB_TABLESPACE db_table = settings.DB_TABLE_PREFIX + '_settings'
verbose_name = _(u'settings') verbose_name = _(u'settings')
verbose_name_plural = _(u'settings') verbose_name_plural = _(u'settings')
...@@ -52,13 +52,13 @@ class Subscription(models.Model): ...@@ -52,13 +52,13 @@ class Subscription(models.Model):
return _("Subscription for: %s") % str(self.settings.user.username) return _("Subscription for: %s") % str(self.settings.user.username)
class Meta: class Meta:
db_tablespace = settings.DB_TABLESPACE db_table = settings.DB_TABLE_PREFIX + '_subscription'
verbose_name = _(u'subscription') verbose_name = _(u'subscription')
verbose_name_plural = _(u'subscriptions') verbose_name_plural = _(u'subscriptions')
class Notification(models.Model): class Notification(models.Model):
subscription = models.ForeignKey(Subscription) subscription = models.ForeignKey(Subscription, null=True, blank=True, on_delete=models.SET_NULL)
message = models.TextField() message = models.TextField()
url = models.URLField(blank=True, null=True, verbose_name=_(u'link for notification')) url = models.URLField(blank=True, null=True, verbose_name=_(u'link for notification'))
is_viewed = models.BooleanField(default=False) is_viewed = models.BooleanField(default=False)
...@@ -97,6 +97,6 @@ class Notification(models.Model): ...@@ -97,6 +97,6 @@ class Notification(models.Model):
return "%s: %s" % (str(self.subscription.settings.user), self.message) return "%s: %s" % (str(self.subscription.settings.user), self.message)
class Meta: class Meta:
db_tablespace = settings.DB_TABLESPACE db_table = settings.DB_TABLE_PREFIX + '_notification'
verbose_name = _(u'notification') verbose_name = _(u'notification')
verbose_name_plural = _(u'notifications') verbose_name_plural = _(u'notifications')
...@@ -2,8 +2,7 @@ from django.conf import settings as django_settings ...@@ -2,8 +2,7 @@ from django.conf import settings as django_settings
_ = lambda x: x _ = lambda x: x
# don't change this :) DB_TABLE_PREFIX = 'notify'
DB_TABLESPACE = 'notify'
# You need to switch this setting on, otherwise nothing will happen :) # You need to switch this setting on, otherwise nothing will happen :)
ENABLED = getattr(django_settings, "NOTIFY_ENABLED", True) ENABLED = getattr(django_settings, "NOTIFY_ENABLED", True)
......
...@@ -21,4 +21,7 @@ if settings.DEBUG: ...@@ -21,4 +21,7 @@ if settings.DEBUG:
from wiki.urls import get_pattern as get_wiki_pattern from wiki.urls import get_pattern as get_wiki_pattern
from django_notify.urls import get_pattern as get_notify_pattern from django_notify.urls import get_pattern as get_notify_pattern
urlpatterns += patterns('', (r'^notify/', get_notify_pattern()), (r'', get_wiki_pattern())) urlpatterns += patterns('',
\ No newline at end of file (r'^notify/', get_notify_pattern()),
(r'', get_wiki_pattern())
)
\ No newline at end of file
...@@ -26,6 +26,9 @@ if ACCOUNT_HANDLING: ...@@ -26,6 +26,9 @@ if ACCOUNT_HANDLING:
else: else:
LOGIN_URL = getattr(django_settings, "LOGIN_URL", "/") LOGIN_URL = getattr(django_settings, "LOGIN_URL", "/")
# Maximum amount of children to display in a menu before going "+more"
SHOW_MAX_CHILDREN = getattr(django_settings, 'WIKI_SHOW_MAX_CHILDREN', 20)
#################### ####################
# PLANNED SETTINGS # # PLANNED SETTINGS #
#################### ####################
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.importlib import import_module from django.utils.importlib import import_module
_cache = {}
_cache = {}
_settings_forms = [] _settings_forms = []
_markdown_extensions = [] _markdown_extensions = []
_article_tabs = []
class BasePlugin(object):
#settings_form = YourForm
pass
def register(PluginClass): def register(PluginClass):
""" """
...@@ -17,7 +13,8 @@ def register(PluginClass): ...@@ -17,7 +13,8 @@ def register(PluginClass):
""" """
if PluginClass in _cache.keys(): if PluginClass in _cache.keys():
raise Exception("Plugin class already registered") raise Exception("Plugin class already registered")
_cache[PluginClass] = PluginClass() plugin = PluginClass()
_cache[PluginClass] = plugin
settings_form = getattr(PluginClass, 'settings_form', None) settings_form = getattr(PluginClass, 'settings_form', None)
if settings_form: if settings_form:
...@@ -28,10 +25,16 @@ def register(PluginClass): ...@@ -28,10 +25,16 @@ def register(PluginClass):
settings_form = getattr(form_module, klassname) settings_form = getattr(form_module, klassname)
_settings_forms.append(settings_form) _settings_forms.append(settings_form)
if PluginClass.article_tab:
_article_tabs.append(plugin)
_markdown_extensions.extend(getattr(PluginClass, 'markdown_extensions', [])) _markdown_extensions.extend(getattr(PluginClass, 'markdown_extensions', []))
def get_plugins(): def get_plugins():
return _cache return _cache
def get_markdown_extensions(): def get_markdown_extensions():
return _markdown_extensions return _markdown_extensions
\ No newline at end of file
def get_article_tabs():
return _article_tabs
...@@ -16,11 +16,27 @@ def json_view(func): ...@@ -16,11 +16,27 @@ def json_view(func):
return response return response
return wrap return wrap
def get_article(func=None, can_read=True, can_write=False): def get_article(func=None, can_read=True, can_write=False, deleted_contents=False):
"""Intercepts the keyword args path or article_id and looks up an article, """View decorator for processing standard url keyword args: Intercepts the
calling the decorated func with this ID.""" keyword args path or article_id and looks up an article, calling the decorated
func with this ID.
def the_func(request, *args, **kwargs): Will accept a func(request, article, *args, **kwargs)
NB! This function will redirect if an article does not exist, permissions
are missing or the article is deleted.
Arguments:
can_read=True and/or can_write=True: Check that the current request.user
has correct permissions.
deleted_contents=True: Do not redirect if the article has been deleted.
Also see: wiki.views.mixins.ArticleMixin
"""
def wrapper(request, *args, **kwargs):
import models import models
path = kwargs.pop('path', None) path = kwargs.pop('path', None)
...@@ -38,7 +54,7 @@ def get_article(func=None, can_read=True, can_write=False): ...@@ -38,7 +54,7 @@ def get_article(func=None, can_read=True, can_write=False):
if article_id: if article_id:
article = get_object_or_404(articles, id=article_id) article = get_object_or_404(articles, id=article_id)
try: try:
urlpath = models.URLPath.objects.get(articles=article) urlpath = models.URLPath.objects.get(articles__article=article)
except models.URLPath.DoesNotExist, models.URLPath.MultipleObjectsReturned: except models.URLPath.DoesNotExist, models.URLPath.MultipleObjectsReturned:
urlpath = None urlpath = None
else: else:
...@@ -51,19 +67,33 @@ def get_article(func=None, can_read=True, can_write=False): ...@@ -51,19 +67,33 @@ def get_article(func=None, can_read=True, can_write=False):
pathlist = filter(lambda x: x!="", path.split("/"),) pathlist = filter(lambda x: x!="", path.split("/"),)
path = "/".join(pathlist[:-1]) path = "/".join(pathlist[:-1])
parent = models.URLPath.get_by_path(path) parent = models.URLPath.get_by_path(path)
return redirect(reverse("wiki:create_url", args=(parent.path,)) + "?slug=%s" % pathlist[-1]) return redirect(reverse("wiki:create", kwargs={'path': parent.path,}) + "?slug=%s" % pathlist[-1])
except models.URLPath.DoesNotExist: except models.URLPath.DoesNotExist:
# TODO: Make a nice page # TODO: Make a nice page
return HttpResponseNotFound("This article was not found, and neither was the parent. This page should look nicer.") return HttpResponseNotFound("This article was not found, and neither was the parent. This page should look nicer.")
# TODO: If the article is not found but it exists, there is a permission error! # TODO: If the article is not found but it exists, there is a permission error!
article = get_object_or_404(articles, id=urlpath.article.id) if urlpath.article:
article = get_object_or_404(articles, id=urlpath.article.id)
else:
# Somehow article is gone
return_url = reverse('wiki:get', kwargs={'path': urlpath.parent.path})
urlpath.delete()
return redirect(return_url)
# 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:
return redirect('wiki:deleted', path=urlpath.path)
else:
return redirect('wiki:deleted', article_id=article.id)
kwargs['urlpath'] = urlpath kwargs['urlpath'] = urlpath
return func(request, article, *args, **kwargs) return func(request, article, *args, **kwargs)
if func: if func:
return the_func return wrapper
else: else:
return lambda func: get_article(func, can_read=can_read, can_write=can_write) return lambda func: get_article(func, can_read=can_read, can_write=can_write,
deleted_contents=deleted_contents)
...@@ -12,8 +12,9 @@ from wiki import models ...@@ -12,8 +12,9 @@ from wiki import models
from wiki.editors import editor from wiki.editors import editor
from wiki.core.diff import simple_merge from wiki.core.diff import simple_merge
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from wiki.plugins import PluginSettingsFormMixin
class CreateRoot(forms.Form): class CreateRootForm(forms.Form):
title = forms.CharField(label=_(u'Title'), help_text=_(u'Initial title of the article. May be overridden with revision titles.')) title = forms.CharField(label=_(u'Title'), help_text=_(u'Initial title of the article. May be overridden with revision titles.'))
content = forms.CharField(label=_(u'Type in some contents'), content = forms.CharField(label=_(u'Type in some contents'),
...@@ -175,7 +176,7 @@ class CreateForm(forms.Form): ...@@ -175,7 +176,7 @@ class CreateForm(forms.Form):
self.urlpath_parent = urlpath_parent self.urlpath_parent = urlpath_parent
title = forms.CharField(label=_(u'Title'),) title = forms.CharField(label=_(u'Title'),)
slug = forms.SlugField(label=_(u'Slug'), help_text=_(u"This will be the address where your article can be found. Use only alphanumeric characters and '-' or '_'."),) slug = forms.SlugField(label=_(u'Slug'), help_text=_(u"This will be the address where your article can be found. Use only alphanumeric characters and - or _. Note that you cannot change the slug after creating the article."),)
content = forms.CharField(label=_(u'Contents'), content = forms.CharField(label=_(u'Contents'),
required=False, widget=editor.get_widget()) #@UndefinedVariable required=False, widget=editor.get_widget()) #@UndefinedVariable
...@@ -186,13 +187,43 @@ class CreateForm(forms.Form): ...@@ -186,13 +187,43 @@ class CreateForm(forms.Form):
slug = self.cleaned_data['slug'] slug = self.cleaned_data['slug']
if slug[0] == "_": if slug[0] == "_":
raise forms.ValidationError(_(u'A slug may not begin with an underscore.')) raise forms.ValidationError(_(u'A slug may not begin with an underscore.'))
if models.URLPath.objects.filter(slug=slug, parent=self.urlpath_parent): already_existing_slug = models.URLPath.objects.filter(slug=slug, parent=self.urlpath_parent)
raise forms.ValidationError(_(u'A slug named "%s" already exists.') % slug) if already_existing_slug:
slug = already_existing_slug[0]
if slug.article and slug.article.deleted:
raise forms.ValidationError(_(u'A deleted article with slug "%s" already exists.') % slug)
else:
raise forms.ValidationError(_(u'A slug named "%s" already exists.') % slug)
return slug return slug
class PermissionsForm(forms.ModelForm):
class DeleteForm(forms.Form):
def __init__(self, *args, **kwargs):
self.article = kwargs.pop('article')
self.has_children = kwargs.pop('has_children')
super(DeleteForm, self).__init__(*args, **kwargs)
confirm = forms.BooleanField(required=False,
label=_(u'Yes, I am sure'))
purge = forms.BooleanField(widget=HiddenInput(), required=False,
label=_(u'Purge'),
help_text=_(u'Purge the article: Completely remove it (and all its contents) with no undo. Purging is a good idea if you want to free the slug such that users can create new articles in its place.'))
revision = forms.ModelChoiceField(models.ArticleRevision.objects.all(),
widget=HiddenInput(), required=False)
def clean(self):
cd = self.cleaned_data
if not cd['confirm']:
raise forms.ValidationError(_(u'You are not sure enough!'))
if cd['revision'] != self.article.current_revision:
raise forms.ValidationError(_(u'While you tried to delete this article, it was modified. TAKE CARE!'))
return cd
class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm):
settings_form_id = "perms"
settings_form_headline = _(u'Permissions') settings_form_headline = _(u'Permissions')
settings_order = 5 settings_order = 5
settings_write_access = False settings_write_access = False
...@@ -221,13 +252,3 @@ class PermissionsForm(forms.ModelForm): ...@@ -221,13 +252,3 @@ class PermissionsForm(forms.ModelForm):
model = models.Article model = models.Article
fields = ('group', 'group_read', 'group_write', 'other_read', 'other_write') fields = ('group', 'group_read', 'group_write', 'other_read', 'other_write')
class DeleteForm(forms.Form):
confirm = forms.BooleanField(required=False)
purge = forms.BooleanField(widget=HiddenInput(), required=False)
def clean(self):
cd = self.cleaned_data
if not cd['confirm']:
raise forms.ValidationError(_(u'You are not sure enough!'))
...@@ -72,13 +72,13 @@ class Article(models.Model): ...@@ -72,13 +72,13 @@ class Article(models.Model):
for decendant in obj.content_object.get_decendants(): for decendant in obj.content_object.get_decendants():
yield decendant yield decendant
def get_children(self, max_num=None): def get_children(self, max_num=None, **kwargs):
"""NB! This generator is expensive, so use it with care!!""" """NB! This generator is expensive, so use it with care!!"""
cnt = 0 cnt = 0
for obj in self.articleforobject_set.filter(is_mptt=True): for obj in self.articleforobject_set.filter(is_mptt=True):
for child in obj.content_object.get_children(): for child in obj.content_object.get_children().filter(**kwargs):
cnt += 1 cnt += 1
if cnt > max_num: return if max_num and cnt > max_num: return
yield child yield child
# All recursive permission methods will use decendant_objects to access # All recursive permission methods will use decendant_objects to access
......
...@@ -110,7 +110,7 @@ class URLPath(MPTTModel): ...@@ -110,7 +110,7 @@ class URLPath(MPTTModel):
if not root_nodes: if not root_nodes:
# (get_or_create does not work for MPTT models??) # (get_or_create does not work for MPTT models??)
root = cls.objects.create(site=site) root = cls.objects.create(site=site)
article = Article(title=title) article = Article()
article.add_revision(ArticleRevision(title=title, **kwargs), article.add_revision(ArticleRevision(title=title, **kwargs),
save=True) save=True)
article.add_object_relation(root) article.add_object_relation(root)
...@@ -119,10 +119,12 @@ class URLPath(MPTTModel): ...@@ -119,10 +119,12 @@ class URLPath(MPTTModel):
return root return root
@classmethod @classmethod
def create_article(cls, parent, slug, site=None, title="Root", **kwargs): def create_article(cls, parent, slug, site=None, title="Root", article_kwargs={}, **kwargs):
"""Utility function:
Create a new urlpath with an article and a new revision for the article"""
if not site: site = Site.objects.get_current() if not site: site = Site.objects.get_current()
newpath = cls.objects.create(site=site, parent=parent, slug=slug) newpath = cls.objects.create(site=site, parent=parent, slug=slug)
article = Article(title=title) article = Article(**article_kwargs)
article.add_revision(ArticleRevision(title=title, **kwargs), article.add_revision(ArticleRevision(title=title, **kwargs),
save=True) save=True)
article.add_object_relation(newpath) article.add_object_relation(newpath)
...@@ -145,6 +147,7 @@ def on_article_delete(instance, *args, **kwargs): ...@@ -145,6 +147,7 @@ def on_article_delete(instance, *args, **kwargs):
# But move all descendants to a lost-and-found node. # But move all descendants to a lost-and-found node.
site = Site.objects.get_current() site = Site.objects.get_current()
# Get the Lost-and-found path or create a new one
try: try:
lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG, lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(), parent=URLPath.root(),
...@@ -153,17 +156,20 @@ def on_article_delete(instance, *args, **kwargs): ...@@ -153,17 +156,20 @@ def on_article_delete(instance, *args, **kwargs):
lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG, lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(), parent=URLPath.root(),
site=site,) site=site,)
article = Article(title=_(u"Lost and found"), article = Article(group_read = True,
group_read = True,
group_write = False, group_write = False,
other_read = False, other_read = False,
other_write = False) other_write = False)
article.add_revision(ArticleRevision( article.add_revision(ArticleRevision(
content=_(u'Articles who lost their parents' content=_(u'Articles who lost their parents'
'==============================='))) '==============================='),
title=_(u"Lost and found")))
for urlpath in URLPath.objects.filter(articles__article=article, site=site): for urlpath in URLPath.objects.filter(articles__article=instance, site=site):
# Delete the children
for child in urlpath.get_children(): for child in urlpath.get_children():
child.move_to(lost_and_found) child.move_to(lost_and_found)
# ...and finally delete the path itself
urlpath.delete()
pre_delete.connect(on_article_delete, Article) pre_delete.connect(on_article_delete, Article)
class Registry(): from django.utils.translation import ugettext as _
class BasePlugin(object):
# Must fill in!
slug = None
# Optional
settings_form = None# A form class to add to the settings tab
urlpatterns = []
article_tab = None #(_(u'Attachments'), "icon-file")
article_view = None # A view for article_id/plugin/slug/
notifications = [] # A list of notification handlers to be subscribed if the notification system is active
# Example
# [{'model': models.AttachmentRevision,
# 'message': lambda obj: _(u"A file was changed: %s") % obj.get_filename(),
# 'key': ARTICLE_EDIT,
# 'created': True,
# 'get_article': lambda obj: obj.attachment.article}
# ]
markdown_extensions = []
def __init__(self): pass
self._registry = []
class PluginSidebarFormMixin(object):
def __init__(self, plugin_instance, *args, **kwargs):
kwargs['prefix'] = plugin_instance.slug
class PluginSettingsFormMixin(object):
settings_form_headline = _(u'Notifications')
settings_order = 1
settings_write_access = False
def register(self, plugin_instance): def get_usermessage(self):
if not isinstance(plugin_instance, WikiPlugin): pass
raise TypeError("That's not a WikiPlugin")
self._registry.append(plugin_instance)
class WikiPlugin():
pass
\ No newline at end of file
...@@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _
import settings import settings
from wiki import managers from wiki import managers
from wiki.conf import settings as wiki_settings
from wiki.models.pluginbase import ReusablePlugin from wiki.models.pluginbase import ReusablePlugin
from wiki.models.article import BaseRevisionMixin from wiki.models.article import BaseRevisionMixin
...@@ -27,7 +26,6 @@ class Attachment(ReusablePlugin): ...@@ -27,7 +26,6 @@ class Attachment(ReusablePlugin):
class Meta: class Meta:
verbose_name = _(u'attachment') verbose_name = _(u'attachment')
verbose_name_plural = _(u'attachments') verbose_name_plural = _(u'attachments')
app_label = wiki_settings.APP_LABEL
def __unicode__(self): def __unicode__(self):
return "%s: %s" % (self.article.current_revision.title, self.original_filename) return "%s: %s" % (self.article.current_revision.title, self.original_filename)
...@@ -77,7 +75,6 @@ class AttachmentRevision(BaseRevisionMixin, models.Model): ...@@ -77,7 +75,6 @@ class AttachmentRevision(BaseRevisionMixin, models.Model):
verbose_name_plural = _(u'attachment revisions') verbose_name_plural = _(u'attachment revisions')
ordering = ('created',) ordering = ('created',)
get_latest_by = ('revision_number',) get_latest_by = ('revision_number',)
app_label = wiki_settings.APP_LABEL
def get_filename(self): def get_filename(self):
"""Used to retrieve the filename of a revision. """Used to retrieve the filename of a revision.
......
...@@ -3,13 +3,14 @@ from django.conf.urls.defaults import patterns, url ...@@ -3,13 +3,14 @@ from django.conf.urls.defaults import patterns, url
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from wiki.core import plugins_registry from wiki.core import plugins_registry
from wiki import plugins
from wiki.plugins.attachments import views from wiki.plugins.attachments import views
from wiki.plugins.attachments import models from wiki.plugins.attachments import models
from wiki.plugins.attachments import settings from wiki.plugins.attachments import settings
from wiki.plugins.attachments.markdown_extensions import AttachmentExtension from wiki.plugins.attachments.markdown_extensions import AttachmentExtension
from wiki.plugins.notifications import ARTICLE_EDIT from wiki.plugins.notifications import ARTICLE_EDIT
class AttachmentPlugin(plugins_registry.BasePlugin): class AttachmentPlugin(plugins.BasePlugin):
#settings_form = 'wiki.plugins.notifications.forms.SubscriptionForm' #settings_form = 'wiki.plugins.notifications.forms.SubscriptionForm'
...@@ -27,7 +28,6 @@ class AttachmentPlugin(plugins_registry.BasePlugin): ...@@ -27,7 +28,6 @@ class AttachmentPlugin(plugins_registry.BasePlugin):
) )
article_tab = (_(u'Attachments'), "icon-file") article_tab = (_(u'Attachments'), "icon-file")
article_view = views.AttachmentView().dispatch article_view = views.AttachmentView().dispatch
article_template_append = 'wiki/plugins/attachments/append.html'
# List of notifications to construct signal handlers for. This # List of notifications to construct signal handlers for. This
# is handled inside the notifications plugin. # is handled inside the notifications plugin.
......
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Image'
db.create_table('images_image', (
('revisionplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.RevisionPlugin'], unique=True, primary_key=True)),
('image', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
('caption', self.gf('django.db.models.fields.CharField')(max_length=2056, null=True, blank=True)),
))
db.send_create_signal('images', ['Image'])
def backwards(self, orm):
# Deleting model 'Image'
db.delete_table('images_image')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'images.image': {
'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']},
'caption': ('django.db.models.fields.CharField', [], {'max_length': '2056', 'null': 'True', 'blank': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'})
},
'wiki.article': {
'Meta': {'object_name': 'Article'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
'wiki.articleplugin': {
'Meta': {'object_name': 'ArticlePlugin'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'wiki.articlerevision': {
'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}),
'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}),
'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}),
'revision_number': ('django.db.models.fields.IntegerField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'wiki.revisionplugin': {
'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']},
'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}),
'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"})
}
}
complete_apps = ['images']
\ No newline at end of file
...@@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _
import settings import settings
from wiki.conf import settings as wiki_settings
from wiki.models.pluginbase import RevisionPlugin from wiki.models.pluginbase import RevisionPlugin
class Image(RevisionPlugin): class Image(RevisionPlugin):
...@@ -20,4 +19,3 @@ class Image(RevisionPlugin): ...@@ -20,4 +19,3 @@ class Image(RevisionPlugin):
class Meta: class Meta:
verbose_name = _(u'image') verbose_name = _(u'image')
verbose_name_plural = _(u'images') verbose_name_plural = _(u'images')
app_label = wiki_settings.APP_LABEL
# Key for django_notify
ARTICLE_EDIT = "article_edit" ARTICLE_EDIT = "article_edit"
ARTICLE_CREATE = "article_create"
...@@ -6,10 +6,10 @@ from django.contrib.contenttypes.models import ContentType ...@@ -6,10 +6,10 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from wiki.plugins.notifications import ARTICLE_EDIT from wiki.plugins.notifications import ARTICLE_EDIT
from wiki.plugins import PluginSettingsFormMixin
class SubscriptionForm(forms.Form): class SubscriptionForm(PluginSettingsFormMixin, forms.Form):
settings_form_id = "notifications"
settings_form_headline = _(u'Notifications') settings_form_headline = _(u'Notifications')
settings_order = 1 settings_order = 1
settings_write_access = False settings_write_access = False
......
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'ArticleSubscription'
db.create_table('notifications_articlesubscription', (
('subscription_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['django_notify.Subscription'], unique=True)),
('articleplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ArticlePlugin'], unique=True, primary_key=True)),
))
db.send_create_signal('notifications', ['ArticleSubscription'])
def backwards(self, orm):
# Deleting model 'ArticleSubscription'
db.delete_table('notifications_articlesubscription')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'django_notify.notificationtype': {
'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
},
'django_notify.settings': {
'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'django_notify.subscription': {
'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}),
'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"})
},
'notifications.articlesubscription': {
'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']},
'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}),
'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'})
},
'wiki.article': {
'Meta': {'object_name': 'Article'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
'wiki.articleplugin': {
'Meta': {'object_name': 'ArticlePlugin'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'wiki.articlerevision': {
'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}),
'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}),
'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}),
'revision_number': ('django.db.models.fields.IntegerField', [], {}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'})
}
}
complete_apps = ['notifications']
\ No newline at end of file
...@@ -6,7 +6,7 @@ from django.db.models import signals ...@@ -6,7 +6,7 @@ from django.db.models import signals
from django_notify import notify from django_notify import notify
from django_notify.models import Subscription from django_notify.models import Subscription
from wiki.plugins.notifications import ARTICLE_CREATE, ARTICLE_EDIT from wiki.plugins.notifications import ARTICLE_EDIT
from wiki import models as wiki_models from wiki import models as wiki_models
from wiki.core import plugins_registry from wiki.core import plugins_registry
...@@ -28,23 +28,19 @@ def default_url(article, urlpath=None): ...@@ -28,23 +28,19 @@ def default_url(article, urlpath=None):
url = reverse('wiki:get', kwargs={'article_id': article.id}) url = reverse('wiki:get', kwargs={'article_id': article.id})
return url return url
def post_article_save(instance, **kwargs):
if kwargs.get('created', False):
url = default_url(instance)
notify(_(u'New article created: %s') % instance.title, ARTICLE_CREATE,
target_object=instance, url=url)
def post_article_revision_save(instance, **kwargs): def post_article_revision_save(instance, **kwargs):
if kwargs.get('created', False): if kwargs.get('created', False):
url = default_url(instance.article) url = default_url(instance.article)
notify(_(u'Article modified: %s') % instance.title, ARTICLE_EDIT, if instance.deleted:
target_object=instance.article, url=url) notify(_(u'Article deleted: %s') % instance.title, ARTICLE_EDIT,
target_object=instance.article, url=url)
# Create notifications when new articles are saved. We do NOT care elif instance.previous_revision:
# about Article objects that are just modified, because many properties notify(_(u'Article modified: %s') % instance.title, ARTICLE_EDIT,
# are modified without any notifications necessary! target_object=instance.article, url=url)
signals.post_save.connect(post_article_save, sender=wiki_models.Article,) else:
notify(_(u'New article created: %s') % instance.title, ARTICLE_EDIT,
target_object=instance, url=url)
# Whenever a new revision is created, we notifý users that an article # Whenever a new revision is created, we notifý users that an article
# was edited # was edited
signals.post_save.connect(post_article_revision_save, sender=wiki_models.ArticleRevision,) signals.post_save.connect(post_article_revision_save, sender=wiki_models.ArticleRevision,)
......
from wiki.core import plugins_registry from wiki.core import plugins_registry
from wiki import plugins
class NotifyPlugin(plugins_registry.BasePlugin): class NotifyPlugin(plugins.BasePlugin):
settings_form = 'wiki.plugins.notifications.forms.SubscriptionForm' settings_form = 'wiki.plugins.notifications.forms.SubscriptionForm'
......
...@@ -5,6 +5,25 @@ ...@@ -5,6 +5,25 @@
{% block pagetitle %}{% trans "Add new article" %}{% endblock %} {% block pagetitle %}{% trans "Add new article" %}{% endblock %}
{% block wiki_contents %} {% block wiki_contents %}
{% addtoblock "js" %}
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/urlify.js "></script>
<script type="text/javascript">
//<![CDATA[
(function($) {
$(document).ready(function (){
$("#id_title").keyup(function () {
var e = $("#id_slug")[0];
if(!e._changed) {
e.value = URLify(this.value, 64);
}
});
});
})(jQuery);
//]]>
</script>
{% endaddtoblock %}
{% include "wiki/includes/editormedia.html" %} {% include "wiki/includes/editormedia.html" %}
<h1 class="page-header">{% trans "Add new article" %}</h1> <h1 class="page-header">{% trans "Add new article" %}</h1>
......
...@@ -5,22 +5,58 @@ ...@@ -5,22 +5,58 @@
{% block pagetitle %}{% trans "Delete article" %}{% endblock %} {% block pagetitle %}{% trans "Delete article" %}{% endblock %}
{% block wiki_contents %} {% block wiki_contents %}
{% include "wiki/includes/editormedia.html" %} <h1 class="page-header">{% trans "Delete" %} "{{ article.current_revision.title }}"</h1>
<h1 class="page-header">{% trans "Delete" %} "{{ article.current_revision.title }}"?</h1>
<form method="POST" class="form-horizontal"> {% if cannot_delete_root %}
{% wiki_form create_form %} <p class="lead">{% trans "You cannot delete a root article." %}</p>
<div class="form-actions"> <p><a href="{% url 'wiki:get' path=urlpath.path article_id=article.id %}">{% trans "Go back" %}</a></p>
<a href="{% url 'wiki:get' path=parent_urlpath.path %}" class="btn btn-large"> {% else %}
<span class="icon-circle-arrow-left"></span>
{% trans "Go back" %} {% if cannot_delete_children %}
</a>
<button type="submit" name="save_changes" class="btn btn-primary btn-large"> <p class="alert alert-error"><strong>{% trans "You cannot delete this article because you do not have permission to delete articles with children. Try to remove the children manually one-by-one." %}</strong></p>
<span class="icon-plus"></span>
{% trans "Create article" %} {% endif %}
</button>
</div> {% if delete_children %}
</form>
<p class="lead">{% trans "You are deleting an article. This means that its children will be deleted as well. If you choose to purge, children will also be purged!" %}</p>
<h2>{% trans "Articles that will be deleted" %}</h2>
<ul>
{% for child in delete_children %}
<li><a href="{% url 'wiki:get' article_id=child.article.id %}" target="_blank">{{ child.article }}</a></li>
{% if delete_children_more %}
<li><em>{% trans "...and more!" %}</em></li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if not cannot_delete_children %}
<p class="lead">{% trans "You are deleting an article. Please confirm." %}</p>
<form method="POST" class="form-horizontal">
{% wiki_form delete_form %}
<script type="text/javascript">
$('#id_revision').val('{{ article.current_revision.id }}');
</script>
<div class="form-actions">
<a href="{% url 'wiki:get' path=urlpath.path article_id=article.id %}" class="btn btn-large">
<span class="icon-circle-arrow-left"></span>
{% trans "Go back" %}
</a>
<button type="submit" name="save_changes" class="btn btn-danger btn-large">
<span class="icon-plus"></span>
{% trans "Delete article" %}
</button>
</div>
</form>
{% endif %}
{% endif %}
{% endblock %} {% endblock %}
{% extends "wiki/base.html" %}
{% load wiki_tags i18n sekizai_tags %}
{% load url from future %}
{% block pagetitle %}{% trans "Article deleted" %}{% endblock %}
{% block wiki_contents %}
<style type="text/css">
label[for=id_confirm] {
float: left;
margin-right: 10px;
}
</style>
<h1 class="page-header">{% trans "Article Deleted" %}</h1>
<p class="lead">
{% trans "The article you were looking for has been deleted." %}
</p>
<div class="row-fluid">
{% if not article.current_revision.locked or user|is_moderator %}
<div class="span6">
<div class="well">
<h2>{% trans "Restore" %}</h2>
<p>{% trans "You may restore this article and its children by clicking restore. Note that this restores ALL children." %}</p>
<p>
<a href="?restore=1" class="btn">
<span class="icon-repeat"></span>
{% trans "Restore" %}
</a>
</p>
</div>
</div>
{% endif %}
{% if user|is_moderator %}
<div class="span6">
<div class="well">
<h2>{% trans "Purge deletion" %}</h2>
<p>{% trans "You may remove this article and any children permanently and free their slugs by clicking the below button. This action cannot be undone." %}</p>
<form method="POST" class="form form-inline">
{% csrf_token %}
{% wiki_form purge_form %}
<button class="btn">
<span class="icon-remove"></span>
{% trans "Purge" %}
</button>
</form>
</div>
</div>
{% endif %}
</div>
{% endblock %}
...@@ -64,6 +64,12 @@ ...@@ -64,6 +64,12 @@
{% if revision == article.current_revision %} {% if revision == article.current_revision %}
<strong>*</strong> <strong>*</strong>
{% endif %} {% endif %}
{% if revision.deleted %}
<span class="badge badge-important">{% trans "deleted" %}</span>
{% endif %}
{% if revision.previous_revision.deleted and not revision.deleted %}
<span class="badge badge-success">{% trans "restored" %}</span>
{% endif %}
<div style="color: #CCC;"> <div style="color: #CCC;">
<small> <small>
{% if revision.user_message %} {% if revision.user_message %}
......
...@@ -2,16 +2,15 @@ ...@@ -2,16 +2,15 @@
{% with selected_tab as selected %} {% with selected_tab as selected %}
{% for plugin in plugins %} {% for plugin in article_tabs %}
{% if plugin.article_tab %} <li class="pull-right{% if selected == plugin.slug %} active{% endif %}">
<li class="pull-right{% if selected == plugin.slug %} active{% endif %}"> <a href="{% url 'wiki:plugin' slug=plugin.slug article_id=article.id path=urlpath.path %}">
<a href="{% url 'wiki:plugin' slug=plugin.slug article_id=article.id path=urlpath.path %}"> <span class="{{ plugin.article_tab.1 }}"></span>
<span class="{{ plugin.article_tab.1 }}"></span> {{ plugin.article_tab.0 }}
{{ plugin.article_tab.0 }} </a>
</a> </li>
</li>
{% endif %}
{% endfor %} {% endfor %}
<li class="pull-right{% if selected == "settings" %} active{% endif %}"> <li class="pull-right{% if selected == "settings" %} active{% endif %}">
{% if not user.is_anonymous %} {% if not user.is_anonymous %}
<a href="{% url 'wiki:settings' article_id=article.id path=urlpath.path %}"> <a href="{% url 'wiki:settings' article_id=article.id path=urlpath.path %}">
......
...@@ -16,15 +16,18 @@ ...@@ -16,15 +16,18 @@
<span class="caret"></span> <span class="caret"></span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for child in urlpath.get_children %} {% for child in children_slice %}
<li> <li>
<a href="{% url 'wiki:get' path=child.path %}"> <a href="{% url 'wiki:get' path=child.path %}">
{{ child.article.current_revision.title }} {{ child.article.current_revision.title }}
</a> </a>
</li> </li>
{% empty %} {% empty %}
<li><a href="#"><em>{% trans "No sub-articles" %}</em></a></li> <li><a href="#"><em>{% trans "No sub-articles" %}</em></a></li>
{% endfor %} {% endfor %}
{% if children_slice_more %}
<li><a href="#"><em>{% trans "...and more" %}</em></a></li>
{% endif %}
<li class="divider"></li> <li class="divider"></li>
<li> <li>
<a href="" onclick="alert('TODO')">{% trans "List sub-pages" %} &raquo;</a> <a href="" onclick="alert('TODO')">{% trans "List sub-pages" %} &raquo;</a>
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
{% block wiki_contents %} {% block wiki_contents %}
{% if not preview %} {% if not preview %}
{% cache 1 article article.current_revision.id %} {% if article.current_revision %}
{{ article.render }} {% cache 1 article article.current_revision.id %}
{% endcache %} {{ article.render }}
{% endcache %}
{% endif %}
{% else %} {% else %}
{{ content|default:"" }} {{ content|default:"" }}
{% endif %} {% endif %}
......
...@@ -27,6 +27,7 @@ urlpatterns += patterns('', ...@@ -27,6 +27,7 @@ urlpatterns += patterns('',
# Paths decided by article_ids # Paths decided by article_ids
url('^(?P<article_id>\d+)/$', article.ArticleView.as_view(), name='get'), url('^(?P<article_id>\d+)/$', article.ArticleView.as_view(), name='get'),
url('^(?P<article_id>\d+)/delete/$', article.Delete.as_view(), name='delete'), url('^(?P<article_id>\d+)/delete/$', article.Delete.as_view(), name='delete'),
url('^(?P<article_id>\d+)/deleted/$', article.Deleted.as_view(), name='deleted'),
url('^(?P<article_id>\d+)/edit/$', article.Edit.as_view(), name='edit'), url('^(?P<article_id>\d+)/edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<article_id>\d+)/preview/$', 'wiki.views.article.preview', name='preview'), url('^(?P<article_id>\d+)/preview/$', 'wiki.views.article.preview', name='preview'),
url('^(?P<article_id>\d+)/history/$', article.History.as_view(), name='history'), url('^(?P<article_id>\d+)/history/$', article.History.as_view(), name='history'),
...@@ -49,7 +50,8 @@ for plugin in plugins_registry._cache.values(): ...@@ -49,7 +50,8 @@ for plugin in plugins_registry._cache.values():
urlpatterns += patterns('', urlpatterns += patterns('',
# Paths decided by URLs # Paths decided by URLs
url('^(?P<path>.+/|)_create/$', article.Create.as_view(), name='create'), url('^(?P<path>.+/|)_create/$', article.Create.as_view(), name='create'),
url('^(?P<path>.+/|)_delete/$', article.Edit.as_view(), name='delete'), url('^(?P<path>.+/|)_delete/$', article.Delete.as_view(), name='delete'),
url('^(?P<path>.+/|)_deleted/$', article.Deleted.as_view(), name='deleted'),
url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit'), url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<path>.+/|)_preview/$', 'wiki.views.article.preview', name='preview'), url('^(?P<path>.+/|)_preview/$', 'wiki.views.article.preview', name='preview'),
url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'), url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'),
......
from django.views.generic.base import TemplateResponseMixin from django.views.generic.base import TemplateResponseMixin
from wiki.core import plugins_registry from wiki.core import plugins_registry
from wiki.conf import settings
class ArticleMixin(TemplateResponseMixin): class ArticleMixin(TemplateResponseMixin):
"""A mixin that receives an article object as a parameter (usually from a wiki """A mixin that receives an article object as a parameter (usually from a wiki
...@@ -10,10 +11,17 @@ class ArticleMixin(TemplateResponseMixin): ...@@ -10,10 +11,17 @@ class ArticleMixin(TemplateResponseMixin):
def dispatch(self, request, article, *args, **kwargs): def dispatch(self, request, article, *args, **kwargs):
self.urlpath = kwargs.pop('urlpath', None) self.urlpath = kwargs.pop('urlpath', None)
self.article = article self.article = article
self.children_slice = []
for child in self.article.get_children(max_num=settings.SHOW_MAX_CHILDREN+1,
articles__article__current_revision__deleted=False):
self.children_slice.append(child)
return super(ArticleMixin, self).dispatch(request, *args, **kwargs) return super(ArticleMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['urlpath'] = self.urlpath kwargs['urlpath'] = self.urlpath
kwargs['article'] = self.article kwargs['article'] = self.article
kwargs['plugins'] = plugins_registry._cache.values() kwargs['article_tabs'] = plugins_registry.get_article_tabs()
kwargs['children_slice'] = self.children_slice[:20]
kwargs['children_slice_more'] = len(self.children_slice) > 20
return kwargs return kwargs
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment