except:
import traceback
traceback.print_exc()
- print "****"
+ print "****", url
print response
print "****"
finally:
return view(request, *args, **kwargs)
return authorized_view
return decorator
+
+import collections
+
+def recursive_groupby(iterable):
+ """
+# >>> recursive_groupby([1,2,3,4,5])
+# [1, 2, 3, 4, 5]
+
+ >>> recursive_groupby([[1]])
+ [1]
+
+ >>> recursive_groupby([('a', 1),('a', 2), 3, ('b', 4), 5])
+ ['a', [1, 2], 3, 'b', [4], 5]
+
+ >>> recursive_groupby([('a', 'x', 1),('a', 'x', 2), ('a', 'x', 3)])
+ ['a', ['x', [1, 2, 3]]]
+
+ """
+
+ def _generator(iterator):
+ group = None
+ grouper = None
+
+ for item in iterator:
+ if not isinstance(item, collections.Sequence):
+ if grouper is not None:
+ yield grouper
+ if len(group):
+ yield recursive_groupby(group)
+ group = None
+ grouper = None
+ yield item
+ continue
+ elif len(item) == 1:
+ if grouper is not None:
+ yield grouper
+ if len(group):
+ yield recursive_groupby(group)
+ group = None
+ grouper = None
+ yield item[0]
+ continue
+ elif not len(item):
+ continue
+
+ if grouper is None:
+ group = [item[1:]]
+ grouper = item[0]
+ continue
+
+ if grouper != item[0]:
+ if grouper is not None:
+ yield grouper
+ if len(group):
+ yield recursive_groupby(group)
+ group = None
+ grouper = None
+ group = [item[1:]]
+ grouper = item[0]
+ continue
+
+ group.append(item[1:])
+
+ if grouper is not None:
+ yield grouper
+ if len(group):
+ yield recursive_groupby(group)
+ group = None
+ grouper = None
+
+ return list(_generator(iterable))
import logging
logger = logging.getLogger("fnp.wiki")
+_PCHARS_DICT = dict(zip((ord(x) for x in u"ĄĆĘŁŃÓŚŻŹąćęłńóśżź "), u"ACELNOSZZacelnoszz_"))
+
+# I know this is barbaric, but I didn't find a better solution ;(
+def split_name(name):
+ parts = name.translate(_PCHARS_DICT).split('__')
+ logger.info("SPLIT %r -> %r", name, parts)
+ return parts
+
+def join_name(*parts, **kwargs):
+ name = u'__'.join(p.translate(_PCHARS_DICT) for p in parts)
+ logger.info("JOIN %r -> %r", parts, name)
+ return name
+
+def normalize_name(name):
+ """
+ >>> normalize_name("gąska".decode('utf-8'))
+ u'gaska'
+ """
+ return name.translate(_PCHARS_DICT).lower()
STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
except DocumentNotFound:
raise Http404
- def put(self, document, author, comment, parent):
+ def put(self, document, author, comment, parent=None):
self.vstorage.save_text(
title=document.name,
text=document.text,
return document
- def create_document(self, id, text, title=None):
- if title is None:
- title = id.title()
+ def create_document(self, text, name):
+ title = u', '.join(p.title for p in split_name(name))
if text is None:
text = u''
- document = Document(self, name=id, text=text, title=title)
- return self.put(document, u"<wiki>", u"Document created.", None)
+ document = Document(self, name=name, text=text, title=title)
+ return self.put(document, u"<wiki>", u"Document created.")
def delete(self, name, author, comment):
self.vstorage.delete_page(name, author, comment)
gallery = os.path.basename(gallery)
result['gallery'] = gallery
-
- if 'title' not in result:
- result['title'] = self.name.title()
-
return result
def info(self):
return self.storage.vstorage.page_meta(self.name, self.revision)
-
def getstorage():
return DocumentStorage(settings.REPOSITORY_PATH)
</div>
<div id="header">
- <h1><a href="{% url wiki.views.document_list %}">Platforma</a></h1>
+ <h1><a href="{% url wiki_document_list %}">Platforma</a></h1>
<div id="tools">
<a href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
{% trans "Help" %}</a>
{% extends "wiki/base.html" %}
+
{% load i18n %}
+{% load wiki %}
{% block extrabody %}
{{ block.super }}
event.preventDefault();
var expr = new RegExp(slugify($('#file-list-filter').val()), 'i');
$('#file-list tbody tr').hide().filter(function(index) {
- return expr.test(slugify($('a', this).text()));
+ return expr.test(slugify( $('a', this).attr('data-id') ));
}).show();
}
</tr>
</thead>
<tbody>
- {% for file in document_list %}
+ {% for doc in docs %}
<tr>
- <td colspan="3"><a target="_blank" href="{% url wiki.views.document_detail file|urlencode %}">{{ file }}</a></td>
+ <td colspan="3"><a target="_blank" data-id="{{doc}}"
+ href="{% url wiki_editor doc %}">{{ doc|wiki_title }}</a></td>
<!-- placeholder </td> -->
</tr>
{% endfor %}
{% block rightcolumn %}
<div id="last-edited-list">
<h2>{% trans "Your last edited documents" %}</h2>
- <ol>
- {% for name, date in last_docs %}
- <li><a href="{% url wiki.views.document_detail name|urlencode %}"
- target="_blank">{{ name }}</a><br/><span class="date">({{ date|date:"H:i:s, d/m/Y" }})</span></li>
+ <ol>
+ {% for name, date in last_docs %}
+ <li><a href="{% url wiki_editor name %}"
+ target="_blank">{{ name|wiki_title }}</a><br/><span class="date">({{ date|date:"H:i:s, d/m/Y" }})</span></li>
{% endfor %}
</ol>
</div>
<button type="button" id="tag-changeset-button">{% trans "Mark version" %}</button>
<button id="open-preview-button"
data-basehref="{% url wiki_details_readonly document_name %}">{% trans "View version" %}</button>
+
</div>
<div id="history-view">
<p class="message-box" style="display:none;"></p>
{% load i18n %}
+{% load wiki %}
<div id="summary-view-editor" class="editor" style="display: none">
<!-- <div class="toolbar">
</div> -->
<h2>
<label for="title">{% trans "Title" %}:</label>
<span data-ui-editable="true" data-edit-target="meta.displayTitle"
- >{{ document_meta.title }}</span>
+ >{{ document.name|wiki_title }}</span>
</h2>
<p>
<label>{% trans "Document ID" %}:</label>
- <span>{{ document.name|urlencode }}</span>
+ <span>{{ document.name }}</span>
</p>
<p>
<label>{% trans "Current version" %}:</label>
- {{ document_info.revision }} ({{document_info.last_update}})
+ {{ document_info.revision }} ({{document_info.date}})
<p>
<label>{% trans "Last edited by" %}:</label>
- {{document_info.last_comitter}}
+ {{document_info.author}}
</p>
<p>
<label for="gallery">{% trans "Link to gallery" %}:</label>
<p><button type="button" id="publish_button">{% trans "Publish" %}</button></p>
</div>
-</div>
\ No newline at end of file
+</div>
{% load i18n %}
+{% load wiki %}
<li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective">
- <span>{{ document_meta.title }}</span>
+ <span>{{ document.name|wiki_title }}</span>
</li>
+# -*- coding: utf-8
from django.conf.urls.defaults import *
+from django.views.generic.simple import redirect_to
+from django.conf import settings
+
+
+PART = ur"""[ ĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9\w_.-]+"""
urlpatterns = patterns('wiki.views',
- url(r'^$',
- 'document_list', name='wiki_doclist'),
+ url(r'^$', redirect_to, {'url': 'catalogue/'}),
+
+ url(r'^catalogue/$', 'document_list', name='wiki_document_list'),
+ url(r'^catalogue/([^/]+)/$', 'document_list'),
+ url(r'^catalogue/([^/]+)/([^/]+)/$', 'document_list'),
+ url(r'^catalogue/([^/]+)/([^/]+)/([^/]+)$', 'document_list'),
+
+ url(r'^(?P<name>%s)$' % PART,
+ 'editor', name="wiki_editor"),
+
+ url(r'^(?P<name>[^/]+)/readonly$',
+ 'editor_readonly', name="wiki_editor_readonly"),
url(r'^create/(?P<name>[^/]+)',
- 'document_create_missing', name='wiki_create_missing'),
+ 'create_missing', name='wiki_create_missing'),
+
+ url(r'^(?P<directory>[^/]+)/gallery$',
+ 'gallery', name="wiki_gallery"),
- url(r'^gallery/(?P<directory>[^/]+)$',
- 'document_gallery', name="wiki_gallery"),
url(r'^(?P<name>[^/]+)/history$',
- 'document_history', name="wiki_history"),
+ 'history', name="wiki_history"),
+
url(r'^(?P<name>[^/]+)/text$',
- 'document_text', name="wiki_text"),
- url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$',
- 'document_publish', name="wiki_publish"),
- url(r'^(?P<name>[^/]+)/diff$',
- 'document_diff', name="wiki_diff"),
- url(r'^(?P<name>[^/]+)/tags$',
- 'document_add_tag', name="wiki_add_tag"),
- url(r'^(?P<name>[^/]+)/publish$', 'document_publish'),
+ 'text', name="wiki_text"),
+
+ url(r'^(?P<name>[^/]+)/publish$', 'publish', name="wiki_publish"),
+ url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="wiki_publish"),
+
+ url(r'^(?P<name>[^/]+)/diff$', 'diff', name="wiki_diff"),
+ url(r'^(?P<name>[^/]+)/tags$', 'add_tag', name="wiki_add_tag"),
+
- url(r'^(?P<name>[^/]+)/readonly$',
- 'document_detail_readonly', name="wiki_details_readonly"),
- url(r'^(?P<name>[^/]+)$',
- 'document_detail', name="wiki_details"),
)
import os
+import functools
+import logging
+logger = logging.getLogger("fnp.wiki")
from django.conf import settings
from django.views.generic.simple import direct_to_template
from django.views.decorators.http import require_POST, require_GET
from django.core.urlresolvers import reverse
-from wiki.helpers import JSONResponse, JSONFormInvalid, JSONServerError, ajax_require_permission
+from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
+ ajax_require_permission, recursive_groupby)
from django import http
-from wiki.models import getstorage, DocumentNotFound
+from wiki.models import getstorage, DocumentNotFound, normalize_name, split_name, join_name
from wiki.forms import DocumentTextSaveForm, DocumentTagForm, DocumentCreateForm
from datetime import datetime
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
-import wlapi
#
# Quick hack around caching problems, TODO: use ETags
#
from django.views.decorators.cache import never_cache
-import logging
-logger = logging.getLogger("fnp.peanut.api")
-
+import wlapi
import nice_diff
import operator
MAX_LAST_DOCS = 10
+def normalized_name(view):
+
+ @functools.wraps(view)
+ def decorated(request, name, *args):
+ normalized = normalize_name(name)
+ logger.debug('View check %r -> %r', name, normalized)
+
+ if normalized != name:
+ return http.HttpResponseRedirect(
+ reverse('wiki_' + view.__name__, kwargs={'name': normalized}))
+
+ return view(request, name, *args)
+
+ return decorated
+
+
@never_cache
-def document_list(request, template_name='wiki/document_list.html'):
- # TODO: find a way to cache "Storage All"
- return direct_to_template(request, template_name, extra_context={
- 'document_list': getstorage().all(),
+def document_list(request):
+ return direct_to_template(request, 'wiki/document_list.html', extra_context={
+ 'docs': getstorage().all(),
'last_docs': sorted(request.session.get("wiki_last_docs", {}).items(),
key=operator.itemgetter(1), reverse=True),
})
@never_cache
-def document_detail(request, name, template_name='wiki/document_details.html'):
+@normalized_name
+def editor(request, name, template_name='wiki/document_details.html'):
storage = getstorage()
try:
@require_GET
-def document_detail_readonly(request, name, template_name='wiki/document_details_readonly.html'):
+@normalized_name
+def editor_readonly(request, name, template_name='wiki/document_details_readonly.html'):
+ name = normalize_name(name)
storage = getstorage()
try:
})
-def document_create_missing(request, name):
+@normalized_name
+def create_missing(request, name):
storage = getstorage()
if request.method == "POST":
@never_cache
-def document_text(request, name):
+@normalized_name
+def text(request, name):
storage = getstorage()
if request.method == 'POST':
@never_cache
-def document_gallery(request, directory):
+def gallery(request, directory):
try:
base_url = ''.join((
smart_unicode(settings.MEDIA_URL),
@never_cache
-def document_diff(request, name):
+@normalized_name
+def diff(request, name):
storage = getstorage()
revA = int(request.GET.get('from', 0))
@never_cache
-def document_history(request, name):
+@normalized_name
+def history(request, name):
storage = getstorage()
# TODO: pagination
@require_POST
@ajax_require_permission('wiki.can_change_tags')
-def document_add_tag(request, name):
+def add_tag(request, name):
+ name = normalize_name(name)
storage = getstorage()
form = DocumentTagForm(request.POST, prefix="addtag")
@require_POST
@ajax_require_permission('wiki.can_publish')
-def document_publish(request, name):
+def publish(request, name):
+ name = normalize_name(name)
+
storage = getstorage()
document = storage.get_by_tag(name, "ready_to_publish")
"""Quotes URL
>>> urlquote(u'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144')
- 'Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84'
+ 'Za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84'
"""
- return urllib.quote(url.replace(' ', '_').encode('utf-8', 'ignore'), safe)
+ return urllib.quote(url.encode('utf-8', 'ignore'), safe)
def urlunquote(url):
"""Unqotes URL
# >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
- # u'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144'
+ # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
"""
- return unicode(urllib.unquote(url), 'utf-8', 'ignore').replace('_', ' ')
+ return unicode(urllib.unquote(url), 'utf-8', 'ignore')
def find_repo_path(path):
def _file_path(self, title):
return os.path.join(self.path, urlquote(title, safe=''))
- def _title_to_file(self, title):
- return os.path.join(self.repo_prefix, urlquote(title, safe=''))
+ def _title_to_file(self, title, type=".xml"):
+ return os.path.join(self.repo_prefix, urlquote(title, safe='')) + type
def _file_to_title(self, filename):
assert filename.startswith(self.repo_prefix)
- name = filename[len(self.repo_prefix):].strip('/')
+ name = filename[len(self.repo_prefix):].strip('/').split('.', 1)[0]
return urlunquote(name)
def __contains__(self, title):
rev = -1
yield title, rev, date, author, comment
- def all_pages(self):
+ def all_pages(self, type=''):
tip = self.repo['tip']
"""Iterate over the titles of all pages in the wiki."""
- return [urlunquote(filename) for filename in tip]
+ return [self._file_to_title(filename) for filename in tip
+ if not filename.startswith('.')
+ and filename.endswith(type) ]
def changed_since(self, rev):
"""Return all pages that changed since specified repository revision."""
class=logging.Formatter
[handler_console]
-class=StreamHandler
+class=FileHandler
level=DEBUG
formatter=default
-args=(sys.stderr, )
\ No newline at end of file
+args=('redakcja.dev.log', )
if __name__ == "__main__":
# Append lib and apps directories to PYTHONPATH
- from os import path
+ import os
import sys
- PROJECT_ROOT = path.realpath(path.dirname(__file__))
- sys.path += [PROJECT_ROOT + '/../apps', PROJECT_ROOT + '/../lib']
+ PROJECT_ROOT = os.path.realpath(os.path.dirname(__file__))
+ sys.path += [os.path.realpath(os.path.join(*x)) for x in (
+ (PROJECT_ROOT, '..', 'apps'),
+ (PROJECT_ROOT, '..', 'lib')
+ )]
+
+
execute_manager(settings)
}
# Import localsettings file, which may override settings defined here
+
+try:
+ import logging
+ if os.path.isfile(LOGGING_CONFIG_FILE):
+ import logging.config
+ logging.config.fileConfig(LOGGING_CONFIG_FILE)
+ else:
+ import sys
+ logging.basicConfig(stream=sys.stderr)
+except NameError:
+ print "No logging"
+ pass
+except ImportError as exc:
+ raise
from django.contrib import admin
from django.conf import settings
+import wiki.urls
+
admin.autodiscover()
urlpatterns = patterns('',
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
url(r'^%s(?P<path>.+)$' % settings.STATIC_URL[1:], 'django.views.static.serve',
{'document_root': settings.STATIC_ROOT, 'show_indexes': True}),
+ (r'^documents/', include(wiki.urls)),
+ url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/documents/'}),
+
)