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
* Notification system **Almost done** (email notifications)
* Simple user account handling: login/register etc. **Done**
* 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
* Example plugin
* Bot editing detection. Don't let anyone edit more than once every other minute.
* Article deletion
* Article deletion **Done**
* Key-value meta data
* Index views for urlpaths
* Searching
......@@ -17,6 +17,7 @@ Not implemented - will be ASAP
* View source for read-only articles + locked status
* Global moderator permission **Almost done** (need to add grant form for users with *grant* permissions)
* Are you sure you wanna leave this page?
* Special view for deleted articles w/ restore button
Ideas
=====
......
......@@ -4,6 +4,8 @@ from django.utils.translation import ugettext as _
import models
_disable_notifications = False
def notify(message, key, target_object=None, url=None):
"""
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):
"""
if _disable_notifications:
return 0
if target_object:
if not isinstance(target_object, 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
from django.http import HttpResponse
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):
"""Similar to login_required. But if the request is an ajax request, then
it returns an error in json with a 403 status code."""
......@@ -25,3 +41,4 @@ def json_view(func):
response.write(data)
return response
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):
return self.key
class Meta:
db_tablespace = settings.DB_TABLESPACE
db_table = settings.DB_TABLE_PREFIX + '_notificationtype'
verbose_name = _(u'type')
verbose_name_plural = _(u'types')
......@@ -36,7 +36,7 @@ class Settings(models.Model):
return _(u"Settings for %s") % self.user.username
class Meta:
db_tablespace = settings.DB_TABLESPACE
db_table = settings.DB_TABLE_PREFIX + '_settings'
verbose_name = _(u'settings')
verbose_name_plural = _(u'settings')
......@@ -52,13 +52,13 @@ class Subscription(models.Model):
return _("Subscription for: %s") % str(self.settings.user.username)
class Meta:
db_tablespace = settings.DB_TABLESPACE
db_table = settings.DB_TABLE_PREFIX + '_subscription'
verbose_name = _(u'subscription')
verbose_name_plural = _(u'subscriptions')
class Notification(models.Model):
subscription = models.ForeignKey(Subscription)
subscription = models.ForeignKey(Subscription, null=True, blank=True, on_delete=models.SET_NULL)
message = models.TextField()
url = models.URLField(blank=True, null=True, verbose_name=_(u'link for notification'))
is_viewed = models.BooleanField(default=False)
......@@ -97,6 +97,6 @@ class Notification(models.Model):
return "%s: %s" % (str(self.subscription.settings.user), self.message)
class Meta:
db_tablespace = settings.DB_TABLESPACE
db_table = settings.DB_TABLE_PREFIX + '_notification'
verbose_name = _(u'notification')
verbose_name_plural = _(u'notifications')
......@@ -2,8 +2,7 @@ from django.conf import settings as django_settings
_ = lambda x: x
# don't change this :)
DB_TABLESPACE = 'notify'
DB_TABLE_PREFIX = 'notify'
# You need to switch this setting on, otherwise nothing will happen :)
ENABLED = getattr(django_settings, "NOTIFY_ENABLED", True)
......
......@@ -21,4 +21,7 @@ if settings.DEBUG:
from wiki.urls import get_pattern as get_wiki_pattern
from django_notify.urls import get_pattern as get_notify_pattern
urlpatterns += patterns('', (r'^notify/', get_notify_pattern()), (r'', get_wiki_pattern()))
\ No newline at end of file
urlpatterns += patterns('',
(r'^notify/', get_notify_pattern()),
(r'', get_wiki_pattern())
)
\ No newline at end of file
......@@ -26,6 +26,9 @@ if ACCOUNT_HANDLING:
else:
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 #
####################
......
# -*- coding: utf-8 -*-
from django.utils.importlib import import_module
_cache = {}
_cache = {}
_settings_forms = []
_markdown_extensions = []
class BasePlugin(object):
#settings_form = YourForm
pass
_article_tabs = []
def register(PluginClass):
"""
......@@ -17,7 +13,8 @@ def register(PluginClass):
"""
if PluginClass in _cache.keys():
raise Exception("Plugin class already registered")
_cache[PluginClass] = PluginClass()
plugin = PluginClass()
_cache[PluginClass] = plugin
settings_form = getattr(PluginClass, 'settings_form', None)
if settings_form:
......@@ -28,10 +25,16 @@ def register(PluginClass):
settings_form = getattr(form_module, klassname)
_settings_forms.append(settings_form)
if PluginClass.article_tab:
_article_tabs.append(plugin)
_markdown_extensions.extend(getattr(PluginClass, 'markdown_extensions', []))
def get_plugins():
return _cache
def get_markdown_extensions():
return _markdown_extensions
\ No newline at end of file
return _markdown_extensions
def get_article_tabs():
return _article_tabs
......@@ -16,11 +16,27 @@ def json_view(func):
return response
return wrap
def get_article(func=None, can_read=True, can_write=False):
"""Intercepts the keyword args path or article_id and looks up an article,
calling the decorated func with this ID."""
def get_article(func=None, can_read=True, can_write=False, deleted_contents=False):
"""View decorator for processing standard url keyword args: Intercepts the
keyword args path or article_id and looks up an article, calling the decorated
func with this ID.
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
path = kwargs.pop('path', None)
......@@ -38,7 +54,7 @@ def get_article(func=None, can_read=True, can_write=False):
if article_id:
article = get_object_or_404(articles, id=article_id)
try:
urlpath = models.URLPath.objects.get(articles=article)
urlpath = models.URLPath.objects.get(articles__article=article)
except models.URLPath.DoesNotExist, models.URLPath.MultipleObjectsReturned:
urlpath = None
else:
......@@ -51,19 +67,33 @@ def get_article(func=None, can_read=True, can_write=False):
pathlist = filter(lambda x: x!="", path.split("/"),)
path = "/".join(pathlist[:-1])
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:
# TODO: Make a nice page
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!
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
return func(request, article, *args, **kwargs)
if func:
return the_func
return wrapper
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
from wiki.editors import editor
from wiki.core.diff import simple_merge
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.'))
content = forms.CharField(label=_(u'Type in some contents'),
......@@ -175,7 +176,7 @@ class CreateForm(forms.Form):
self.urlpath_parent = urlpath_parent
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'),
required=False, widget=editor.get_widget()) #@UndefinedVariable
......@@ -186,13 +187,43 @@ class CreateForm(forms.Form):
slug = self.cleaned_data['slug']
if slug[0] == "_":
raise forms.ValidationError(_(u'A slug may not begin with an underscore.'))
if models.URLPath.objects.filter(slug=slug, parent=self.urlpath_parent):
raise forms.ValidationError(_(u'A slug named "%s" already exists.') % slug)
already_existing_slug = models.URLPath.objects.filter(slug=slug, parent=self.urlpath_parent)
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
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_order = 5
settings_write_access = False
......@@ -221,13 +252,3 @@ class PermissionsForm(forms.ModelForm):
model = models.Article
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):
for decendant in obj.content_object.get_decendants():
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!!"""
cnt = 0
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
if cnt > max_num: return
if max_num and cnt > max_num: return
yield child
# All recursive permission methods will use decendant_objects to access
......
......@@ -110,7 +110,7 @@ class URLPath(MPTTModel):
if not root_nodes:
# (get_or_create does not work for MPTT models??)
root = cls.objects.create(site=site)
article = Article(title=title)
article = Article()
article.add_revision(ArticleRevision(title=title, **kwargs),
save=True)
article.add_object_relation(root)
......@@ -119,10 +119,12 @@ class URLPath(MPTTModel):
return root
@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()
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),
save=True)
article.add_object_relation(newpath)
......@@ -145,6 +147,7 @@ def on_article_delete(instance, *args, **kwargs):
# But move all descendants to a lost-and-found node.
site = Site.objects.get_current()
# Get the Lost-and-found path or create a new one
try:
lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(),
......@@ -153,17 +156,20 @@ def on_article_delete(instance, *args, **kwargs):
lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(),
site=site,)
article = Article(title=_(u"Lost and found"),
group_read = True,
article = Article(group_read = True,
group_write = False,
other_read = False,
other_write = False)
article.add_revision(ArticleRevision(
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():
child.move_to(lost_and_found)
# ...and finally delete the path itself
urlpath.delete()
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):
self._registry = []
pass
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):
if not isinstance(plugin_instance, WikiPlugin):
raise TypeError("That's not a WikiPlugin")
self._registry.append(plugin_instance)
def get_usermessage(self):
pass
class WikiPlugin():
pass
\ No newline at end of file
......@@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _
import settings
from wiki import managers
from wiki.conf import settings as wiki_settings
from wiki.models.pluginbase import ReusablePlugin
from wiki.models.article import BaseRevisionMixin
......@@ -27,7 +26,6 @@ class Attachment(ReusablePlugin):
class Meta:
verbose_name = _(u'attachment')
verbose_name_plural = _(u'attachments')
app_label = wiki_settings.APP_LABEL
def __unicode__(self):
return "%s: %s" % (self.article.current_revision.title, self.original_filename)
......@@ -77,7 +75,6 @@ class AttachmentRevision(BaseRevisionMixin, models.Model):
verbose_name_plural = _(u'attachment revisions')
ordering = ('created',)
get_latest_by = ('revision_number',)
app_label = wiki_settings.APP_LABEL
def get_filename(self):
"""Used to retrieve the filename of a revision.
......
......@@ -3,13 +3,14 @@ from django.conf.urls.defaults import patterns, url
from django.utils.translation import ugettext as _
from wiki.core import plugins_registry
from wiki import plugins
from wiki.plugins.attachments import views
from wiki.plugins.attachments import models
from wiki.plugins.attachments import settings
from wiki.plugins.attachments.markdown_extensions import AttachmentExtension
from wiki.plugins.notifications import ARTICLE_EDIT
class AttachmentPlugin(plugins_registry.BasePlugin):
class AttachmentPlugin(plugins.BasePlugin):
#settings_form = 'wiki.plugins.notifications.forms.SubscriptionForm'
......@@ -27,7 +28,6 @@ class AttachmentPlugin(plugins_registry.BasePlugin):
)
article_tab = (_(u'Attachments'), "icon-file")
article_view = views.AttachmentView().dispatch
article_template_append = 'wiki/plugins/attachments/append.html'
# List of notifications to construct signal handlers for. This
# 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 _
import settings
from wiki.conf import settings as wiki_settings
from wiki.models.pluginbase import RevisionPlugin
class Image(RevisionPlugin):
......@@ -20,4 +19,3 @@ class Image(RevisionPlugin):
class Meta:
verbose_name = _(u'image')
verbose_name_plural = _(u'images')
app_label = wiki_settings.APP_LABEL
# Key for django_notify
ARTICLE_EDIT = "article_edit"
ARTICLE_CREATE = "article_create"
......@@ -6,10 +6,10 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.safestring import mark_safe
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_order = 1
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
from django_notify import notify
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.core import plugins_registry
......@@ -28,23 +28,19 @@ def default_url(article, urlpath=None):
url = reverse('wiki:get', kwargs={'article_id': article.id})
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):
if kwargs.get('created', False):
url = default_url(instance.article)
notify(_(u'Article modified: %s') % instance.title, ARTICLE_EDIT,
target_object=instance.article, url=url)
# Create notifications when new articles are saved. We do NOT care
# about Article objects that are just modified, because many properties
# are modified without any notifications necessary!
signals.post_save.connect(post_article_save, sender=wiki_models.Article,)
if instance.deleted:
notify(_(u'Article deleted: %s') % instance.title, ARTICLE_EDIT,
target_object=instance.article, url=url)
elif instance.previous_revision:
notify(_(u'Article modified: %s') % instance.title, ARTICLE_EDIT,
target_object=instance.article, url=url)
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
# was edited
signals.post_save.connect(post_article_revision_save, sender=wiki_models.ArticleRevision,)
......
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'
......
......@@ -5,6 +5,25 @@
{% block pagetitle %}{% trans "Add new article" %}{% endblock %}
{% 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" %}
<h1 class="page-header">{% trans "Add new article" %}</h1>
......
......@@ -5,22 +5,58 @@
{% block pagetitle %}{% trans "Delete article" %}{% endblock %}
{% 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">
{% wiki_form create_form %}
<div class="form-actions">
<a href="{% url 'wiki:get' path=parent_urlpath.path %}" class="btn btn-large">
<span class="icon-circle-arrow-left"></span>
{% trans "Go back" %}
</a>
<button type="submit" name="save_changes" class="btn btn-primary btn-large">
<span class="icon-plus"></span>
{% trans "Create article" %}
</button>
</div>
</form>
{% if cannot_delete_root %}
<p class="lead">{% trans "You cannot delete a root article." %}</p>
<p><a href="{% url 'wiki:get' path=urlpath.path article_id=article.id %}">{% trans "Go back" %}</a></p>
{% else %}
{% if cannot_delete_children %}
<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>
{% endif %}
{% if delete_children %}
<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 %}
{% 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 @@
{% if revision == article.current_revision %}
<strong>*</strong>
{% 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;">
<small>
{% if revision.user_message %}
......
......@@ -2,16 +2,15 @@
{% with selected_tab as selected %}
{% for plugin in plugins %}
{% if plugin.article_tab %}
<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 %}">
<span class="{{ plugin.article_tab.1 }}"></span>
{{ plugin.article_tab.0 }}
</a>
</li>
{% endif %}
{% for plugin in article_tabs %}
<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 %}">
<span class="{{ plugin.article_tab.1 }}"></span>
{{ plugin.article_tab.0 }}
</a>
</li>
{% endfor %}
<li class="pull-right{% if selected == "settings" %} active{% endif %}">
{% if not user.is_anonymous %}
<a href="{% url 'wiki:settings' article_id=article.id path=urlpath.path %}">
......
......@@ -16,15 +16,18 @@
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{% for child in urlpath.get_children %}
{% for child in children_slice %}
<li>
<a href="{% url 'wiki:get' path=child.path %}">
{{ child.article.current_revision.title }}
</a>
<a href="{% url 'wiki:get' path=child.path %}">
{{ child.article.current_revision.title }}
</a>
</li>
{% empty %}
<li><a href="#"><em>{% trans "No sub-articles" %}</em></a></li>
{% endfor %}
{% if children_slice_more %}
<li><a href="#"><em>{% trans "...and more" %}</em></a></li>
{% endif %}
<li class="divider"></li>
<li>
<a href="" onclick="alert('TODO')">{% trans "List sub-pages" %} &raquo;</a>
......
......@@ -2,9 +2,11 @@
{% block wiki_contents %}
{% if not preview %}
{% cache 1 article article.current_revision.id %}
{{ article.render }}
{% endcache %}
{% if article.current_revision %}
{% cache 1 article article.current_revision.id %}
{{ article.render }}
{% endcache %}
{% endif %}
{% else %}
{{ content|default:"" }}
{% endif %}
......
......@@ -27,6 +27,7 @@ urlpatterns += patterns('',
# Paths decided by article_ids
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+)/deleted/$', article.Deleted.as_view(), name='deleted'),
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+)/history/$', article.History.as_view(), name='history'),
......@@ -49,7 +50,8 @@ for plugin in plugins_registry._cache.values():
urlpatterns += patterns('',
# Paths decided by URLs
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>.+/|)_preview/$', 'wiki.views.article.preview', name='preview'),
url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'),
......
from django.views.generic.base import TemplateResponseMixin
from wiki.core import plugins_registry
from wiki.conf import settings
class ArticleMixin(TemplateResponseMixin):
"""A mixin that receives an article object as a parameter (usually from a wiki
......@@ -10,10 +11,17 @@ class ArticleMixin(TemplateResponseMixin):
def dispatch(self, request, article, *args, **kwargs):
self.urlpath = kwargs.pop('urlpath', None)
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)
def get_context_data(self, **kwargs):
kwargs['urlpath'] = self.urlpath
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
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