Commit 341db91d by Bridger Maxwell

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

parents 193d5149 846a665f
...@@ -16,6 +16,7 @@ Not implemented - will be ASAP ...@@ -16,6 +16,7 @@ Not implemented - will be ASAP
* South migrations **Soon** * South migrations **Soon**
* 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?
Ideas Ideas
===== =====
......
...@@ -11,6 +11,7 @@ from itertools import chain ...@@ -11,6 +11,7 @@ from itertools import chain
from wiki import models 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
class CreateRoot(forms.Form): class CreateRoot(forms.Form):
...@@ -219,3 +220,14 @@ class PermissionsForm(forms.ModelForm): ...@@ -219,3 +220,14 @@ class PermissionsForm(forms.ModelForm):
class Meta: class Meta:
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!'))
#!/usr/bin/env python
"""Django model to DOT (Graphviz) converter
by Antonio Cavedoni <antonio@cavedoni.org>
edited as management script by Benjamin Bach <benjamin@overtag.dk>
Depends on package 'graphviz', ie. 'apt-get install graphviz'
Example usage:
$ ./manage.py wikiwiz wiki --inheritance | dot -Tpdf -o <filename>.pdf
Place this script in the management.commands package of your application or project.
options:
-h, --help
show this help message and exit.
-a, --all_applications
show models from all applications.
-d, --disable_fields
don't show the class member fields.
-g, --group_models
draw an enclosing box around models from the same app.
-i, --include_models=User,Person,Car
only include selected models in graph.
-n, --verbose_names
use verbose_name for field and models.
-L, --language
specify language used for verrbose_name localization
-x, --exclude_columns
exclude specific column(s) from the graph.
-X, --exclude_models
exclude specific model(s) from the graph.
-e, --inheritance
show inheritance arrows.
"""
__version__ = "0.99"
__svnid__ = "$Id$"
__license__ = "Python"
__author__ = "Antonio Cavedoni <http://cavedoni.com/>"
__contributors__ = [
"Stefano J. Attardi <http://attardi.org/>",
"limodou <http://www.donews.net/limodou/>",
"Carlo C8E Miron",
"Andre Campos <cahenan@gmail.com>",
"Justin Findlay <jfindlay@gmail.com>",
"Alexander Houben <alexander@houben.ch>",
"Bas van Oostveen <v.oostveen@gmail.com>",
"Joern Hees <gitdev@joernhees.de>",
"Benjamin Bach <benjamin@overtag.dk>",
]
import sys, os
from django.core.management.base import BaseCommand
from optparse import make_option
from django.utils.translation import activate as activate_language
from django.utils.safestring import mark_safe
from django.template import Context, loader
from django.db import models
from django.db.models import get_models
from django.db.models.fields.related import \
ForeignKey, OneToOneField, ManyToManyField, RelatedField
try:
from django.db.models.fields.generic import GenericRelation
except ImportError:
from django.contrib.contenttypes.generic import GenericRelation
def parse_file_or_list(arg):
if not arg:
return []
if not ',' in arg and os.path.isfile(arg):
return [e.strip() for e in open(arg).readlines()]
return arg.split(',')
def generate_dot(app_labels, **kwargs):
disable_fields = kwargs.get('disable_fields', False)
include_models = parse_file_or_list(kwargs.get('include_models', ""))
all_applications = kwargs.get('all_applications', False)
use_subgraph = kwargs.get('group_models', False)
verbose_names = kwargs.get('verbose_names', False)
inheritance = kwargs.get('inheritance', False)
language = kwargs.get('language', None)
if language is not None:
activate_language(language)
exclude_columns = parse_file_or_list(kwargs.get('exclude_columns', ""))
exclude_models = parse_file_or_list(kwargs.get('exclude_models', ""))
def skip_field(field):
if exclude_columns:
if verbose_names and field.verbose_name:
if field.verbose_name in exclude_columns:
return True
if field.name in exclude_columns:
return True
return False
t = loader.get_template_from_string("""
digraph name {
fontname = "Helvetica"
fontsize = 8
node [
fontname = "Helvetica"
fontsize = 8
shape = "plaintext"
]
edge [
fontname = "Helvetica"
fontsize = 8
]
""")
c = Context({})
dot = t.render(c)
apps = []
if all_applications:
apps = models.get_apps()
for app_label in app_labels:
app = models.get_app(app_label)
if not app in apps:
apps.append(app)
graphs = []
for app in apps:
graph = Context({
'name': '"%s"' % app.__name__,
'app_name': "%s" % '.'.join(app.__name__.split('.')[:-1]),
'cluster_app_name': "cluster_%s" % app.__name__.replace(".", "_"),
'disable_fields': disable_fields,
'use_subgraph': use_subgraph,
'models': []
})
appmodels = get_models(app)
abstract_models = []
for appmodel in appmodels:
abstract_models = abstract_models + [abstract_model for abstract_model in appmodel.__bases__ if hasattr(abstract_model, '_meta') and abstract_model._meta.abstract]
abstract_models = list(set(abstract_models)) # remove duplicates
appmodels = abstract_models + appmodels
for appmodel in appmodels:
appmodel_abstracts = [abstract_model.__name__ for abstract_model in appmodel.__bases__ if hasattr(abstract_model, '_meta') and abstract_model._meta.abstract]
# collect all attribs of abstract superclasses
def getBasesAbstractFields(c):
_abstract_fields = []
for e in c.__bases__:
if hasattr(e, '_meta') and e._meta.abstract:
_abstract_fields.extend(e._meta.fields)
_abstract_fields.extend(getBasesAbstractFields(e))
return _abstract_fields
abstract_fields = getBasesAbstractFields(appmodel)
model = {
'app_name': appmodel.__module__.replace(".", "_"),
'name': appmodel.__name__,
'abstracts': appmodel_abstracts,
'fields': [],
'relations': []
}
# consider given model name ?
def consider(model_name):
if exclude_models and model_name in exclude_models:
return False
return not include_models or model_name in include_models
if not consider(appmodel._meta.object_name):
continue
if verbose_names and appmodel._meta.verbose_name:
model['label'] = appmodel._meta.verbose_name
else:
model['label'] = model['name']
# model attributes
def add_attributes(field):
if verbose_names and field.verbose_name:
label = field.verbose_name
else:
label = field.name
t = type(field).__name__
if isinstance(field, (OneToOneField, ForeignKey)):
t += " ({0})".format(field.rel.field_name)
# TODO: ManyToManyField, GenericRelation
model['fields'].append({
'name': field.name,
'label': label,
'type': t,
'blank': field.blank,
'abstract': field in abstract_fields,
})
# Find all the real attributes. Relations are depicted as graph edges instead of attributes
attributes = [field for field in appmodel._meta.local_fields if not isinstance(field, RelatedField)]
# find primary key and print it first, ignoring implicit id if other pk exists
pk = appmodel._meta.pk
if not appmodel._meta.abstract and pk in attributes:
add_attributes(pk)
for field in attributes:
if skip_field(field):
continue
if not field.primary_key:
add_attributes(field)
# FIXME: actually many_to_many fields aren't saved in this model's db table, so why should we add an attribute-line for them in the resulting graph?
#if appmodel._meta.many_to_many:
# for field in appmodel._meta.many_to_many:
# if skip_field(field):
# continue
# add_attributes(field)
# relations
def add_relation(field, extras=""):
if verbose_names and field.verbose_name:
label = field.verbose_name
else:
label = field.name
# show related field name
if hasattr(field, 'related_query_name'):
label += ' (%s)' % field.related_query_name()
# handle self-relationships
if field.rel.to == 'self':
target_model = field.model
else:
target_model = field.rel.to
_rel = {
'target_app': target_model.__module__.replace('.', '_'),
'target': target_model.__name__,
'type': type(field).__name__,
'name': field.name,
'label': label,
'arrows': extras,
'needs_node': True
}
if _rel not in model['relations'] and consider(_rel['target']):
model['relations'].append(_rel)
for field in appmodel._meta.local_fields:
if field.attname.endswith('_ptr_id'): # excluding field redundant with inheritance relation
continue
if field in abstract_fields: # excluding fields inherited from abstract classes. they too show as local_fields
continue
if skip_field(field):
continue
if isinstance(field, OneToOneField):
add_relation(field, '[arrowhead=none, arrowtail=none]')
elif isinstance(field, ForeignKey):
add_relation(field, '[arrowhead=none, arrowtail=dot]')
for field in appmodel._meta.local_many_to_many:
if skip_field(field):
continue
if isinstance(field, ManyToManyField):
if (getattr(field, 'creates_table', False) or # django 1.1.
(hasattr(field.rel.through, '_meta') and field.rel.through._meta.auto_created)): # django 1.2
add_relation(field, '[arrowhead=dot arrowtail=dot, dir=both]')
elif isinstance(field, GenericRelation):
add_relation(field, mark_safe('[style="dotted", arrowhead=normal, arrowtail=normal, dir=both]'))
if inheritance:
# add inheritance arrows
for parent in appmodel.__bases__:
if hasattr(parent, "_meta"): # parent is a model
l = "multi-table"
if parent._meta.abstract:
l = "abstract"
if appmodel._meta.proxy:
l = "proxy"
l += r"\ninheritance"
_rel = {
'target_app': parent.__module__.replace(".", "_"),
'target': parent.__name__,
'type': "inheritance",
'name': "inheritance",
'label': l,
'arrows': '[arrowhead=empty, arrowtail=none]',
'needs_node': True
}
# TODO: seems as if abstract models aren't part of models.getModels, which is why they are printed by this without any attributes.
if _rel not in model['relations'] and consider(_rel['target']):
model['relations'].append(_rel)
graph['models'].append(model)
graphs.append(graph)
nodes = []
for graph in graphs:
nodes.extend([e['name'] for e in graph['models']])
for graph in graphs:
# don't draw duplication nodes because of relations
for model in graph['models']:
for relation in model['relations']:
if relation['target'] in nodes:
relation['needs_node'] = False
# render templates
t = loader.get_template_from_string("""{% if use_subgraph %}
subgraph {{ cluster_app_name }} {
label=<
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER"
><FONT FACE="Helvetica Bold" COLOR="Black" POINT-SIZE="12"
>{{ app_name }}</FONT></TD></TR>
</TABLE>
>
color=olivedrab4
style="rounded"
{% endif %}
{% for model in models %}
{{ model.app_name }}_{{ model.name }} [label=<
<TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
><FONT FACE="Helvetica Bold" COLOR="white"
>{{ model.label }}{% if model.abstracts %}<BR/>&lt;<FONT FACE="Helvetica Italic">{{ model.abstracts|join:"," }}</FONT>&gt;{% endif %}</FONT></TD></TR>
{% if not disable_fields %}
{% for field in model.fields %}
<TR><TD ALIGN="LEFT" BORDER="0"
><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica {% if field.abstract %}Italic{% else %}Bold{% endif %}">{{ field.label }}</FONT
></TD>
<TD ALIGN="LEFT"
><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica {% if field.abstract %}Italic{% else %}Bold{% endif %}">{{ field.type }}</FONT
></TD></TR>
{% endfor %}
{% endif %}
</TABLE>
>]
{% endfor %}
{% if use_subgraph %}
}
{% endif %}""")
dot += '\n' + t.render(graph)
for graph in graphs:
t = loader.get_template_from_string("""{% for model in models %}
{% for relation in model.relations %}
{% if relation.needs_node %}
{{ relation.target_app }}_{{ relation.target }} [label=<
<TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
><FONT FACE="Helvetica Bold" COLOR="white"
>{{ relation.target }}</FONT></TD></TR>
</TABLE>
>]
{% endif %}
{{ model.app_name }}_{{ model.name }} -> {{ relation.target_app }}_{{ relation.target }}
[label="{{ relation.label }}"] {{ relation.arrows }};
{% endfor %}
{% endfor %}""")
dot += '\n' + t.render(graph)
t = loader.get_template_from_string("}")
c = Context({})
dot += '\n' + t.render(c)
return dot
class Command(BaseCommand):
args = ('--dummy')
help = 'Create a graph of your app!' #@ReservedAssignment
option_list = BaseCommand.option_list + (
make_option('--all_applications', '-a', dest='all_applications', default=False,
action='store_true',
help='Include all applications'),
make_option('--disable_fields', '-d', action='store',
dest='disable_fields',
default="",
help='Specify fields to exclude'),
make_option('--group_models', '-g', action='store', dest='group_models',
help=''),
make_option('--include_models', '-i', action='store', dest='include_models',
help=''),
make_option('--verbose_names', '-n', action='store', dest='verbose_names',
help=''),
make_option('--language', '-l', action='store', dest='language',
help=''),
make_option('--exclude_models', '-X', action='store', dest='exclude_models',
help=''),
make_option('--inheritance', '-e', action='store_true', dest='inheritance',
help=''),
)
def handle(self, *args, **options):
if not args and not options.get('all_applications', False):
print __doc__
sys.exit()
print generate_dot(args, **options)
...@@ -48,3 +48,25 @@ from wiki.core.plugins_load import load_wiki_plugins ...@@ -48,3 +48,25 @@ from wiki.core.plugins_load import load_wiki_plugins
load_wiki_plugins() load_wiki_plugins()
from django.core import urlresolvers
original_django_reverse = urlresolvers.reverse
def reverse(*args, **kwargs):
"""Now this is a crazy and silly hack, but it is basically here to
enforce that an empty path always takes precedence over an article_id
such that the root article doesn't get resolved to /ID/ but /."""
if args[0].startswith('wiki:'):
url_kwargs = kwargs.get('kwargs', {})
path = url_kwargs.get('path', False)
# If a path is supplied then discard the article_id
if path != False:
url_kwargs.pop('article_id', None)
url_kwargs['path'] = path
kwargs['kwargs'] = url_kwargs
return original_django_reverse(*args, **kwargs)
# Now we redefine reverse method
urlresolvers.reverse = reverse
\ No newline at end of file
...@@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from wiki.conf import settings from wiki.conf import settings
from wiki.core import article_markdown, plugins_registry from wiki.core import article_markdown, plugins_registry
from wiki import managers from wiki import managers
from mptt.models import MPTTModel
class Article(models.Model): class Article(models.Model):
...@@ -69,7 +70,7 @@ class Article(models.Model): ...@@ -69,7 +70,7 @@ class Article(models.Model):
return False return False
def decendant_objects(self): def decendant_objects(self):
for obj in self.objectforarticle_set.filter(has_parent_field=True): for obj in self.objectforarticle_set.filter(is_mptt=True):
for decendant in obj.get_decendants(): for decendant in obj.get_decendants():
yield decendant yield decendant
...@@ -115,11 +116,11 @@ class Article(models.Model): ...@@ -115,11 +116,11 @@ class Article(models.Model):
def add_object_relation(self, obj): def add_object_relation(self, obj):
content_type = ContentType.objects.get_for_model(obj) content_type = ContentType.objects.get_for_model(obj)
has_parent_field = hasattr(obj, 'parent') is_mptt = isinstance(obj, MPTTModel)
rel = ArticleForObject.objects.get_or_create(article=self, rel = ArticleForObject.objects.get_or_create(article=self,
content_type=content_type, content_type=content_type,
object_id=obj.id, object_id=obj.id,
has_parent_method=has_parent_field) is_mptt=is_mptt)
return rel return rel
@classmethod @classmethod
...@@ -159,7 +160,7 @@ class ArticleForObject(models.Model): ...@@ -159,7 +160,7 @@ class ArticleForObject(models.Model):
object_id = models.PositiveIntegerField(_('object ID')) object_id = models.PositiveIntegerField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_id") content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_id")
has_parent_method = models.BooleanField(default=False, editable=False) is_mptt = models.BooleanField(default=False, editable=False)
class Meta: class Meta:
app_label = settings.APP_LABEL app_label = settings.APP_LABEL
...@@ -168,7 +169,9 @@ class ArticleForObject(models.Model): ...@@ -168,7 +169,9 @@ class ArticleForObject(models.Model):
# Do not allow several objects # Do not allow several objects
unique_together = ('content_type', 'object_id') unique_together = ('content_type', 'object_id')
class BaseRevision(models.Model): class BaseRevisionMixin(models.Model):
"""This is an abstract model used as a mixin: Do not override any of the
core model methods but respect the inheritor's freedom to do so itself."""
revision_number = models.IntegerField(editable=False, verbose_name=_(u'revision number')) revision_number = models.IntegerField(editable=False, verbose_name=_(u'revision number'))
...@@ -200,11 +203,8 @@ class BaseRevision(models.Model): ...@@ -200,11 +203,8 @@ class BaseRevision(models.Model):
class Meta: class Meta:
abstract = True abstract = True
app_label = settings.APP_LABEL
get_latest_by = ('revision_number',)
ordering = ('created',)
class ArticleRevision(BaseRevision): class ArticleRevision(BaseRevisionMixin, models.Model):
"""This is where main revision data is stored. To make it easier to """This is where main revision data is stored. To make it easier to
copy, do NEVER create m2m relationships.""" copy, do NEVER create m2m relationships."""
...@@ -266,3 +266,10 @@ class ArticleRevision(BaseRevision): ...@@ -266,3 +266,10 @@ class ArticleRevision(BaseRevision):
self.article.save() self.article.save()
if not self.title: if not self.title:
self.title = self.article.title self.title = self.article.title
class Meta:
app_label = settings.APP_LABEL
get_latest_by = ('revision_number',)
ordering = ('created',)
unique_together = ('article', 'revision_number')
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import signals
""" """
There are two kinds of plugin objects: There are two kinds of plugin objects:
...@@ -18,26 +19,36 @@ There are two kinds of plugin objects: ...@@ -18,26 +19,36 @@ There are two kinds of plugin objects:
from article import Article, ArticleRevision from article import Article, ArticleRevision
from wiki.conf import settings
class ArticlePlugin(models.Model): class ArticlePlugin(models.Model):
"""Always extend from this if you're making a plugin. That way, a deletion
of an article will CASCADE to your plugin, and the database will be kept
clean. Furthermore, it's possible to list all plugins and maintain generic
properties in the future..."""
article = models.ForeignKey(Article, on_delete=models.CASCADE, article = models.ForeignKey(Article, on_delete=models.CASCADE,
verbose_name=_(u"article")) verbose_name=_(u"article"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_(u"created"))
modified = models.DateTimeField(auto_now=True, verbose_name=_(u"created"))
deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False)
def purge(self):
"""Remove related contents completely, ie. media files."""
pass
class Meta: class Meta:
abstract = True app_label = settings.APP_LABEL
class ReusablePlugin(ArticlePlugin):
class ReusablePlugin(ArticlePlugin):
"""Extend from this model if you have a plugin that may be related to many
articles. Please note that the ArticlePlugin.article ForeignKey STAYS! This
is in order to maintain an explicit set of permissions. If you do not like this,
you can override can_read and can_write."""
# The article on which the plugin was originally created. # The article on which the plugin was originally created.
# Used to apply permissions. # Used to apply permissions.
ArticlePlugin.article.on_delete=models.SET_NULL
ArticlePlugin.article.verbose_name=_(u'original article') ArticlePlugin.article.verbose_name=_(u'original article')
ArticlePlugin.article.help_text=_(u'Permissions are inherited from this article') ArticlePlugin.article.help_text=_(u'Permissions are inherited from this article')
ArticlePlugin.article.on_delete=models.SET_NULL
ArticlePlugin.article.null = True ArticlePlugin.article.null = True
ArticlePlugin.article.blank = True ArticlePlugin.article.blank = True
...@@ -54,9 +65,6 @@ class ReusablePlugin(ArticlePlugin): ...@@ -54,9 +65,6 @@ class ReusablePlugin(ArticlePlugin):
return self.article.can_write(*args, **kwargs) return self.article.can_write(*args, **kwargs)
return False return False
class Meta:
abstract = True
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Automatically make the original article the first one in the added set # Automatically make the original article the first one in the added set
...@@ -67,24 +75,30 @@ class ReusablePlugin(ArticlePlugin): ...@@ -67,24 +75,30 @@ class ReusablePlugin(ArticlePlugin):
super(ReusablePlugin, self).save(*args, **kwargs) super(ReusablePlugin, self).save(*args, **kwargs)
class Meta:
app_label = settings.APP_LABEL
class RevisionPluginCreateError(Exception): pass class RevisionPluginCreateError(Exception): pass
class RevisionPlugin(models.Model): class RevisionPlugin(ArticlePlugin):
""" """
Inherit from this model and make sure to specify an article when Inherit from this model and make sure to specify an article when
saving a new instance. This way, a new revision will be created, and saving a new instance. This way, a new revision will be created, and
users are able to roll back to the a previous revision (containing some users are able to roll back to the a previous revision (in which your
other instance of your plugin). plugin wasn't related to the article).
Furthermore, your plugin relation is kept when new revisions are created.
Usage: Usage:
class YourPlugin(RevisionPlugin): class YourPlugin(RevisionPlugin):
... ...
Creating new plugins: Creating new plugins instances:
YourPlugin(article=article_instance, ...) or YourPlugin(article=article_instance, ...) or
YourPlugin.objects.create(article=article_instance, ...) YourPlugin.objects.create(article=article_instance, ...)
""" """
revision = models.ForeignKey(ArticleRevision, on_delete=models.CASCADE) revision = models.ForeignKey(ArticleRevision, on_delete=models.CASCADE)
...@@ -109,5 +123,22 @@ class RevisionPlugin(models.Model): ...@@ -109,5 +123,22 @@ class RevisionPlugin(models.Model):
self.revision = new_revision self.revision = new_revision
class Meta: class Meta:
abstract = True app_label = settings.APP_LABEL
######################################################
# SIGNAL HANDLERS
######################################################
# Look at me. I'm a plane.
# And the plane becomes a metaphor for my life.
# It's my art, when I disguise my body in the shape of a plane.
# (Shellac, 1993)
def update_revision_plugins(instance, *args, **kwargs):
"""Every time a new article revision is created, we update all active
plugins to match this article revision"""
if kwargs.get('created', False):
p_revisions = RevisionPlugin.objects.filter(article=instance.article, deleted=False)
p_revisions.update(revision=instance)
signals.post_save.connect(update_revision_plugins, ArticleRevision)
\ No newline at end of file
...@@ -56,7 +56,7 @@ class URLPath(MPTTModel): ...@@ -56,7 +56,7 @@ class URLPath(MPTTModel):
super(URLPath, self).save(*args, **kwargs) super(URLPath, self).save(*args, **kwargs)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
assert not self.parent and self.get_children(), "You cannot delete a root article with children." assert not (self.parent and self.get_children()), "You cannot delete a root article with children."
super(URLPath, self).delete(*args, **kwargs) super(URLPath, self).delete(*args, **kwargs)
class Meta: class Meta:
......
...@@ -24,12 +24,15 @@ class AttachmentPreprocessor(markdown.preprocessors.Preprocessor): ...@@ -24,12 +24,15 @@ class AttachmentPreprocessor(markdown.preprocessors.Preprocessor):
m = ATTACHMENT_RE.match(line) m = ATTACHMENT_RE.match(line)
if m: if m:
attachment_id = m.group('id').strip() attachment_id = m.group('id').strip()
attachment = models.Attachment.objects.get(article=self.markdown.article, try:
attachment = models.Attachment.objects.get(article=self.markdown.article,
id=attachment_id) id=attachment_id)
url = reverse('wiki:attachments_download', kwargs={'article_id': self.markdown.article.id, url = reverse('wiki:attachments_download', kwargs={'article_id': self.markdown.article.id,
'attachment_id':attachment.id,}) 'attachment_id':attachment.id,})
line = line.replace(m.group(1), u"""<span class="attachment"><a href="%s" title="%s">%s</a>""" % line = line.replace(m.group(1), u"""<span class="attachment"><a href="%s" title="%s">%s</a>""" %
(url, _(u"Click to download file"), attachment.original_filename)) (url, _(u"Click to download file"), attachment.original_filename))
except models.Attachment.DoesNotExist:
line = line.replace(m.group(1), u"""<span class="attachment attachment-deleted">Attachment with ID #%s is deleted.</span>""" % attachment_id)
new_text.append(line) new_text.append(line)
return new_text return new_text
...@@ -6,7 +6,7 @@ import settings ...@@ -6,7 +6,7 @@ import settings
from wiki import managers from wiki import managers
from wiki.conf import settings as wiki_settings 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 BaseRevision from wiki.models.article import BaseRevisionMixin
class IllegalFileExtension(Exception): class IllegalFileExtension(Exception):
"""File extension on upload is not allowed""" """File extension on upload is not allowed"""
...@@ -63,7 +63,7 @@ def upload_path(instance, filename): ...@@ -63,7 +63,7 @@ def upload_path(instance, filename):
return path.join(upload_path, filename + '.upload') return path.join(upload_path, filename + '.upload')
class AttachmentRevision(BaseRevision): class AttachmentRevision(BaseRevisionMixin, models.Model):
attachment = models.ForeignKey('Attachment') attachment = models.ForeignKey('Attachment')
...@@ -75,6 +75,7 @@ class AttachmentRevision(BaseRevision): ...@@ -75,6 +75,7 @@ class AttachmentRevision(BaseRevision):
class Meta: class Meta:
verbose_name = _(u'attachment revision') verbose_name = _(u'attachment revision')
verbose_name_plural = _(u'attachment revisions') verbose_name_plural = _(u'attachment revisions')
ordering = ('created',)
get_latest_by = ('revision_number',) get_latest_by = ('revision_number',)
app_label = wiki_settings.APP_LABEL app_label = wiki_settings.APP_LABEL
......
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n humanize %} {% load wiki_tags i18n humanize %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "Delete" %} "{{ attachment.current_revision.get_filename }}"{% endblock %} {% block pagetitle %}{% trans "Delete" %} "{{ attachment.current_revision.get_filename }}"{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block wiki_contents %}
<div class="tabbable tabs-top" style="margin-top: 20px;"> {% if attachment.article == article %}
<ul class="nav nav-tabs"> <h2>{% trans "Delete" %} "{{ attachment.current_revision.get_filename }}"?</h2>
{% with "attachments" as selected %} <p class="lead">
{% include "wiki/includes/article_menu.html" %} {% blocktrans with attachment.original_filename as filename %}
{% endwith %} The file may be referenced on other articles. Deleting it means that they will loose their references to this file. The following articles reference this file:
<li> {% endblocktrans %}
<h1 style="margin-top: -10px;"> </p>
{{ article.current_revision.title }} <ul>
</h1> {% for a in attachment.articles.all %}
</li> <li style="font-size: 150%;">{{ a.current_revision.title }}</li>
</ul> {% endfor %}
<div class="tab-content"> </ul>
{% if attachment.article == article %} <hr />
<h2>{% trans "Delete" %} "{{ attachment.current_revision.get_filename }}"?</h2> <form method="POST" class="form-horizontal" id="attachment_form" enctype="multipart/form-data">
<p class="lead"> {% wiki_form form %}
{% blocktrans with attachment.original_filename as filename %} <div class="form-actions">
The file may be referenced on other articles. Deleting it means that they will loose their references to this file. The following articles reference this file: <a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}" class="btn">
{% endblocktrans %} <span class="icon-arrow-left"></span>
</p> {% trans "Go back" %}
<ul> </a>
{% for a in attachment.articles.all %} <button class="btn btn-danger btn-large">
<li style="font-size: 150%;">{{ a.current_revision.title }}</li> <span class="icon-upload"></span>
{% endfor %} {% trans "Delete it!" %}
</ul> </button>
<hr />
<form method="POST" class="form-horizontal" id="attachment_form" enctype="multipart/form-data">
{% wiki_form form %}
<div class="form-actions">
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}" class="btn">
<span class="icon-arrow-left"></span>
{% trans "Go back" %}
</a>
<button class="btn btn-danger btn-large">
<span class="icon-upload"></span>
{% trans "Delete it!" %}
</button>
</div>
</form>
{% else %}
<h2>{% trans "Remove" %} "{{ attachment.current_revision.get_filename }}"?</h2>
<p class="lead">
{% blocktrans with attachment.original_filename as filename %}
You can remove a reference to a file, but it will retain its references on other articles.
{% endblocktrans %}
</p>
<form method="POST" class="form-horizontal" id="attachment_form" enctype="multipart/form-data">
{% wiki_form form %}
<div class="form-actions">
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}" class="btn">
<span class="icon-arrow-left"></span>
{% trans "Go back" %}
</a>
<button class="btn btn-danger btn-large">
<span class="icon-upload"></span>
{% trans "Remove reference" %}
</button>
</div>
</form>
{% endif %}
</div>
</div> </div>
</form>
<div class="tabbable tabs-below" style="margin-top: 20px;"> {% else %}
<ul class="nav nav-tabs"> <h2>{% trans "Remove" %} "{{ attachment.current_revision.get_filename }}"?</h2>
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li> <p class="lead">
</ul> {% blocktrans with attachment.original_filename as filename %}
You can remove a reference to a file, but it will retain its references on other articles.
{% endblocktrans %}
</p>
<form method="POST" class="form-horizontal" id="attachment_form" enctype="multipart/form-data">
{% wiki_form form %}
<div class="form-actions">
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}" class="btn">
<span class="icon-arrow-left"></span>
{% trans "Go back" %}
</a>
<button class="btn btn-danger btn-large">
<span class="icon-upload"></span>
{% trans "Remove reference" %}
</button>
</div> </div>
</form>
{% endif %}
{% endblock %} {% endblock %}
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n humanize %} {% load wiki_tags i18n humanize %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "History of" %} "{{ attachment.current_revision.get_filename }}"{% endblock %} {% block pagetitle %}{% trans "History of" %} "{{ attachment.current_revision.get_filename }}"{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block wiki_contents %}
<div class="tabbable tabs-top" style="margin-top: 20px;">
<ul class="nav nav-tabs">
{% with "attachments" as selected %}
{% include "wiki/includes/article_menu.html" %}
{% endwith %}
<li>
<h1 style="margin-top: -10px;">
{{ article.current_revision.title }}
</h1>
</li>
</ul>
<div class="tab-content">
<h2>{% trans "History of" %} "{{ attachment.current_revision.get_filename }}"</h2>
<table class="table table-striped table-bordered">
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "File" %}</th>
<th>{% trans "Size" %}</th>
<th style="text-align: right">{% trans "Action" %}</th>
</tr>
{% for revision in revisions %}
<tr>
<td>
{{ revision.created }}
{% if revision.deleted %}<span class="badge badge-important">{% trans "deleted" %}</span>{% endif %}
</td>
<td>
{% if revision.user %}{{ revision.user }}{% else %}{% if user|is_moderator %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
</td>
<td>{{ revision.description|default:_("<em>No description</em>")|safe }}</td>
<td>{{ revision.get_filename }}</td>
<td>{{ revision.file.size|filesizeformat }}</td>
<td style="text-align: right">
<form method="POST" action="{% url 'wiki:attachments_revision_change' path=urlpath.path article_id=article.id attachment_id=attachment.id revision_id=revision.id %}">
{% csrf_token %}
<a href="{% url 'wiki:attachments_download' path=urlpath.path article_id=article.id attachment_id=attachment.id revision_id=revision.id %}" class="btn btn-primary">
<span class="icon-download"></span>
{% trans "Download" %}
</a>
{% if revision.attachment.article|can_write:user %}
<button{% if revision == attachment.current_revision %} disabled="disabled"{% endif %} class="btn">
<span class="icon-flag"></span>
{% trans "Use this!" %}
</button>
{% endif %}
</form>
</td>
</tr>
{% endfor %}
</table>
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}"><span class="icon-arrow-left"></span> {% trans "Go back" %}</a>
</div>
</div>
<div class="tabbable tabs-below" style="margin-top: 20px;"> <h2>{% trans "History of" %} "{{ attachment.current_revision.get_filename }}"</h2>
<ul class="nav nav-tabs"> <table class="table table-striped table-bordered">
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li> <tr>
</ul> <th>{% trans "Date" %}</th>
</div> <th>{% trans "User" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "File" %}</th>
<th>{% trans "Size" %}</th>
<th style="text-align: right">{% trans "Action" %}</th>
</tr>
{% for revision in revisions %}
<tr>
<td>
{{ revision.created }}
{% if revision.deleted %}<span class="badge badge-important">{% trans "deleted" %}</span>{% endif %}
</td>
<td>
{% if revision.user %}{{ revision.user }}{% else %}{% if user|is_moderator %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
</td>
<td>{{ revision.description|default:_("<em>No description</em>")|safe }}</td>
<td>{{ revision.get_filename }}</td>
<td>{{ revision.file.size|filesizeformat }}</td>
<td style="text-align: right">
<form method="POST" action="{% url 'wiki:attachments_revision_change' path=urlpath.path article_id=article.id attachment_id=attachment.id revision_id=revision.id %}">
{% csrf_token %}
<a href="{% url 'wiki:attachments_download' path=urlpath.path article_id=article.id attachment_id=attachment.id revision_id=revision.id %}" class="btn btn-primary">
<span class="icon-download"></span>
{% trans "Download" %}
</a>
{% if revision.attachment.article|can_write:user %}
<button{% if revision == attachment.current_revision %} disabled="disabled"{% endif %} class="btn">
<span class="icon-flag"></span>
{% trans "Use this!" %}
</button>
{% endif %}
</form>
</td>
</tr>
{% endfor %}
</table>
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}"><span class="icon-arrow-left"></span> {% trans "Go back" %}</a>
{% endblock %} {% endblock %}
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n humanize %} {% load wiki_tags i18n humanize %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "Attachments" %}: {{ article.current_revision.title }}{% endblock %} {% block pagetitle %}{% trans "Attachments" %}: {{ article.current_revision.title }}{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %} <div class="row-fluid">
{% endblock %} <div class="span7">
<p class="lead">{% trans "The following files are available for this article. Copy the markdown tag to directly refer to a file from the article text." %}</p>
{% block wiki_contents %} {% for attachment in attachments %}
<table class="table table-bordered table-striped" style="width: 100%;">
<tr>
<td colspan="4">
<h3>
<a href="{% url 'wiki:attachments_download' path=urlpath.path article_id=article.id attachment_id=attachment.id %}">{{ attachment.current_revision.get_filename }}</a>
<span class="badge">{{ attachment.current_revision.created|naturaltime }}</span>
{% if attachment.current_revision.deleted %}
<span class="badge badge-important">{% trans "deleted" %}</span>
{% endif %}
</h3>
{{ attachment.current_revision.description }}
</td>
</tr>
<tr>
<th>{% trans "Markdown tag" %}</th>
<th>{% trans "Uploaded by" %}</th>
<th>{% trans "Size" %}</th>
<td style="text-align: right;" rowspan="2">
{% if attachment|can_write:user %}
<p>
{% if not attachment.current_revision.deleted %}
<a href="{% url 'wiki:attachments_replace' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">{% trans "Replace" %}</a>
{% if attachment.article = article %}
<a href="{% url 'wiki:attachments_delete' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">{% trans "Delete" %}</a>
{% else %}
<a href="{% url 'wiki:attachments_delete' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">{% trans "Detach" %}</a>
{% endif %}
{% else %}
{% if attachment.current_revision.previous_revision.id %}
<form method="POST" action="{% url 'wiki:attachments_revision_change' path=urlpath.path article_id=article.id attachment_id=attachment.id revision_id=attachment.current_revision.previous_revision.id %}">
{% csrf_token %}
<button class="btn">
{% trans "Restore" %}
</button>
</form>
{% endif %}
{% endif %}
</p>
{% endif %}
<p>
<a href="{% url 'wiki:attachments_history' path=urlpath.path article_id=article.id attachment_id=attachment.id %}">
<span class="icon-time"></span>
{% trans "File history" %} ({{ attachment.attachmentrevision_set.all.count }} {% trans "revisions" %})
</a>
</p>
</td>
</tr>
<tr>
<td><code>[attachment:{{ attachment.id }}]</code></td>
<td>
{% if attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
</td>
<td>{{ attachment.current_revision.get_size|filesizeformat }}</td>
</tr>
</table>
{% empty %}
<p style="margin-bottom: 20px;"><em>{% trans "There are no attachments for this article." %}</em></p>
{% endfor %}
</div>
{% if article|can_write:user %}
<div class="span5" style="min-width: 330px;">
<div class="accordion" id="accordion_upload">
<div class="tabbable tabs-top" style="margin-top: 20px;"> <div class="accordion-group">
<ul class="nav nav-tabs">
{% with "attachments" as selected %} <div class="accordion-heading">
{% include "wiki/includes/article_menu.html" %} <a class="accordion-toggle" href="#collapse_upload" data-toggle="collapse">
{% endwith %} <h2>{% trans "Upload new file" %} <span class="icon-upload"></span></h2>
<li> </a>
<h1 style="margin-top: -10px;">
{{ article.current_revision.title }}
</h1>
</li>
</ul>
<div class="tab-content">
<div class="row-fluid">
<div class="span7">
<p class="lead">{% trans "The following files are available for this article. Copy the markdown tag to directly refer to a file from the article text." %}</p>
{% for attachment in attachments %}
<table class="table table-bordered table-striped" style="width: 100%;">
<tr>
<td colspan="4">
<h3>
<a href="{% url 'wiki:attachments_download' path=urlpath.path article_id=article.id attachment_id=attachment.id %}">{{ attachment.current_revision.get_filename }}</a>
<span class="badge">{{ attachment.current_revision.created|naturaltime }}</span>
{% if attachment.current_revision.deleted %}
<span class="badge badge-important">{% trans "deleted" %}</span>
{% endif %}
</h3>
{{ attachment.current_revision.description }}
</td>
</tr>
<tr>
<th>{% trans "Markdown tag" %}</th>
<th>{% trans "Uploaded by" %}</th>
<th>{% trans "Size" %}</th>
<td style="text-align: right;" rowspan="2">
{% if attachment|can_write:user %}
<p>
{% if not attachment.current_revision.deleted %}
<a href="{% url 'wiki:attachments_replace' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">{% trans "Replace" %}</a>
{% if attachment.article = article %}
<a href="{% url 'wiki:attachments_delete' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">{% trans "Delete" %}</a>
{% else %}
<a href="{% url 'wiki:attachments_delete' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">{% trans "Detach" %}</a>
{% endif %}
{% else %}
{% if attachment.current_revision.previous_revision.id %}
<form method="POST" action="{% url 'wiki:attachments_revision_change' path=urlpath.path article_id=article.id attachment_id=attachment.id revision_id=attachment.current_revision.previous_revision.id %}">
{% csrf_token %}
<button class="btn">
{% trans "Restore" %}
</button>
</form>
{% endif %}
{% endif %}
</p>
{% endif %}
<p>
<a href="{% url 'wiki:attachments_history' path=urlpath.path article_id=article.id attachment_id=attachment.id %}">
<span class="icon-time"></span>
{% trans "File history" %} ({{ attachment.attachmentrevision_set.all.count }} {% trans "revisions" %})
</a>
</p>
</td>
</tr>
<tr>
<td><code>[attachment:{{ attachment.id }}]</code></td>
<td>
{% if attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
</td>
<td>{{ attachment.current_revision.get_size|filesizeformat }}</td>
</tr>
</table>
{% empty %}
<p style="margin-bottom: 20px;"><em>{% trans "There are no attachments for this article." %}</em></p>
{% endfor %}
</div> </div>
{% if article|can_write:user %}
<div class="span5" style="min-width: 330px;"> <div id="collapse_upload" class="accordion-body collapse{% if form.errors %} in{% endif %}">
<div class="accordion" id="accordion_upload"> <div class="accordion-inner">
<form method="POST" class="form-vertical" id="attachment_form" enctype="multipart/form-data">
<div class="accordion-group"> {% wiki_form form %}
<button type="submit" name="save" value="1" class="btn btn-large">
<div class="accordion-heading"> {% trans "Upload file" %}
<a class="accordion-toggle" href="#collapse_upload" data-toggle="collapse"> </button>
<h2>{% trans "Upload new file" %} <span class="icon-upload"></span></h2> </form>
</a>
</div>
<div id="collapse_upload" class="accordion-body collapse{% if form.errors %} in{% endif %}">
<div class="accordion-inner">
<form method="POST" class="form-vertical" id="attachment_form" enctype="multipart/form-data">
{% wiki_form form %}
<button type="submit" name="save" value="1" class="btn btn-large">
{% trans "Upload file" %}
</button>
</form>
</div>
</div>
</div>
</div>
<div class="accordion" id="accordion_add">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" href="#collapse_add" data-toggle="collapse">
<h2>{% trans "Search and add file" %} <span class="icon-plus-sign"></span></h2>
</a>
</div>
<div id="collapse_add" class="accordion-body collapse">
<div class="accordion-inner">
<p>{% trans "You can reuse files from other articles. These files are subject to updates on other articles which may or may not be a good thing." %}</p>
<form method="GET" action="{% url 'wiki:attachments_search' path=urlpath.path article_id=article.id %}" class="form-search">
{{ search_form.query }}
<button class="btn btn-large">
{% trans "Search files and articles" %}
</button>
</form>
</div>
</div>
</div>
</div> </div>
</div> </div>
{% endif %}
</div>
</div>
<div class="accordion" id="accordion_add">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" href="#collapse_add" data-toggle="collapse">
<h2>{% trans "Search and add file" %} <span class="icon-plus-sign"></span></h2>
</a>
</div>
<div id="collapse_add" class="accordion-body collapse">
<div class="accordion-inner">
<p>{% trans "You can reuse files from other articles. These files are subject to updates on other articles which may or may not be a good thing." %}</p>
<form method="GET" action="{% url 'wiki:attachments_search' path=urlpath.path article_id=article.id %}" class="form-search">
{{ search_form.query }}
<button class="btn btn-large">
{% trans "Search files and articles" %}
</button>
</form>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
<div class="tabbable tabs-below" style="margin-top: 20px;">
<ul class="nav nav-tabs"> </div>
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li>
</ul>
</div>
{% endblock %} {% endblock %}
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n humanize %} {% load wiki_tags i18n humanize %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "Replace" %} "{{ attachment.current_revision.get_filename }}"{% endblock %} {% block pagetitle %}{% trans "Replace" %} "{{ attachment.current_revision.get_filename }}"{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block wiki_contents %} <h2>{% trans "Replace" %} "{{ attachment.current_revision.get_filename }}"</h2>
<div class="tabbable tabs-top" style="margin-top: 20px;"> {% if attachment.articles.count > 1 %}
<ul class="nav nav-tabs"> <p class="lead">
{% with "attachments" as selected %} {% blocktrans with attachment.original_filename as filename %}
{% include "wiki/includes/article_menu.html" %} Replacing an attachment means adding a new file that will be used in its place. All references to the file will be replaced by the one you upload and the file will be downloaded as <strong>{{ filename }}</strong>. Please note that this attachment is in use on other articles, you may distort contents. However, do not hestitate to take advantage of this and make replacements for the listed articles where necessary. This way of working is more efficient....
{% endwith %} {% endblocktrans %}
<li> </p>
<h1 style="margin-top: -10px;"> <h3>{% trans "Articles using" %} {{ attachment.current_revision.get_filename }}</h3>
{{ article.current_revision.title }} <ul>
</h1> {% for a in attachment.articles.all %}<li>{{ a.current_revision.title }}</li>{% endfor %}
</li> </ul>
</ul> <hr />
<div class="tab-content"> {% else %}
<h2>{% trans "Replace" %} "{{ attachment.current_revision.get_filename }}"</h2> <p class="lead">
{% if attachment.articles.count > 1 %} {% blocktrans with attachment.original_filename as filename %}
<p class="lead"> Replacing an attachment means adding a new file that will be used in its place. All references to the file will be replaced by the one you upload and the file will be downloaded as <strong>{{ filename }}</strong>.
{% blocktrans with attachment.original_filename as filename %} {% endblocktrans %}
Replacing an attachment means adding a new file that will be used in its place. All references to the file will be replaced by the one you upload and the file will be downloaded as <strong>{{ filename }}</strong>. Please note that this attachment is in use on other articles, you may distort contents. However, do not hestitate to take advantage of this and make replacements for the listed articles where necessary. This way of working is more efficient.... </p>
{% endblocktrans %} {% endif %}
</p>
<h3>{% trans "Articles using" %} {{ attachment.current_revision.get_filename }}</h3>
<ul>
{% for a in attachment.articles.all %}<li>{{ a.current_revision.title }}</li>{% endfor %}
</ul>
<hr />
{% else %}
<p class="lead">
{% blocktrans with attachment.original_filename as filename %}
Replacing an attachment means adding a new file that will be used in its place. All references to the file will be replaced by the one you upload and the file will be downloaded as <strong>{{ filename }}</strong>.
{% endblocktrans %}
</p>
{% endif %}
<form method="POST" class="form-horizontal" id="attachment_form" enctype="multipart/form-data">
{% wiki_form form %}
<div class="form-actions">
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}" class="btn">
<span class="icon-arrow-left"></span>
{% trans "Go back" %}
</a>
<button class="btn btn-primary">
<span class="icon-upload"></span>
{% trans "Upload replacement" %}
</button>
</div>
</form>
</div>
</div>
<div class="tabbable tabs-below" style="margin-top: 20px;"> <form method="POST" class="form-horizontal" id="attachment_form" enctype="multipart/form-data">
<ul class="nav nav-tabs"> {% wiki_form form %}
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li> <div class="form-actions">
</ul> <a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}" class="btn">
<span class="icon-arrow-left"></span>
{% trans "Go back" %}
</a>
<button class="btn btn-primary">
<span class="icon-upload"></span>
{% trans "Upload replacement" %}
</button>
</div> </div>
</form>
{% endblock %} {% endblock %}
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n humanize %} {% load wiki_tags i18n humanize %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "Add file to" %} "{{ article.current_revision.title }}"{% endblock %} {% block pagetitle %}{% trans "Add file to" %} "{{ article.current_revision.title }}"{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block wiki_contents %} <h2>{% trans "Add attachment from other article" %}</h2>
<div class="tabbable tabs-top" style="margin-top: 20px;"> <form method="GET" action="{% url 'wiki:attachments_search' path=urlpath.path article_id=article.id %}" class="form-search">
<ul class="nav nav-tabs"> {{ search_form.query }}
{% with "attachments" as selected %} <button class="btn">
{% include "wiki/includes/article_menu.html" %} <span class="icon-search"></span>
{% endwith %} {% trans "Search files and articles" %}
<li> </button>
<h1 style="margin-top: -10px;"> </form>
{{ article.current_revision.title }}
</h1>
</li>
</ul>
<div class="tab-content">
<h2>{% trans "Add existing attachment to" %} {{ article.current_revision.title }}</h2>
<form method="GET" action="{% url 'wiki:attachments_search' path=urlpath.path article_id=article.id %}" class="form-search"> {% if attachments %}
{{ search_form.query }} <table class="table table-striped table-bordered">
<button class="btn"> <tr>
<span class="icon-search"></span> <th>{% trans "File" %}</th>
{% trans "Search files and articles" %} <th>{% trans "Main article" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Uploaded by" %}</th>
<th>{% trans "Size" %}</th>
<th style="text-align: right">{% trans "Action" %}</th>
</tr>
{% for attachment in attachments %}
<tr>
<td>
<h4>{{ attachment.original_filename }}</h4>
{{ attachment.current_revision.description|default:_("<em>No description</em>")|safe }}
</td>
<td>
<strong>{{ attachment.article.current_revision.title }}</strong>
</td>
<td>
{{ attachment.current_revision.created }}
{% if attachment.current_revision.deleted %}<span class="badge badge-important">{% trans "deleted" %}</span>{% endif %}
</td>
<td>
{% if attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
</td>
<td>{{ attachment.current_revision.file.size|filesizeformat }}</td>
<td style="text-align: right">
<form method="POST" action="{% url 'wiki:attachments_add' path=urlpath.path article_id=article.id attachment_id=attachment.id %}">
{% csrf_token %}
<a href="{% url 'wiki:attachments_download' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">
<span class="icon-download"></span>
{% trans "Download" %}
</a>
<button class="btn btn-primary">
<span class="icon-plus"></span>
{% trans "Add to article" %}
</button> </button>
</form> </form>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p><em>{% trans "Your search did not return any results" %}</em></p>
{% endif %}
{% if attachments %} {% with query as appended_value and "query" as appended_key %}
<table class="table table-striped table-bordered"> {% include "wiki/includes/pagination.html" %}
<tr> {% endwith %}
<th>{% trans "File" %}</th>
<th>{% trans "Main article" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Uploaded by" %}</th>
<th>{% trans "Size" %}</th>
<th style="text-align: right">{% trans "Action" %}</th>
</tr>
{% for attachment in attachments %}
<tr>
<td>
<h4>{{ attachment.original_filename }}</h4>
{{ attachment.current_revision.description|default:_("<em>No description</em>")|safe }}
</td>
<td>
<strong>{{ attachment.article.current_revision.title }}</strong>
</td>
<td>
{{ attachment.current_revision.created }}
{% if attachment.current_revision.deleted %}<span class="badge badge-important">{% trans "deleted" %}</span>{% endif %}
</td>
<td>
{% if attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
</td>
<td>{{ attachment.current_revision.file.size|filesizeformat }}</td>
<td style="text-align: right">
<form method="POST" action="{% url 'wiki:attachments_add' path=urlpath.path article_id=article.id attachment_id=attachment.id %}">
{% csrf_token %}
<a href="{% url 'wiki:attachments_download' path=urlpath.path article_id=article.id attachment_id=attachment.id %}" class="btn">
<span class="icon-download"></span>
{% trans "Download" %}
</a>
<button class="btn btn-primary">
<span class="icon-plus"></span>
{% trans "Add to article" %}
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p><em>{% trans "Your search did not return any results" %}</em></p>
{% endif %}
{% with query as appended_value and "query" as appended_key %}
{% include "wiki/includes/pagination.html" %}
{% endwith %}
<p>
<a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}"><span class="icon-arrow-left"></span> {% trans "Go back" %}</a>
</p>
</div>
</div>
<div class="tabbable tabs-below" style="margin-top: 20px;"> <p>
<ul class="nav nav-tabs"> <a href="{% url 'wiki:attachments_index' path=urlpath.path article_id=article.id %}"><span class="icon-arrow-left"></span> {% trans "Go back" %}</a>
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li> </p>
</ul>
</div>
{% endblock %}
{% endblock %}
...@@ -32,7 +32,7 @@ class AttachmentView(ArticleMixin, FormView): ...@@ -32,7 +32,7 @@ class AttachmentView(ArticleMixin, FormView):
return super(AttachmentView, self).dispatch(request, article, *args, **kwargs) return super(AttachmentView, self).dispatch(request, article, *args, **kwargs)
# WARNING! The below decorator silences other exceptions that may occur! # WARNING! The below decorator silences other exceptions that may occur!
@transaction.commit_manually #@transaction.commit_manually
def form_valid(self, form): def form_valid(self, form):
try: try:
attachment_revision = form.save(commit=False) attachment_revision = form.save(commit=False)
...@@ -48,18 +48,17 @@ class AttachmentView(ArticleMixin, FormView): ...@@ -48,18 +48,17 @@ class AttachmentView(ArticleMixin, FormView):
except models.IllegalFileExtension, e: except models.IllegalFileExtension, e:
transaction.rollback() transaction.rollback()
messages.error(self.request, _(u'Your file could not be saved: %s') % e) messages.error(self.request, _(u'Your file could not be saved: %s') % e)
except Exception: #except Exception:
transaction.rollback() # transaction.rollback()
messages.error(self.request, _(u'Your file could not be saved, probably because of a permission error on the web server.')) # messages.error(self.request, _(u'Your file could not be saved, probably because of a permission error on the web server.'))
transaction.commit() #transaction.commit()
if self.urlpath: return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id)
return redirect("wiki:attachments_index", self.urlpath.path)
# TODO: What if no urlpath?
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['attachments'] = self.attachments kwargs['attachments'] = self.attachments
kwargs['search_form'] = forms.SearchForm() kwargs['search_form'] = forms.SearchForm()
kwargs['selected_tab'] = 'attachments'
return super(AttachmentView, self).get_context_data(**kwargs) return super(AttachmentView, self).get_context_data(**kwargs)
...@@ -78,6 +77,7 @@ class AttachmentHistoryView(ArticleMixin, TemplateView): ...@@ -78,6 +77,7 @@ class AttachmentHistoryView(ArticleMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['attachment'] = self.attachment kwargs['attachment'] = self.attachment
kwargs['revisions'] = self.attachment.attachmentrevision_set.all().order_by('-revision_number') kwargs['revisions'] = self.attachment.attachmentrevision_set.all().order_by('-revision_number')
kwargs['selected_tab'] = 'attachments'
return super(AttachmentHistoryView, self).get_context_data(**kwargs) return super(AttachmentHistoryView, self).get_context_data(**kwargs)
...@@ -102,10 +102,7 @@ class AttachmentReplaceView(ArticleMixin, FormView): ...@@ -102,10 +102,7 @@ class AttachmentReplaceView(ArticleMixin, FormView):
self.attachment.save() self.attachment.save()
messages.success(self.request, _(u'%s uploaded and replaces old attachment.') % attachment_revision.get_filename()) messages.success(self.request, _(u'%s uploaded and replaces old attachment.') % attachment_revision.get_filename())
if self.urlpath: return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id)
return redirect("wiki:attachments_index", self.urlpath.path)
# TODO: What if we do not have a urlpath?
def get_form(self, form_class): def get_form(self, form_class):
form = FormView.get_form(self, form_class) form = FormView.get_form(self, form_class)
...@@ -117,6 +114,7 @@ class AttachmentReplaceView(ArticleMixin, FormView): ...@@ -117,6 +114,7 @@ class AttachmentReplaceView(ArticleMixin, FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['attachment'] = self.attachment kwargs['attachment'] = self.attachment
kwargs['selected_tab'] = 'attachments'
return super(AttachmentReplaceView, self).get_context_data(**kwargs) return super(AttachmentReplaceView, self).get_context_data(**kwargs)
class AttachmentDownloadView(ArticleMixin, View): class AttachmentDownloadView(ArticleMixin, View):
...@@ -132,7 +130,6 @@ class AttachmentDownloadView(ArticleMixin, View): ...@@ -132,7 +130,6 @@ class AttachmentDownloadView(ArticleMixin, View):
return super(AttachmentDownloadView, self).dispatch(request, article, *args, **kwargs) return super(AttachmentDownloadView, self).dispatch(request, article, *args, **kwargs)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if self.revision: if self.revision:
return send_file(request, self.revision.file.path, return send_file(request, self.revision.file.path,
self.revision.created, self.attachment.original_filename) self.revision.created, self.attachment.original_filename)
...@@ -157,12 +154,11 @@ class AttachmentChangeRevisionView(ArticleMixin, View): ...@@ -157,12 +154,11 @@ class AttachmentChangeRevisionView(ArticleMixin, View):
self.attachment.save() self.attachment.save()
messages.success(self.request, _(u'Current revision changed for %s.') % self.attachment.original_filename) messages.success(self.request, _(u'Current revision changed for %s.') % self.attachment.original_filename)
if self.urlpath: return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id)
return redirect("wiki:attachments_index", path=self.urlpath.path)
# TODO: What if this hasn't got a urlpath. def get_context_data(self, **kwargs):
else: kwargs['selected_tab'] = 'attachments'
pass return ArticleMixin.get_context_data(self, **kwargs)
class AttachmentAddView(ArticleMixin, View): class AttachmentAddView(ArticleMixin, View):
...@@ -177,12 +173,8 @@ class AttachmentAddView(ArticleMixin, View): ...@@ -177,12 +173,8 @@ class AttachmentAddView(ArticleMixin, View):
messages.success(self.request, _(u'Added a reference to "%(att)s" from "%(art)s".') % messages.success(self.request, _(u'Added a reference to "%(att)s" from "%(art)s".') %
{'att': self.attachment.original_filename, {'att': self.attachment.original_filename,
'art': self.article.current_revision.title}) 'art': self.article.current_revision.title})
if self.urlpath: return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id)
return redirect("wiki:attachments_index", path=self.urlpath.path)
# TODO: What if this hasn't got a urlpath.
else:
pass
class AttachmentDeleteView(ArticleMixin, FormView): class AttachmentDeleteView(ArticleMixin, FormView):
...@@ -214,12 +206,11 @@ class AttachmentDeleteView(ArticleMixin, FormView): ...@@ -214,12 +206,11 @@ class AttachmentDeleteView(ArticleMixin, FormView):
self.attachment.articles.remove(self.article) self.attachment.articles.remove(self.article)
messages.info(self.request, _(u'This article is no longer related to the file %s.') % self.attachment.original_filename) messages.info(self.request, _(u'This article is no longer related to the file %s.') % self.attachment.original_filename)
if self.urlpath: return redirect("wiki:get", path=self.urlpath.path, article_id=self.article.id)
return redirect("wiki:get_url", path=self.urlpath.path)
# TODO: No urlpath?
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['attachment'] = self.attachment kwargs['attachment'] = self.attachment
kwargs['selected_tab'] = 'attachments'
return super(AttachmentDeleteView, self).get_context_data(**kwargs) return super(AttachmentDeleteView, self).get_context_data(**kwargs)
...@@ -254,4 +245,5 @@ class AttachmentSearchView(ArticleMixin, ListView): ...@@ -254,4 +245,5 @@ class AttachmentSearchView(ArticleMixin, ListView):
kwargs['query'] = self.query kwargs['query'] = self.query
kwargs.update(kwargs_article) kwargs.update(kwargs_article)
kwargs.update(kwargs_listview) kwargs.update(kwargs_listview)
kwargs['selected_tab'] = 'attachments'
return kwargs return kwargs
...@@ -19,12 +19,13 @@ class ArticleSubscription(wiki_models.pluginbase.ArticlePlugin, Subscription): ...@@ -19,12 +19,13 @@ class ArticleSubscription(wiki_models.pluginbase.ArticlePlugin, Subscription):
'article': self.article.current_revision.title, 'article': self.article.current_revision.title,
'type': self.notification_type.label}) 'type': self.notification_type.label})
def default_url(article): def default_url(article, urlpath=None):
try: try:
urlpath = wiki_models.URLPath.objects.get(articles=article) if not urlpath:
url = reverse('wiki:get_url', args=(urlpath.path,)) urlpath = wiki_models.URLPath.objects.get(articles=article)
url = reverse('wiki:get', kwargs={'path': urlpath.path})
except wiki_models.URLPath.DoesNotExist: except wiki_models.URLPath.DoesNotExist:
url = None url = reverse('wiki:get', kwargs={'article_id': article.id})
return url return url
def post_article_save(instance, **kwargs): def post_article_save(instance, **kwargs):
......
notify_latest_id = 0; notify_latest_id = 0;
notify_update_timeout = 30000;
notify_update_timeout_adjust = 1.2; // factor to adjust between each timeout.
function notify_update() { function notify_update() {
jsonWrapper(URL_NOTIFY_GET_NEW, function (data) { jsonWrapper(URL_NOTIFY_GET_NEW, function (data) {
...@@ -29,9 +31,15 @@ function notify_mark_read() { ...@@ -29,9 +31,15 @@ function notify_mark_read() {
}); });
} }
function update_timeout() {
setTimeout("notify_update()", notify_update_timeout);
setTimeout("update_timeout()", notify_update_timeout);
notify_update_timeout *= notify_update_timeout_adjust;
}
$(document).ready(function () { $(document).ready(function () {
notify_update(); // Don't check immediately... some users just click through pages very quickly.
// Update every second minute. setTimeout("notify_update()", 2000);
setInterval("notify_update()", 120000); update_timeout();
}) })
...@@ -15,23 +15,16 @@ ...@@ -15,23 +15,16 @@
<li style="float: left"> <li style="float: left">
<h1 style="margin-top: -10px;">{{ article.current_revision.title }}</h1> <h1 style="margin-top: -10px;">{{ article.current_revision.title }}</h1>
</li> </li>
{% with "view" as selected %} {% include "wiki/includes/article_menu.html" %}
{% include "wiki/includes/article_menu.html" %}
{% endwith %}
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
{% block wiki_contents_tab %}
{% wiki_render article %} {% wiki_render article %}
{% endblock %}
</div> </div>
</div> </div>
<div class="tabbable tabs-below" style="margin-top: 20px;"> <p style="margin-top: 20px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></p>
<ul class="nav nav-tabs">
{% with "view" as selected %}
{% include "wiki/includes/article_menu.html" %}
{% endwith %}
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li>
</ul>
</div>
{% endblock %} {% endblock %}
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<form method="POST" class="form-horizontal"> <form method="POST" class="form-horizontal">
{% wiki_form create_form %} {% wiki_form create_form %}
<div class="form-actions"> <div class="form-actions">
<a href="{% url 'wiki:get_url' parent_urlpath.path %}" class="btn btn-large"> <a href="{% url 'wiki:get' path=parent_urlpath.path %}" class="btn btn-large">
<span class="icon-circle-arrow-left"></span> <span class="icon-circle-arrow-left"></span>
{% trans "Go back" %} {% trans "Go back" %}
</a> </a>
......
{% extends "wiki/base.html" %}
{% load wiki_tags i18n sekizai_tags %}
{% load url from future %}
{% 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>
<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>
{% endblock %}
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n %} {% load wiki_tags i18n %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "Edit" %}: {{ article.current_revision.title }}{% endblock %} {% block pagetitle %}{% trans "Edit" %}: {{ article.current_revision.title }}{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block wiki_contents %}
<div class="tabbable tabs-top" style="margin-top: 20px;"> <form method="POST" class="form-horizontal">
<ul class="nav nav-tabs"> {% include "wiki/includes/editor.html" %}
{% with "edit" as selected %} <div class="form-actions">
{% include "wiki/includes/article_menu.html" %} <button type="submit" name="preview" value="1" class="btn btn-large" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.action='{% url 'wiki:preview' path=urlpath.path article_id=article.id %}'">
{% endwith %} <span class="icon-eye-open"></span>
<li> {% trans "Preview" %}
<h1 style="margin-top: -10px;"> </button>
{{ article.current_revision.title }} <button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="this.form.target=''; this.form.action='{% url 'wiki:edit' path=urlpath.path article_id=article.id %}'">
</h1> <span class="icon-ok"></span>
</li> {% trans "Save changes" %}
</ul> </button>
<div class="tab-content">
<a href="{% url 'wiki:delete' path=urlpath.path article_id=article.id %}" class="pull-right btn btn-danger">
<form method="POST" class="form-horizontal"> <span class="icon-trash"></span>
{% include "wiki/includes/editor.html" %} {% trans "Delete article" %}
<div class="form-actions"> </a>
<button type="submit" name="preview" value="1" class="btn btn-large" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.action='{% url 'wiki:preview_url' urlpath.path %}'">
<span class="icon-eye-open"></span> </div>
{% trans "Preview" %} <div class="modal hide fade" id="previewModal" style="width: 80%; min-height: 500px; margin-left: -40%;">
</button> <div class="modal-body">
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="this.form.target=''; this.form.action='{% url 'wiki:edit_url' urlpath.path %}'"> <iframe name="previewWindow" style="width: 100%; min-height: 400px; border: 0;" frameborder="0"></iframe>
<span class="icon-ok"></span>
{% trans "Save changes" %}
</button>
<a class="pull-right btn btn-danger">
<span class="icon-trash"></span>
{% trans "Delete article" %}
</a>
</div>
<div class="modal hide fade" id="previewModal" style="width: 80%; min-height: 500px; margin-left: -40%;">
<div class="modal-body">
<iframe name="previewWindow" style="width: 100%; min-height: 400px; border: 0;" frameborder="0"></iframe>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-large" data-dismiss="modal">
<span class="icon-circle-arrow-left"></span>
{% trans "Back to editor" %}
</a>
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="this.form.target=''; this.form.action='{% url 'wiki:edit_url' urlpath.path %}'">
<span class="icon-ok"></span>
{% trans "Save changes" %}
</button>
</div>
</div>
</form>
</div>
</div> </div>
<div class="modal-footer">
<div class="tabbable tabs-below" style="margin-top: 20px;"> <a href="#" class="btn btn-large" data-dismiss="modal">
<ul class="nav nav-tabs"> <span class="icon-circle-arrow-left"></span>
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li> {% trans "Back to editor" %}
</ul> </a>
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="this.form.target=''; this.form.action='{% url 'wiki:edit' path=urlpath.path article_id=article.id %}'">
<span class="icon-ok"></span>
{% trans "Save changes" %}
</button>
</div> </div>
</div>
</form>
{% endblock %} {% endblock %}
{% extends "wiki/base.html" %} {% extends "wiki/article.html" %}
{% load wiki_tags i18n sekizai_tags %} {% load wiki_tags i18n sekizai_tags %}
{% load url from future %} {% load url from future %}
{% block pagetitle %}{% trans "History" %}: {{ article.current_revision.title }}{% endblock %} {% block pagetitle %}{% trans "History" %}: {{ article.current_revision.title }}{% endblock %}
{% block wiki_breadcrumbs %} {% block wiki_contents_tab %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block wiki_contents %} {% addtoblock "js" %}
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/diffview.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/diff.js"></script>
<script type="text/javascript">
$(document).ready(
function() {
$('.accordion input[disabled!="disabled"][type="radio"]').first().attr('checked', 'true');
// Fix modal heights
$('.modal-body').css('height', $(window).height()*0.70 + 'px');
$('.modal').css('max-height', $(window).height() + 'px');
});
</script>
{% endaddtoblock %}
{% addtoblock "css" %}
<style type="text/css">
td.linenumber {
width: 20px;
}
tr.insert td {
background-color: #DFC;
}
tr.delete td {
background-color: #FDC;
}
tr.equal td {
background-color: #F2F2F2;
}
.diff-container td {
white-space: pre; font-family: monospace;
}
.diff-container td,
.diff-container th {
padding: 2px 7px;
border-right: 1px solid #DDD;
}
.diff-container td:last-child,
.diff-container th:last-child {
border-right: none;
}
.diff-container table {
border-top: 1px solid #DDD;
}
</style>
{% endaddtoblock %}
{% addtoblock "js" %}
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/diffview.js"></script> <form method="GET">
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/diff.js"></script> <div class="tab-content" style="overflow: visible;">
<script type="text/javascript"> {% for revision in revisions %}
$(document).ready( <div class="accordion" id="accordion{{ revision.revision_number }}">
function() { <div class="accordion-group">
$('.accordion input[disabled!="disabled"][type="radio"]').first().attr('checked', 'true'); <div class="accordion-heading">
// Fix modal heights <a class="accordion-toggle" style="float: left;" href="#collapse{{ revision.revision_number }}" onclick="get_diff_json('{% url 'wiki:diff' revision.id %}', $('#collapse{{ revision.revision_number }}'))">
$('.modal-body').css('height', $(window).height()*0.70 + 'px'); {{ revision.created }} (#{{ revision.revision_number }}) by {% if revision.user %}{{ revision.user }}{% else %}{% if user|is_moderator %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
$('.modal').css('max-height', $(window).height() + 'px'); {% if revision == article.current_revision %}
}); <strong>*</strong>
</script> {% endif %}
{% endaddtoblock %} <div style="color: #CCC;">
{% addtoblock "css" %} <small>
<style type="text/css"> {% if revision.user_message %}
td.linenumber { {{ revision.user_message }}
width: 20px; {% else %}
} ({% trans "no log message" %})
tr.insert td { {% endif %}
background-color: #DFC; </small>
}
tr.delete td {
background-color: #FDC;
}
tr.equal td {
background-color: #F2F2F2;
}
.diff-container td {
white-space: pre; font-family: monospace;
}
.diff-container td,
.diff-container th {
padding: 2px 7px;
border-right: 1px solid #DDD;
}
.diff-container td:last-child,
.diff-container th:last-child {
border-right: none;
}
.diff-container table {
border-top: 1px solid #DDD;
}
</style>
{% endaddtoblock %}
<div class="tabbable tabs-top" style="margin-top: 20px;">
<ul class="nav nav-tabs">
{% with "history" as selected %}
{% include "wiki/includes/article_menu.html" %}
{% endwith %}
<li>
<h1 style="margin-top: -10px;">
{{ article.current_revision.title }}
</h1>
</li>
</ul>
<form method="GET">
<div class="tab-content" style="overflow: visible;">
{% for revision in revisions %}
<div class="accordion" id="accordion{{ revision.revision_number }}">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" style="float: left;" href="#collapse{{ revision.revision_number }}" onclick="get_diff_json('{% url 'wiki:diff' revision.id %}', $('#collapse{{ revision.revision_number }}'))">
{{ revision.created }} (#{{ revision.revision_number }}) by {% if revision.user %}{{ revision.user }}{% else %}{% if user|is_moderator %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %}
{% if revision == article.current_revision %}
<strong>*</strong>
{% endif %}
<div style="color: #CCC;">
<small>
{% if revision.user_message %}
{{ revision.user_message }}
{% else %}
({% trans "no log message" %})
{% endif %}
</small>
</div>
</a>
<div class="progress progress-striped active" style="display: none; width: 40px; float: left; margin-top: 7px; margin-bottom: -7px;">
<div class="bar" style="width: 100%;"></div>
</div>
<div class="pull-right" style="vertical-align: middle; margin: 8px 3px;">
{% if revision == article.current_revision %}
<a href="#" class="btn disabled">
<span class="icon-lock"></span>
{% trans "Preview this version" %}
</a>
{% else %}
<button type="submit" class="btn" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.r.value='{{ revision.id }}'; this.form.action='{% url 'wiki:preview_revision' article.id %}'; $('#previewModal .switch-to-revision').attr('href', '{% url 'wiki:change_revision_url' urlpath.path revision.id %}')">
<span class="icon-eye-open"></span>
{% trans "Preview this version" %}
</button>
{% endif %}
<a class="btn btn-info" href="#collapse{{ revision.revision_number }}" onclick="get_diff_json('{% url 'wiki:diff' revision.id %}', $('#collapse{{ revision.revision_number }}'))">
<span class="icon-list-alt"></span>
{% trans "Show changes" %}
</a>
{% if article|can_write:user %}
<input type="radio"{% if revision == article.current_revision %} disabled="true"{% endif %} style="margin: 0 10px;" value="{{ revision.id }}" name="revision_id" switch-button-href="{% url 'wiki:change_revision_url' urlpath.path revision.id %}" merge-button-href="{% url 'wiki:merge_revision_preview' article.id revision.id %}" merge-button-commit-href="{% url 'wiki:merge_revision_url' urlpath.path revision.id %}" />
{% endif %}
</div>
<div style="clear: both"></div>
</div>
<div id="collapse{{ revision.revision_number }}" class="accordion-body collapse">
<div class="accordion-inner diff-container" style="padding: 0;">
<dl class="dl-horizontal">
<dt>{% trans "Auto log:" %}</dt>
<dd>{{ revision.automatic_log|default:"-"|linebreaksbr }}</dd>
</dl>
<table class="table table-condensed" style="margin: 0; border-collapse: collapse;">
<thead>
<tr>
<th class="linenumber">{% if revision.previous_revision %}#{{revision.previous_revision.revision_number}}{% endif %}</th>
<th class="linenumber">#{{revision.revision_number}}</th>
<th>{% trans "Change" %}</th>
</tr>
</thead>
</table>
</div>
</div> </div>
</a>
<div class="progress progress-striped active" style="display: none; width: 40px; float: left; margin-top: 7px; margin-bottom: -7px;">
<div class="bar" style="width: 100%;"></div>
</div> </div>
</div> <div class="pull-right" style="vertical-align: middle; margin: 8px 3px;">
{% endfor %} {% if revision == article.current_revision %}
<a href="#" class="btn disabled">
{% include "wiki/includes/pagination.html" %}
{% if revisions.count > 1 %}
<div class="form-actions">
<div class="pull-right">
{% if article|can_write:user %}
<button type="submit" name="preview" value="1" class="btn btn-large" onclick="$('#mergeModal').modal('show'); this.form.target='mergeWindow'; this.form.action=$('input[type=radio]:checked').attr('merge-button-href'); $('.merge-revision-commit').attr('href', $('input[type=radio]:checked').attr('merge-button-commit-href'))">
<span class="icon-random"></span>
{% trans "Merge selected with current..." %}
</button>
{% else %}
<button type="submit" disabled="true" name="preview" value="1" class="btn btn-large">
<span class="icon-lock"></span> <span class="icon-lock"></span>
{% trans "Merge selected with current..." %} {% trans "Preview this version" %}
</a>
{% else %}
<button type="submit" class="btn" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.r.value='{{ revision.id }}'; this.form.action='{% url 'wiki:preview_revision' article.id %}'; $('#previewModal .switch-to-revision').attr('href', '{% url 'wiki:change_revision' path=urlpath.path article_id=article.id revision_id=revision.id %}')">
<span class="icon-eye-open"></span>
{% trans "Preview this version" %}
</button> </button>
{% endif %} {% endif %}
<button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.action=$('input[type=radio]:checked').attr('switch-button-href')"> <a class="btn btn-info" href="#collapse{{ revision.revision_number }}" onclick="get_diff_json('{% url 'wiki:diff' revision_id=revision.id %}', $('#collapse{{ revision.revision_number }}'))">
<span class="icon-flag"></span> <span class="icon-list-alt"></span>
{% trans "Switch to selected version" %} {% trans "Show changes" %}
</button> </a>
{% if article|can_write:user %}
<input type="radio"{% if revision == article.current_revision %} disabled="true"{% endif %} style="margin: 0 10px;" value="{{ revision.id }}" name="revision_id" switch-button-href="{% url 'wiki:change_revision' path=urlpath.path revision_id=revision.id %}" merge-button-href="{% url 'wiki:merge_revision_preview' article_id=article.id revision_id=revision.id %}" merge-button-commit-href="{% url 'wiki:merge_revision' path=urlpath.path article_id=article.id revision_id=revision.id %}" />
{% endif %}
</div>
<div style="clear: both"></div>
</div>
<div id="collapse{{ revision.revision_number }}" class="accordion-body collapse">
<div class="accordion-inner diff-container" style="padding: 0;">
<dl class="dl-horizontal">
<dt>{% trans "Auto log:" %}</dt>
<dd>{{ revision.automatic_log|default:"-"|linebreaksbr }}</dd>
</dl>
<table class="table table-condensed" style="margin: 0; border-collapse: collapse;">
<thead>
<tr>
<th class="linenumber">{% if revision.previous_revision %}#{{revision.previous_revision.revision_number}}{% endif %}</th>
<th class="linenumber">#{{revision.revision_number}}</th>
<th>{% trans "Change" %}</th>
</tr>
</thead>
</table>
</div>
</div> </div>
</div>
{% endif %}
</div>
<input type="hidden" name="r" value="" />
<div class="modal hide fade" id="previewModal" style="width: 80%; margin-left: -40%;">
<div class="modal-body">
<iframe name="previewWindow" style="width: 100%; height: 100%; border: 0;" frameborder="0"></iframe>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-large" data-dismiss="modal">
<span class="icon-circle-arrow-left"></span>
{% trans "Back to history view" %}
</a>
{% if article|can_write:user %}
<a href="#" class="btn btn-large btn-primary switch-to-revision">
<span class="icon-flag"></span>
{% trans "Switch to this version" %}
</a>
{% else %}
<a href="#" class="btn btn-large btn-primary disabled">
<span class="icon-lock"></span>
{% trans "Switch to this version" %}
</a>
{% endif %}
</div> </div>
</div> </div>
{% endfor %}
<div class="modal hide fade" id="mergeModal" style="width: 80%; margin-left: -40%;">
<div class="modal-header"> {% include "wiki/includes/pagination.html" %}
<h1>{% trans "Merge with current" %}</h1>
<p class="lead"><span class="icon-info-sign"></span> {% trans "When you merge a revision with the current, all data will be retained from both versions and merged at its approximate location from each revision." %} <strong>{% trans "After this, it's important to do a manual review." %}</strong></p> {% if revisions.count > 1 %}
</div> <div class="form-actions">
<div class="modal-body"> <div class="pull-right">
<iframe name="mergeWindow" style="width: 100%; height: 100%; border: 0;" frameborder="0"></iframe> {% if article|can_write:user %}
</div> <button type="submit" name="preview" value="1" class="btn btn-large" onclick="$('#mergeModal').modal('show'); this.form.target='mergeWindow'; this.form.action=$('input[type=radio]:checked').attr('merge-button-href'); $('.merge-revision-commit').attr('href', $('input[type=radio]:checked').attr('merge-button-commit-href'))">
<div class="modal-footer"> <span class="icon-random"></span>
<a href="#" class="btn btn-large" data-dismiss="modal"> {% trans "Merge selected with current..." %}
<span class="icon-circle-arrow-left"></span> </button>
{% trans "Back to history view" %} {% else %}
</a> <button type="submit" disabled="true" name="preview" value="1" class="btn btn-large">
{% if article|can_write:user %} <span class="icon-lock"></span>
<a href="#" class="btn btn-large btn-primary merge-revision-commit"> {% trans "Merge selected with current..." %}
<span class="icon-file"></span> </button>
{% trans "Create new merged version" %} {% endif %}
</a> <button type="submit" name="save" value="1" class="btn btn-large btn-primary" onclick="$('#previewModal').modal('show'); this.form.target='previewWindow'; this.form.action=$('input[type=radio]:checked').attr('switch-button-href')">
{% else %} <span class="icon-flag"></span>
<a href="#" class="btn btn-large btn-primary disabled"> {% trans "Switch to selected version" %}
<span class="icon-lock"></span> </button>
{% trans "Create new merged version" %}
</a>
{% endif %}
</div>
</div> </div>
</form> </div>
{% endif %}
</div>
<input type="hidden" name="r" value="" />
<div class="modal hide fade" id="previewModal" style="width: 80%; margin-left: -40%;">
<div class="modal-body">
<iframe name="previewWindow" style="width: 100%; height: 100%; border: 0;" frameborder="0"></iframe>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-large" data-dismiss="modal">
<span class="icon-circle-arrow-left"></span>
{% trans "Back to history view" %}
</a>
{% if article|can_write:user %}
<a href="#" class="btn btn-large btn-primary switch-to-revision">
<span class="icon-flag"></span>
{% trans "Switch to this version" %}
</a>
{% else %}
<a href="#" class="btn btn-large btn-primary disabled">
<span class="icon-lock"></span>
{% trans "Switch to this version" %}
</a>
{% endif %}
</div>
</div> </div>
<div style="clear:both"></div> <div class="modal hide fade" id="mergeModal" style="width: 80%; margin-left: -40%;">
<div class="tabbable tabs-below" style="margin-top: 30px;"> <div class="modal-header">
<ul class="nav nav-tabs"> <h1>{% trans "Merge with current" %}</h1>
<li style="margin-top: 10px;"><em>{% trans "Article last modified:" %} {{ article.current_revision.modified }}</em></li> <p class="lead"><span class="icon-info-sign"></span> {% trans "When you merge a revision with the current, all data will be retained from both versions and merged at its approximate location from each revision." %} <strong>{% trans "After this, it's important to do a manual review." %}</strong></p>
</ul> </div>
<div class="modal-body">
<iframe name="mergeWindow" style="width: 100%; height: 100%; border: 0;" frameborder="0"></iframe>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-large" data-dismiss="modal">
<span class="icon-circle-arrow-left"></span>
{% trans "Back to history view" %}
</a>
{% if article|can_write:user %}
<a href="#" class="btn btn-large btn-primary merge-revision-commit">
<span class="icon-file"></span>
{% trans "Create new merged version" %}
</a>
{% else %}
<a href="#" class="btn btn-large btn-primary disabled">
<span class="icon-lock"></span>
{% trans "Create new merged version" %}
</a>
{% endif %}
</div>
</div> </div>
</form>
{% endblock %} {% endblock %}
{% load i18n wiki_tags %}{% load url from future %} {% load i18n wiki_tags %}{% load url from future %}
{% with selected_tab as selected %}
{% for plugin in plugins %} {% for plugin in plugins %}
{% if plugin.article_tab %} {% if plugin.article_tab %}
<li class="pull-right{% if selected == plugin.slug %} active{% endif %}"> <li class="pull-right{% if selected == plugin.slug %} active{% endif %}">
{% if urlpath %} <a href="{% url 'wiki:plugin' slug=plugin.slug article_id=article.id path=urlpath.path %}">
<a href="{% url 'wiki:plugin_url' path=urlpath.path slug=plugin.slug %}">
<span class="{{ plugin.article_tab.1 }}"></span>
{{ plugin.article_tab.0 }}
</a>
{% else %}
<a href="{% url 'wiki:plugin' slug=plugin.slug article_id=article.id %}">
<span class="{{ plugin.article_tab.1 }}"></span> <span class="{{ plugin.article_tab.1 }}"></span>
{{ plugin.article_tab.0 }} {{ plugin.article_tab.0 }}
</a> </a>
{% endif %}
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if urlpath %}
<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_url' path=urlpath.path %}"> <a href="{% url 'wiki:settings' article_id=article.id path=urlpath.path %}">
<span class="icon-wrench"></span> <span class="icon-wrench"></span>
{% trans "Settings" %} {% trans "Settings" %}
</a> </a>
{% endif %} {% endif %}
</li> </li>
<li class="pull-right{% if selected == "history" %} active{% endif %}"> <li class="pull-right{% if selected == "history" %} active{% endif %}">
<a href="{% url 'wiki:history_url' urlpath.path %}"> <a href="{% url 'wiki:history' article_id=article.id path=urlpath.path %}">
<span class="icon-time"></span> <span class="icon-time"></span>
{% trans "Changes" %} {% trans "Changes" %}
</a> </a>
</li> </li>
<li class="pull-right{% if selected == "edit" %} active{% endif %}"> <li class="pull-right{% if selected == "edit" %} active{% endif %}">
<a href="{% url 'wiki:edit_url' urlpath.path %}"> <a href="{% url 'wiki:edit' article_id=article.id path=urlpath.path %}">
<span class="icon-edit"></span> <span class="icon-edit"></span>
{% trans "Edit" %} {% trans "Edit" %}
</a> </a>
</li> </li>
<li class="pull-right{% if selected == "view" %} active{% endif %}"> <li class="pull-right{% if selected == "view" %} active{% endif %}">
<a href="{% url 'wiki:get_url' urlpath.path %}"> <a href="{% url 'wiki:get' article_id=article.id path=urlpath.path %}">
<span class="icon-home"></span> <span class="icon-home"></span>
{% trans "View" %} {% trans "View" %}
</a> </a>
</li> </li>
{% else %} {% endwith %}
<li class="pull-right{% if selected == "settings" %} active{% endif %}">
{% if not user.is_anonymous %}
<a href="{% url 'wiki:settings' article_id=article.id %}">
<span class="icon-wrench"></span>
{% trans "Settings" %}
</a>
{% endif %}
</li>
<li class="pull-right{% if selected == "history" %} active{% endif %}">
<a href="{% url 'wiki:history' article_id=article.id %}">
<span class="icon-time"></span>
{% trans "Changes" %}
</a>
</li>
<li class="pull-right{% if selected == "edit" %} active{% endif %}">
<a href="{% url 'wiki:edit' article_id=article.id %}">
<span class="icon-edit"></span>
{% trans "Edit" %}
</a>
</li>
<li class="pull-right{% if selected == "view" %} active{% endif %}">
<a href="{% url 'wiki:get' article_id=article.id %}">
<span class="icon-home"></span>
{% trans "View" %}
</a>
</li>
{% endif %}
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
<ul class="breadcrumb pull-left" class=""> <ul class="breadcrumb pull-left" class="">
{% for ancestor in urlpath.get_ancestors.all %} {% for ancestor in urlpath.get_ancestors.all %}
<span class="divider">/</span> <span class="divider">/</span>
<li><a href="{% url 'wiki:get_url' ancestor.path %}">{{ ancestor.article.current_revision.title }}</a></li> <li><a href="{% url 'wiki:get' path=ancestor.path %}">{{ ancestor.article.current_revision.title }}</a></li>
{% endfor %} {% endfor %}
<span class="divider">/</span> <span class="divider">/</span>
<li class="active"><a href="{% url 'wiki:get_url' urlpath.path %}">{{ article.current_revision.title }}</a></li> <li class="active"><a href="{% url 'wiki:get' path=urlpath.path %}">{{ article.current_revision.title }}</a></li>
<span class="divider">/</span> <span class="divider">/</span>
</ul> </ul>
<div class="pull-left" style="margin-left: 10px;"> <div class="pull-left" style="margin-left: 10px;">
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for child in urlpath.get_children %} {% for child in urlpath.get_children %}
<li> <li>
<a href="{% url 'wiki:get_url' child.path %}"> <a href="{% url 'wiki:get' path=child.path %}">
{{ child.article.current_revision.title }} {{ child.article.current_revision.title }}
</a> </a>
</li> </li>
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="pull-left" style="margin-left: 10px;"> <div class="pull-left" style="margin-left: 10px;">
<a class="btn" href="{% url 'wiki:create_url' urlpath.path %}" style="padding: 7px;"> <a class="btn" href="{% url 'wiki:create' path=urlpath.path %}" style="padding: 7px;">
<span class="icon-plus"></span> <span class="icon-plus"></span>
{% trans "Add article" %} {% trans "Add article" %}
</a> </a>
......
...@@ -8,7 +8,7 @@ from wiki.core import plugins_registry ...@@ -8,7 +8,7 @@ from wiki.core import plugins_registry
urlpatterns = patterns('', urlpatterns = patterns('',
url('^$', article.ArticleView.as_view(), name='root', kwargs={'path': ''}), url('^$', article.ArticleView.as_view(), name='root', kwargs={'path': ''}),
url('^create-root/$', 'wiki.views.article.root_create', name='root_create'), url('^create-root/$', 'wiki.views.article.root_create', name='root_create'),
url('^_revision/diff/(\d+)/$', 'wiki.views.article.diff', name='diff'), url('^_revision/diff/(?P<revision_id>\d+)/$', 'wiki.views.article.diff', name='diff'),
) )
if settings.ACCOUNT_HANDLING: if settings.ACCOUNT_HANDLING:
...@@ -21,12 +21,12 @@ if settings.ACCOUNT_HANDLING: ...@@ -21,12 +21,12 @@ if settings.ACCOUNT_HANDLING:
urlpatterns += patterns('', urlpatterns += patterns('',
# This one doesn't work because it don't know where to redirect after... # This one doesn't work because it don't know where to redirect after...
url('^_revision/change/(?P<article_id>\d+)/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'), url('^_revision/change/(?P<article_id>\d+)/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'),
url('^_revision/preview/(?P<article_id>\d+)/$', 'wiki.views.article.preview', name='preview_revision'), url('^_revision/preview/(?P<article_id>\d+)/$', 'wiki.views.article.preview', name='preview_revision'),
url('^_revision/merge/(?P<article_id>\d+)/(?P<revision_id>\d+)/preview/$', 'wiki.views.article.merge', name='merge_revision_preview', kwargs={'preview': True}), url('^_revision/merge/(?P<article_id>\d+)/(?P<revision_id>\d+)/preview/$', 'wiki.views.article.merge', name='merge_revision_preview', kwargs={'preview': True}),
# 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+)/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'),
...@@ -42,32 +42,28 @@ for plugin in plugins_registry._cache.values(): ...@@ -42,32 +42,28 @@ for plugin in plugins_registry._cache.values():
plugin_urlpatterns = getattr(plugin, 'urlpatterns', None) plugin_urlpatterns = getattr(plugin, 'urlpatterns', None)
if slug and plugin_urlpatterns: if slug and plugin_urlpatterns:
urlpatterns += patterns('', urlpatterns += patterns('',
url('^(?P<path>.+/|)_plugin/'+slug+'/', include(plugin_urlpatterns)),
url('^(?P<article_id>\d+)(?P<path>)/plugin/'+slug+'/', include(plugin_urlpatterns)),
url('^(?P<article_id>\d+)/plugin/'+slug+'/', include(plugin_urlpatterns)), url('^(?P<article_id>\d+)/plugin/'+slug+'/', include(plugin_urlpatterns)),
url('^(?P<path>.+/|)_plugin/'+slug+'/', include(plugin_urlpatterns)),
) )
urlpatterns += patterns('', urlpatterns += patterns('',
# Paths decided by URLs # Paths decided by URLs
url('^(?P<path>.+/|)_create/$', article.Create.as_view(), name='create_url'), url('^(?P<path>.+/|)_create/$', article.Create.as_view(), name='create'),
url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit_url'), url('^(?P<path>.+/|)_delete/$', article.Edit.as_view(), name='delete'),
url('^(?P<path>.+/|)_preview/$', 'wiki.views.article.preview', name='preview_url'), url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history_url'), url('^(?P<path>.+/|)_preview/$', 'wiki.views.article.preview', name='preview'),
url('^(?P<path>.+/|)_settings/$', article.Settings.as_view(), name='settings_url'), url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'),
url('^(?P<path>.+/|)_revision/change/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision_url'), url('^(?P<path>.+/|)_settings/$', article.Settings.as_view(), name='settings'),
url('^(?P<path>.+/|)_revision/merge/(?P<revision_id>\d+)/$', 'wiki.views.article.merge', name='merge_revision_url'), url('^(?P<path>.+/|)_revision/change/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'),
url('^(?P<path>.+/|)_plugin/(?P<slug>\w+)/$', article.Plugin.as_view(), name='plugin_url'), url('^(?P<path>.+/|)_revision/merge/(?P<revision_id>\d+)/$', 'wiki.views.article.merge', name='merge_revision'),
url('^(?P<path>.+/|)_plugin/(?P<slug>\w+)/$', article.Plugin.as_view(), name='plugin'),
) url('^(?P<path>.+/|)$', article.ArticleView.as_view(), name='get'),
urlpatterns += patterns('',
url('^(?P<path>.+/|)$', article.ArticleView.as_view(), name='get_url'),
) )
def get_pattern(app_name="wiki", namespace="wiki"): def get_pattern(app_name="wiki", namespace="wiki"):
"""Every url resolution takes place as "wiki:view_name". """Every url resolution takes place as "wiki:view_name".
You should not attempt to have multiple deployments of the wiki on You should not attempt to have multiple deployments of the wiki in a
one site. single Django project.
https://docs.djangoproject.com/en/dev/topics/http/urls/#topics-http-reversing-url-namespaces https://docs.djangoproject.com/en/dev/topics/http/urls/#topics-http-reversing-url-namespaces
""" """
return urlpatterns, app_name, namespace return urlpatterns, app_name, namespace
\ No newline at end of file
...@@ -28,8 +28,8 @@ class Logout(View): ...@@ -28,8 +28,8 @@ class Logout(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
auth_logout(request) auth_logout(request)
messages.info(request, _(u"You are no longer logged in. Bye bye!")) messages.info(request, _(u"You are no longer logged in. Bye bye!"))
print redirect("wiki:get_url", URLPath.root().path) print redirect("wiki:get", URLPath.root().path)
return redirect("wiki:get_url", URLPath.root().path) return redirect("wiki:get", URLPath.root().path)
class Login(FormView): class Login(FormView):
...@@ -47,5 +47,5 @@ class Login(FormView): ...@@ -47,5 +47,5 @@ class Login(FormView):
messages.info(self.request, _(u"You are now logged in! Have fun!")) messages.info(self.request, _(u"You are now logged in! Have fun!"))
if self.request.GET.get("next", None): if self.request.GET.get("next", None):
return redirect(self.request.GET['next']) return redirect(self.request.GET['next'])
return redirect("wiki:get_url", URLPath.root().path) return redirect("wiki:get", URLPath.root().path)
...@@ -18,15 +18,19 @@ from wiki.core.diff import simple_merge ...@@ -18,15 +18,19 @@ from wiki.core.diff import simple_merge
from wiki.decorators import get_article, json_view from wiki.decorators import get_article, json_view
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from wiki.core.exceptions import NoRootURL
class ArticleView(ArticleMixin, TemplateView, ): class ArticleView(ArticleMixin, TemplateView):
template_name="wiki/article.html" template_name="wiki/article.html"
@method_decorator(get_article(can_read=True)) @method_decorator(get_article(can_read=True))
def dispatch(self, request, article, *args, **kwargs): def dispatch(self, request, article, *args, **kwargs):
return super(ArticleView, self).dispatch(request, article, *args, **kwargs) return super(ArticleView, self).dispatch(request, article, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['selected_tab'] = 'view'
return ArticleMixin.get_context_data(self, **kwargs)
class Create(FormView, ArticleMixin): class Create(FormView, ArticleMixin):
...@@ -78,14 +82,14 @@ class Create(FormView, ArticleMixin): ...@@ -78,14 +82,14 @@ class Create(FormView, ArticleMixin):
else: else:
messages.error(self.request, _(u"There was an error creating this article.")) messages.error(self.request, _(u"There was an error creating this article."))
transaction.commit() transaction.commit()
return redirect('wiki:get_url', '') return redirect('wiki:get', '')
url = self.get_success_url() url = self.get_success_url()
transaction.commit() transaction.commit()
return url return url
def get_success_url(self): def get_success_url(self):
return redirect('wiki:get_url', self.newpath.path) return redirect('wiki:get', self.newpath.path)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['parent_urlpath'] = self.urlpath kwargs['parent_urlpath'] = self.urlpath
...@@ -95,6 +99,56 @@ class Create(FormView, ArticleMixin): ...@@ -95,6 +99,56 @@ class Create(FormView, ArticleMixin):
return super(Create, self).get_context_data(**kwargs) return super(Create, self).get_context_data(**kwargs)
class Delete(FormView, ArticleMixin):
form_class = forms.DeleteForm
template_name="wiki/delete.html"
@method_decorator(get_article(can_write=True))
def dispatch(self, request, article, *args, **kwargs):
super_return = super(Delete, self).dispatch(request, article, *args, **kwargs)
# Where to go after deletion...
self.next = request.GET.get('next', None)
if not self.next:
if self.urlpath:
self.next = reverse('wiki:get', path=self.urlpath.parent.path)
else:
for art_obj in self.objectforarticle_set.filter(is_mptt=True):
self.next = reverse('wiki:get', kwargs={'article_id': art_obj.parent.article.id})
return super_return
def get_initial(self):
return {'revision': self.article.current_revision}
def get_form(self, form_class):
form = FormView.get_form(self, form_class)
if self.request.user.has_perm('wiki.moderator'):
form.fields['purge'].widget = forms.forms.CheckboxInput()
def form_valid(self, form):
cd = form.cleaned_data
if self.request.user.has_perm('wiki.moderator') and cd['purge']:
pass
messages.success(self.request, _(u'This article together with all its contents are now completely gone! Thanks!'))
else:
revision = models.ArticleRevision()
revision.inherit_predecessor(self.article)
revision.deleted = True
self.article.add_revision(revision)
messages.success(self.request, _(u'This article is now marked as deleted! Thanks for keeping the site free from unwanted material!'))
def get_success_url(self):
return redirect(self.next)
def get_context_data(self, **kwargs):
kwargs['parent_urlpath'] = self.urlpath
kwargs['parent_article'] = self.article
kwargs['create_form'] = kwargs.pop('form', None)
kwargs['editor'] = editors.editor
return super(Create, self).get_context_data(**kwargs)
class Edit(FormView, ArticleMixin): class Edit(FormView, ArticleMixin):
form_class = forms.EditForm form_class = forms.EditForm
...@@ -122,16 +176,15 @@ class Edit(FormView, ArticleMixin): ...@@ -122,16 +176,15 @@ class Edit(FormView, ArticleMixin):
return self.get_success_url() return self.get_success_url()
def get_success_url(self): def get_success_url(self):
if not self.urlpath is None: if self.urlpath:
return redirect("wiki:get_url", self.urlpath.path) return redirect("wiki:get", path=self.urlpath.path)
# TODO: Where to go if it's a different object? It's probably return redirect('wiki:get', article_id=self.article.id)
# an ajax callback, so we don't care... but should perhaps return
# a status
return
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['edit_form'] = kwargs.pop('form', None) kwargs['edit_form'] = kwargs.pop('form', None)
kwargs['editor'] = editors.editor kwargs['editor'] = editors.editor
kwargs['selected_tab'] = 'edit'
return super(Edit, self).get_context_data(**kwargs) return super(Edit, self).get_context_data(**kwargs)
...@@ -151,6 +204,7 @@ class History(ListView, ArticleMixin): ...@@ -151,6 +204,7 @@ class History(ListView, ArticleMixin):
kwargs_listview = ListView.get_context_data(self, **kwargs) kwargs_listview = ListView.get_context_data(self, **kwargs)
kwargs.update(kwargs_article) kwargs.update(kwargs_article)
kwargs.update(kwargs_listview) kwargs.update(kwargs_listview)
kwargs['selected_tab'] = 'history'
return kwargs return kwargs
@method_decorator(get_article(can_read=True)) @method_decorator(get_article(can_read=True))
...@@ -198,7 +252,9 @@ class Settings(ArticleMixin, TemplateView): ...@@ -198,7 +252,9 @@ class Settings(ArticleMixin, TemplateView):
usermessage = form.get_usermessage() usermessage = form.get_usermessage()
if usermessage: if usermessage:
messages.success(self.request, usermessage) messages.success(self.request, usermessage)
return redirect('wiki:settings_url', self.urlpath.path) if self.urlpath:
return redirect('wiki:settings', path=self.urlpath.path)
return redirect('wiki:settings', article_id=self.article.id)
else: else:
form = Form(self.article, self.request.user) form = Form(self.article, self.request.user)
self.forms.append(form) self.forms.append(form)
...@@ -211,9 +267,12 @@ class Settings(ArticleMixin, TemplateView): ...@@ -211,9 +267,12 @@ class Settings(ArticleMixin, TemplateView):
return super(Settings, self).get(*args, **kwargs) return super(Settings, self).get(*args, **kwargs)
def get_success_url(self): def get_success_url(self):
return redirect('wiki:settings_url', self.urlpath.path) if self.urlpath:
return redirect('wiki:settings', path=self.urlpath.path)
return redirect('wiki:settings', article_id=self.article.id)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['selected_tab'] = 'settings'
kwargs['forms'] = self.forms kwargs['forms'] = self.forms
return super(Settings, self).get_context_data(**kwargs) return super(Settings, self).get_context_data(**kwargs)
...@@ -226,10 +285,9 @@ def change_revision(request, article, revision_id=None, urlpath=None): ...@@ -226,10 +285,9 @@ def change_revision(request, article, revision_id=None, urlpath=None):
article.save() article.save()
messages.success(request, _(u'The article %s is now set to display revision #%d') % (revision.title, revision.revision_number)) messages.success(request, _(u'The article %s is now set to display revision #%d') % (revision.title, revision.revision_number))
if urlpath: if urlpath:
return redirect("wiki:history_url", urlpath.path) return redirect("wiki:history", path=urlpath.path)
else: else:
# TODO: Where to go if not a urlpath object? return redirect('wiki:history', article_id=article.id)
pass
# TODO: Throw in a class-based view # TODO: Throw in a class-based view
@get_article(can_read=True) @get_article(can_read=True)
...@@ -306,7 +364,9 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ ...@@ -306,7 +364,9 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ
{'r1': revision.revision_number, {'r1': revision.revision_number,
'r2': old_revision.revision_number}) 'r2': old_revision.revision_number})
if urlpath: if urlpath:
return redirect('wiki:edit_url', urlpath.path) return redirect('wiki:edit', path=urlpath.path)
else:
return redirect('wiki:edit', article_id=article.id)
c = RequestContext(request, {'article': article, c = RequestContext(request, {'article': article,
...@@ -319,6 +379,14 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ ...@@ -319,6 +379,14 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ
return render_to_response(template_file, c) return render_to_response(template_file, c)
def root_create(request): def root_create(request):
try:
root = models.URLPath.root()
if not root.article:
root.delete()
raise NoRootURL
return redirect('wiki:get', path=root.path)
except NoRootURL:
pass
if not request.user.has_perm('wiki.add_article'): if not request.user.has_perm('wiki.add_article'):
return redirect(reverse("wiki:login") + "?next=" + reverse("wiki:root_create")) return redirect(reverse("wiki:login") + "?next=" + reverse("wiki:root_create"))
if request.method == 'POST': if request.method == 'POST':
......
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