--- /dev/null
+from __future__ import absolute_import
+
+from django.template.defaultfilters import stringfilter
+from django import template
+
+register = template.Library()
+
+# from wiki.models import split_name
+
+@register.filter
+@stringfilter
+def wiki_title(value):
+ parts = (p.replace('_', ' ').title() for p in split_name(value))
+ return ' / '.join(parts)
--- /dev/null
+cd $WORKSPACE
+virtualenv -q ve
+source ./ve/bin/activate
+pip install -q -E ./ve -r requirements.txt
+pip install -q -E ./ve -r requirements-test.txt
+django-admin.py test --settings=redakcja.settings.test
+++ /dev/null
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-
-import os
-import tempfile
-from nose.tools import *
-from nose.core import runmodule
-
-import vstorage
-
-NULL_PARENT = -1
-
-
-def clear_directory(top):
- for root, dirs, files in os.walk(top, topdown=False):
- for name in files:
- os.remove(os.path.join(root, name))
- for name in dirs:
- os.rmdir(os.path.join(root, name))
- try:
- os.removedirs(top)
- except OSError:
- pass
-
-
-class TestVersionedStorage(object):
- def setUp(self):
- self.repo_path = tempfile.mkdtemp()
- self.repo = vstorage.VersionedStorage(self.repo_path)
-
- def tearDown(self):
- clear_directory(self.repo_path)
-
- def test_save_text(self):
- text = u"test text"
- title = u"test title"
- author = u"test author"
- comment = u"test comment"
-
- self.repo.save_text(
- title=title,
- text=text,
- author=author,
- comment=comment,
- parent=NULL_PARENT,
- )
-
- saved = self.repo.open_page(title).read()
- assert_equal(saved, text)
-
- def test_save_text_noparent(self):
- text = u"test text"
- title = u"test title"
- author = u"test author"
- comment = u"test comment"
-
- self.repo.save_text(title=title,
- text=text, author=author,
- comment=comment, parent=None)
-
- saved = self.repo.open_page(title).read()
- assert_equal(saved, text)
-
- def test_save_merge_no_conflict(self):
- text = u"test\ntext"
- title = u"test title"
- author = u"test author"
- comment = u"test comment"
- self.repo.save_text(title=title,
- text=text, author=author,
- comment=comment, parent=NULL_PARENT)
- self.repo.save_text(title=title,
- text=text, author=author,
- comment=comment, parent=NULL_PARENT)
- saved = self.repo.open_page(title).read()
- assert_equal(saved, text)
-
- def test_save_merge_line_conflict(self):
- text = u"test\ntest\n"
- text1 = u"test\ntext\n"
- text2 = u"text\ntest\n"
- title = u"test title"
- author = u"test author"
- comment = u"test comment"
-
- self.repo.save_text(title=title,
- text=text, author=author,
- comment=comment, parent=NULL_PARENT)
-
- self.repo.save_text(title=title,
- text=text1, author=author,
- comment=comment, parent=0)
-
- self.repo.save_text(title=title,
- text=text2, author=author,
- comment=comment, parent=0)
-
- saved = self.repo.open_page(title).read()
-
- # Other conflict markers placement can also be correct
- assert_equal(saved, u'''\
-text
-test
-<<<<<<< local
-=======
-text
->>>>>>> other
-''')
-
- def test_delete(self):
- text = u"text test"
- title = u"test title"
- author = u"test author"
- comment = u"test comment"
- self.repo.save_text(title=title,
- text=text, author=author,
- comment=comment, parent=NULL_PARENT)
-
- assert title in self.repo
-
- self.repo.delete_page(title, author, comment)
-
- assert title not in self.repo
-
- @raises(vstorage.DocumentNotFound)
- def test_document_not_found(self):
- self.repo.open_page(u'unknown entity')
-
- def test_open_existing_repository(self):
- self.repo.save_text(title=u'Python!', text=u'ham and spam')
- current_repo_revision = self.repo.repo_revision()
- same_repo = vstorage.VersionedStorage(self.repo_path)
- assert_equal(same_repo.repo_revision(), current_repo_revision)
-
- def test_history(self):
- COMMITS = [
- {"author": "bunny", "text":"1", "comment": "Oh yeah!"},
- {"author": "frank", "text":"2", "comment": "Second is the best!"},
- {"text":"3", "comment": "Third"},
- {"author": "welma", "text":"4", "comment": "Fourth"},
- ]
-
- for commit in COMMITS:
- self.repo.save_text(title=u"Sample", **commit)
-
- for n, entry in enumerate(reversed(list(self.repo.page_history(u"Sample")))):
- assert_equal(entry["version"], n)
- assert_equal(entry["author"], COMMITS[n].get("author", "anonymous"))
- assert_equal(entry["description"], COMMITS[n]["comment"])
- assert_equal(entry["tag"], [])
-
-
-class TestVSTags(object):
-
- TITLE_1 = "Sample"
-
- COMMITS = [
- {"author": "bunny", "text":"1", "comment": "Oh yeah!"},
- {"author": "frank", "text":"2", "comment": "Second is the best!"},
- {"text":"3", "comment": "Third"},
- {"author": "welma", "text":"4", "comment": "Fourth"},
- ]
-
- def setUp(self):
- self.repo_path = tempfile.mkdtemp()
- self.repo = vstorage.VersionedStorage(self.repo_path)
-
- # generate some history
- for commit in self.COMMITS:
- self.repo.save_text(title=u"Sample", **commit)
-
- # verify
- for n, entry in enumerate(reversed(list(self.repo.page_history(self.TITLE_1)))):
- assert_equal(entry["tag"], [])
-
- def tearDown(self):
- clear_directory(self.repo_path)
-
- def test_add_tag(self):
- TAG_USER = "mike_the_tagger"
- TAG_NAME = "production"
- TAG_VERSION = 2
-
- # Add tag
- self.repo.add_page_tag(self.TITLE_1, TAG_VERSION, TAG_NAME, TAG_USER)
-
- # check history again
- history = list(self.repo.page_history(self.TITLE_1))
- for entry in reversed(history):
- if entry["version"] == TAG_VERSION:
- assert_equal(entry["tag"], [TAG_NAME])
- else:
- assert_equal(entry["tag"], [])
-
- def test_add_many_tags(self):
- TAG_USER = "mike_the_tagger"
- tags = [
- (2, "production", "mike"),
- (2, "finished", "jeremy"),
- (0, "original", "jeremy"),
- ]
-
- for rev, name, user in tags:
- self.repo.add_page_tag(self.TITLE_1, rev, name, user)
-
- # check history again
- history = list(self.repo.page_history(self.TITLE_1))
- for entry in reversed(history):
- expected = [tag[1] for tag in tags if tag[0] == entry["version"]]
- assert_equal(set(entry["tag"]), set(expected))
-
-
-if __name__ == '__main__':
- runmodule()
+++ /dev/null
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-
-from nose.tools import *
-from nose.core import runmodule
-
-import wlapi
-
-
-class FakeDocument():
-
- def __init__(self):
- self.text = "Some Text"
-
-
-class TestWLAPI(object):
-
- def setUp(self):
- self.api = wlapi.WLAPI(
- URL="http://localhost:7000/api/",
- AUTH_REALM="WL API",
- AUTH_USER="platforma",
- AUTH_PASSWD="platforma",
- )
-
- def test_basic_call(self):
- assert_equal(self.api.list_books(), [])
-
- def test_publish_book(self):
- self.api.publish_book(FakeDocument())
-
-if __name__ == '__main__':
- runmodule()
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-import os
-import tempfile
-import datetime
-import mimetypes
-import urllib
-import functools
-
-import logging
-logger = logging.getLogger('fnp.hazlenut.vstorage')
-
-# Note: we have to set these before importing Mercurial
-os.environ['HGENCODING'] = 'utf-8'
-os.environ['HGMERGE'] = "internal:merge"
-
-import mercurial.hg
-import mercurial.ui
-import mercurial.revlog
-import mercurial.util
-
-
-def urlquote(url, safe='/'):
- """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'
- """
- return urllib.quote(url.replace(' ', '_').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'
- """
- return unicode(urllib.unquote(url), 'utf-8', 'ignore').replace('_', ' ')
-
-
-def find_repo_path(path):
- """Go up the directory tree looking for a Mercurial repository (a directory containing a .hg subdirectory)."""
- while not os.path.isdir(os.path.join(path, ".hg")):
- old_path, path = path, os.path.dirname(path)
- if path == old_path:
- return None
- return path
-
-
-def with_working_copy_locked(func):
- """A decorator for locking the repository when calling a method."""
-
- @functools.wraps(func)
- def wrapped(self, *args, **kwargs):
- """Wrap the original function in locks."""
- wlock = self.repo.wlock()
- try:
- return func(self, *args, **kwargs)
- finally:
- wlock.release()
- return wrapped
-
-
-def with_storage_locked(func):
- """A decorator for locking the repository when calling a method."""
-
- @functools.wraps(func)
- def wrapped(self, *args, **kwargs):
- """Wrap the original function in locks."""
- lock = self.repo.lock()
- try:
- return func(self, *args, **kwargs)
- finally:
- lock.release()
- return wrapped
-
-
-def guess_mime(file_name):
- """
- Guess file's mime type based on extension.
- Default of text/x-wiki for files without an extension.
-
- >>> guess_mime('something.txt')
- 'text/plain'
- >>> guess_mime('SomePage')
- 'text/x-wiki'
- >>> guess_mime(u'ąęśUnicodePage')
- 'text/x-wiki'
- >>> guess_mime('image.png')
- 'image/png'
- >>> guess_mime('style.css')
- 'text/css'
- >>> guess_mime('archive.tar.gz')
- 'archive/gzip'
- """
-
- mime, encoding = mimetypes.guess_type(file_name, strict=False)
- if encoding:
- mime = 'archive/%s' % encoding
- if mime is None:
- mime = 'text/x-wiki'
- return mime
-
-
-class DocumentNotFound(Exception):
- pass
-
-
-class VersionedStorage(object):
- """
- Provides means of storing text pages and keeping track of their
- change history, using Mercurial repository as the storage method.
- """
-
- def __init__(self, path, charset=None):
- """
- Takes the path to the directory where the pages are to be kept.
- If the directory doen't exist, it will be created. If it's inside
- a Mercurial repository, that repository will be used, otherwise
- a new repository will be created in it.
- """
-
- self.charset = charset or 'utf-8'
- self.path = path
- if not os.path.exists(self.path):
- os.makedirs(self.path)
- self.repo_path = find_repo_path(self.path)
-
- self.ui = mercurial.ui.ui()
- self.ui.quiet = True
- self.ui._report_untrusted = False
- self.ui.setconfig('ui', 'interactive', False)
-
- if self.repo_path is None:
- self.repo_path = self.path
- create = True
- else:
- create = False
-
- self.repo_prefix = self.path[len(self.repo_path):].strip('/')
- self.repo = mercurial.hg.repository(self.ui, self.repo_path,
- create=create)
-
- def reopen(self):
- """Close and reopen the repo, to make sure we are up to date."""
- self.repo = mercurial.hg.repository(self.ui, self.repo_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 _file_to_title(self, filename):
- assert filename.startswith(self.repo_prefix)
- name = filename[len(self.repo_prefix):].strip('/')
- return urlunquote(name)
-
- def __contains__(self, title):
- return urlquote(title) in self.repo['tip']
-
- def __iter__(self):
- return self.all_pages()
-
- def merge_changes(self, changectx, repo_file, text, user, parent):
- """Commits and merges conflicting changes in the repository."""
- tip_node = changectx.node()
- filectx = changectx[repo_file].filectx(parent)
- parent_node = filectx.changectx().node()
-
- self.repo.dirstate.setparents(parent_node)
- node = self._commit([repo_file], text, user)
-
- partial = lambda filename: repo_file == filename
-
- # If p1 is equal to p2, there is no work to do. Even the dirstate is correct.
- p1, p2 = self.repo[None].parents()[0], self.repo[tip_node]
- if p1 == p2:
- return text
-
- try:
- mercurial.merge.update(self.repo, tip_node, True, False, partial)
- msg = 'merge of edit conflict'
- except mercurial.util.Abort:
- msg = 'failed merge of edit conflict'
-
- self.repo.dirstate.setparents(tip_node, node)
- # Mercurial 1.1 and later need updating the merge state
- try:
- mercurial.merge.mergestate(self.repo).mark(repo_file, "r")
- except (AttributeError, KeyError):
- pass
- return msg
-
- @with_working_copy_locked
- @with_storage_locked
- def save_file(self, title, file_name, **kwargs):
- """Save an existing file as specified page."""
-
- author = kwargs.get('author', u'anonymous').encode('utf-8')
- comment = kwargs.get('comment', u'Empty comment.').encode('utf-8')
- parent = kwargs.get('parent', None)
-
- repo_file = self._title_to_file(title)
- file_path = self._file_path(title)
- mercurial.util.rename(file_name, file_path)
- changectx = self._changectx()
-
- try:
- filectx_tip = changectx[repo_file]
- current_page_rev = filectx_tip.filerev()
- except mercurial.revlog.LookupError:
- self.repo.add([repo_file])
- current_page_rev = -1
-
- if parent is not None and current_page_rev != parent:
- msg = self.merge_changes(changectx, repo_file, comment, author, parent)
- author = '<wiki>'
- comment = msg.encode('utf-8')
-
- self._commit([repo_file], comment, author)
-
- def save_data(self, title, data, **kwargs):
- """Save data as specified page."""
- try:
- temp_path = tempfile.mkdtemp(dir=self.path)
- file_path = os.path.join(temp_path, 'saved')
- f = open(file_path, "wb")
- f.write(data)
- f.close()
-
- return self.save_file(title=title, file_name=file_path, **kwargs)
- finally:
- try:
- os.unlink(file_path)
- except OSError:
- pass
- try:
- os.rmdir(temp_path)
- except OSError:
- pass
-
- def save_text(self, **kwargs):
- """Save text as specified page, encoded to charset."""
- text = kwargs.pop('text')
- return self.save_data(data=text.encode(self.charset), **kwargs)
-
- def _commit(self, files, comment, user):
- match = mercurial.match.exact(self.repo_path, '', list(files))
- return self.repo.commit(match=match, text=comment, user=user, force=True)
-
- @with_working_copy_locked
- @with_storage_locked
- def delete_page(self, title, author=u'', comment=u''):
- user = author.encode('utf-8') or 'anon'
- text = comment.encode('utf-8') or 'deleted'
- repo_file = self._title_to_file(title)
- file_path = self._file_path(title)
- try:
- os.unlink(file_path)
- except OSError:
- pass
- self.repo.remove([repo_file])
- self._commit([repo_file], text, user)
-
- def page_text(self, title, revision=None):
- """Read unicode text of a page."""
- ctx = self._find_filectx(title, revision)
-
- if ctx is None:
- raise DocumentNotFound(title)
-
- return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
-
- def page_text_by_tag(self, title, tag):
- """Read unicode text of a taged page."""
- fname = self._title_to_file(title)
- tag = u"{fname}#{tag}".format(**locals()).encode('utf-8')
-
- try:
- ctx = self.repo[tag][fname]
- return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
- except IndexError:
- raise DocumentNotFound(fname)
-
- @with_working_copy_locked
- def page_file_meta(self, title):
- """Get page's inode number, size and last modification time."""
- try:
- (_st_mode, st_ino, _st_dev, _st_nlink, _st_uid, _st_gid, st_size,
- _st_atime, st_mtime, _st_ctime) = os.stat(self._file_path(title))
- except OSError:
- return 0, 0, 0
- return st_ino, st_size, st_mtime
-
- @with_working_copy_locked
- def page_meta(self, title, revision=None):
- """Get page's revision, date, last editor and his edit comment."""
- fctx = self._find_filectx(title, revision)
-
- if fctx is None:
- raise DocumentNotFound(title)
-
- return {
- "revision": fctx.filerev(),
- "date": datetime.datetime.fromtimestamp(fctx.date()[0]),
- "author": fctx.user().decode("utf-8", 'replace'),
- "comment": fctx.description().decode("utf-8", 'replace'),
- }
-
- def repo_revision(self):
- return self.repo['tip'].rev()
-
- def _changectx(self):
- return self.repo['tip']
-
- def page_mime(self, title):
- """
- Guess page's mime type based on corresponding file name.
- Default ot text/x-wiki for files without an extension.
- """
- return guess_mime(self._file_path(title))
-
- def _find_filectx(self, title, rev=None):
- """Find the last revision in which the file existed."""
- repo_file = self._title_to_file(title)
- changectx = self._changectx() # start with tip
- visited = set()
-
- stack = [changectx]
- visited.add(changectx)
-
- while repo_file not in changectx:
- if not stack:
- raise DocumentNotFound(title)
-
- changectx = stack.pop()
- for parent in changectx.parents():
- if parent not in visited:
- stack.append(parent)
- visited.add(parent)
-
- try:
- fctx = changectx[repo_file]
-
- if rev is not None:
- fctx = fctx.filectx(rev)
- fctx.filerev()
-
- return fctx
- except (IndexError, LookupError) as e:
- raise DocumentNotFound(title)
-
- def page_history(self, title):
- """Iterate over the page's history."""
-
- filectx_tip = self._find_filectx(title)
-
- maxrev = filectx_tip.filerev()
- minrev = 0
- for rev in range(maxrev, minrev - 1, -1):
- filectx = filectx_tip.filectx(rev)
- date = datetime.datetime.fromtimestamp(filectx.date()[0])
- author = filectx.user().decode('utf-8', 'replace')
- comment = filectx.description().decode("utf-8", 'replace')
- tags = [t.rsplit('#', 1)[-1] for t in filectx.changectx().tags() if '#' in t]
-
- yield {
- "version": rev,
- "date": date,
- "author": author,
- "description": comment,
- "tag": tags,
- }
-
- @with_working_copy_locked
- def add_page_tag(self, title, rev, tag, user, doctag=True):
- ctitle = self._title_to_file(title)
-
- if doctag:
- tag = u"{ctitle}#{tag}".format(**locals()).encode('utf-8')
-
- message = u"Assigned tag {tag!r} to version {rev!r} of {ctitle!r}".format(**locals()).encode('utf-8')
-
- fctx = self._find_filectx(title, rev)
- self.repo.tag(
- names=tag, node=fctx.node(), local=False,
- user=user, message=message, date=None,
- )
-
- def history(self):
- """Iterate over the history of entire wiki."""
-
- changectx = self._changectx()
- maxrev = changectx.rev()
- minrev = 0
- for wiki_rev in range(maxrev, minrev - 1, -1):
- change = self.repo.changectx(wiki_rev)
- date = datetime.datetime.fromtimestamp(change.date()[0])
- author = change.user().decode('utf-8', 'replace')
- comment = change.description().decode("utf-8", 'replace')
- for repo_file in change.files():
- if repo_file.startswith(self.repo_prefix):
- title = self._file_to_title(repo_file)
- try:
- rev = change[repo_file].filerev()
- except mercurial.revlog.LookupError:
- rev = -1
- yield title, rev, date, author, comment
-
- def all_pages(self):
- tip = self.repo['tip']
- """Iterate over the titles of all pages in the wiki."""
- return [urlunquote(filename) for filename in tip]
-
- def changed_since(self, rev):
- """Return all pages that changed since specified repository revision."""
-
- try:
- last = self.repo.lookup(int(rev))
- except IndexError:
- for page in self.all_pages():
- yield page
- return
- current = self.repo.lookup('tip')
- status = self.repo.status(current, last)
- modified, added, removed, deleted, unknown, ignored, clean = status
- for filename in modified + added + removed + deleted:
- if filename.startswith(self.repo_prefix):
- yield self._file_to_title(filename)
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import os
+import tempfile
+import datetime
+import mimetypes
+import urllib
+import functools
+
+import logging
+logger = logging.getLogger('fnp.hazlenut.vstorage')
+
+# Note: we have to set these before importing Mercurial
+os.environ['HGENCODING'] = 'utf-8'
+os.environ['HGMERGE'] = "internal:merge"
+
+import mercurial.hg
+import mercurial.ui
+import mercurial.revlog
+import mercurial.util
+
+
+def urlquote(url, safe='/'):
+ """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'
+ """
+ return urllib.quote(url.replace(' ', '_').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'
+ """
+ return unicode(urllib.unquote(url), 'utf-8', 'ignore').replace('_', ' ')
+
+
+def find_repo_path(path):
+ """Go up the directory tree looking for a Mercurial repository (a directory containing a .hg subdirectory)."""
+ while not os.path.isdir(os.path.join(path, ".hg")):
+ old_path, path = path, os.path.dirname(path)
+ if path == old_path:
+ return None
+ return path
+
+
+def with_working_copy_locked(func):
+ """A decorator for locking the repository when calling a method."""
+
+ @functools.wraps(func)
+ def wrapped(self, *args, **kwargs):
+ """Wrap the original function in locks."""
+ wlock = self.repo.wlock()
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ wlock.release()
+ return wrapped
+
+
+def with_storage_locked(func):
+ """A decorator for locking the repository when calling a method."""
+
+ @functools.wraps(func)
+ def wrapped(self, *args, **kwargs):
+ """Wrap the original function in locks."""
+ lock = self.repo.lock()
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ lock.release()
+ return wrapped
+
+
+def guess_mime(file_name):
+ """
+ Guess file's mime type based on extension.
+ Default of text/x-wiki for files without an extension.
+
+ >>> guess_mime('something.txt')
+ 'text/plain'
+ >>> guess_mime('SomePage')
+ 'text/x-wiki'
+ >>> guess_mime(u'ąęśUnicodePage')
+ 'text/x-wiki'
+ >>> guess_mime('image.png')
+ 'image/png'
+ >>> guess_mime('style.css')
+ 'text/css'
+ >>> guess_mime('archive.tar.gz')
+ 'archive/gzip'
+ """
+
+ mime, encoding = mimetypes.guess_type(file_name, strict=False)
+ if encoding:
+ mime = 'archive/%s' % encoding
+ if mime is None:
+ mime = 'text/x-wiki'
+ return mime
+
+
+class DocumentNotFound(Exception):
+ pass
+
+
+class VersionedStorage(object):
+ """
+ Provides means of storing text pages and keeping track of their
+ change history, using Mercurial repository as the storage method.
+ """
+
+ def __init__(self, path, charset=None):
+ """
+ Takes the path to the directory where the pages are to be kept.
+ If the directory doen't exist, it will be created. If it's inside
+ a Mercurial repository, that repository will be used, otherwise
+ a new repository will be created in it.
+ """
+
+ self.charset = charset or 'utf-8'
+ self.path = path
+ if not os.path.exists(self.path):
+ os.makedirs(self.path)
+ self.repo_path = find_repo_path(self.path)
+
+ self.ui = mercurial.ui.ui()
+ self.ui.quiet = True
+ self.ui._report_untrusted = False
+ self.ui.setconfig('ui', 'interactive', False)
+
+ if self.repo_path is None:
+ self.repo_path = self.path
+ create = True
+ else:
+ create = False
+
+ self.repo_prefix = self.path[len(self.repo_path):].strip('/')
+ self.repo = mercurial.hg.repository(self.ui, self.repo_path,
+ create=create)
+
+ def reopen(self):
+ """Close and reopen the repo, to make sure we are up to date."""
+ self.repo = mercurial.hg.repository(self.ui, self.repo_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 _file_to_title(self, filename):
+ assert filename.startswith(self.repo_prefix)
+ name = filename[len(self.repo_prefix):].strip('/')
+ return urlunquote(name)
+
+ def __contains__(self, title):
+ return urlquote(title) in self.repo['tip']
+
+ def __iter__(self):
+ return self.all_pages()
+
+ def merge_changes(self, changectx, repo_file, text, user, parent):
+ """Commits and merges conflicting changes in the repository."""
+ tip_node = changectx.node()
+ filectx = changectx[repo_file].filectx(parent)
+ parent_node = filectx.changectx().node()
+
+ self.repo.dirstate.setparents(parent_node)
+ node = self._commit([repo_file], text, user)
+
+ partial = lambda filename: repo_file == filename
+
+ # If p1 is equal to p2, there is no work to do. Even the dirstate is correct.
+ p1, p2 = self.repo[None].parents()[0], self.repo[tip_node]
+ if p1 == p2:
+ return text
+
+ try:
+ mercurial.merge.update(self.repo, tip_node, True, False, partial)
+ msg = 'merge of edit conflict'
+ except mercurial.util.Abort:
+ msg = 'failed merge of edit conflict'
+
+ self.repo.dirstate.setparents(tip_node, node)
+ # Mercurial 1.1 and later need updating the merge state
+ try:
+ mercurial.merge.mergestate(self.repo).mark(repo_file, "r")
+ except (AttributeError, KeyError):
+ pass
+ return msg
+
+ @with_working_copy_locked
+ @with_storage_locked
+ def save_file(self, title, file_name, **kwargs):
+ """Save an existing file as specified page."""
+
+ author = kwargs.get('author', u'anonymous').encode('utf-8')
+ comment = kwargs.get('comment', u'Empty comment.').encode('utf-8')
+ parent = kwargs.get('parent', None)
+
+ repo_file = self._title_to_file(title)
+ file_path = self._file_path(title)
+ mercurial.util.rename(file_name, file_path)
+ changectx = self._changectx()
+
+ try:
+ filectx_tip = changectx[repo_file]
+ current_page_rev = filectx_tip.filerev()
+ except mercurial.revlog.LookupError:
+ self.repo.add([repo_file])
+ current_page_rev = -1
+
+ if parent is not None and current_page_rev != parent:
+ msg = self.merge_changes(changectx, repo_file, comment, author, parent)
+ author = '<wiki>'
+ comment = msg.encode('utf-8')
+
+ self._commit([repo_file], comment, author)
+
+ def save_data(self, title, data, **kwargs):
+ """Save data as specified page."""
+ try:
+ temp_path = tempfile.mkdtemp(dir=self.path)
+ file_path = os.path.join(temp_path, 'saved')
+ f = open(file_path, "wb")
+ f.write(data)
+ f.close()
+
+ return self.save_file(title=title, file_name=file_path, **kwargs)
+ finally:
+ try:
+ os.unlink(file_path)
+ except OSError:
+ pass
+ try:
+ os.rmdir(temp_path)
+ except OSError:
+ pass
+
+ def save_text(self, **kwargs):
+ """Save text as specified page, encoded to charset."""
+ text = kwargs.pop('text')
+ return self.save_data(data=text.encode(self.charset), **kwargs)
+
+ def _commit(self, files, comment, user):
+ match = mercurial.match.exact(self.repo_path, '', list(files))
+ return self.repo.commit(match=match, text=comment, user=user, force=True)
+
+ @with_working_copy_locked
+ @with_storage_locked
+ def delete_page(self, title, author=u'', comment=u''):
+ user = author.encode('utf-8') or 'anon'
+ text = comment.encode('utf-8') or 'deleted'
+ repo_file = self._title_to_file(title)
+ file_path = self._file_path(title)
+ try:
+ os.unlink(file_path)
+ except OSError:
+ pass
+ self.repo.remove([repo_file])
+ self._commit([repo_file], text, user)
+
+ def page_text(self, title, revision=None):
+ """Read unicode text of a page."""
+ ctx = self._find_filectx(title, revision)
+
+ if ctx is None:
+ raise DocumentNotFound(title)
+
+ return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
+
+ def page_text_by_tag(self, title, tag):
+ """Read unicode text of a taged page."""
+ fname = self._title_to_file(title)
+ tag = u"{fname}#{tag}".format(**locals()).encode('utf-8')
+
+ try:
+ ctx = self.repo[tag][fname]
+ return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
+ except IndexError:
+ raise DocumentNotFound(fname)
+
+ @with_working_copy_locked
+ def page_file_meta(self, title):
+ """Get page's inode number, size and last modification time."""
+ try:
+ (_st_mode, st_ino, _st_dev, _st_nlink, _st_uid, _st_gid, st_size,
+ _st_atime, st_mtime, _st_ctime) = os.stat(self._file_path(title))
+ except OSError:
+ return 0, 0, 0
+ return st_ino, st_size, st_mtime
+
+ @with_working_copy_locked
+ def page_meta(self, title, revision=None):
+ """Get page's revision, date, last editor and his edit comment."""
+ fctx = self._find_filectx(title, revision)
+
+ if fctx is None:
+ raise DocumentNotFound(title)
+
+ return {
+ "revision": fctx.filerev(),
+ "date": datetime.datetime.fromtimestamp(fctx.date()[0]),
+ "author": fctx.user().decode("utf-8", 'replace'),
+ "comment": fctx.description().decode("utf-8", 'replace'),
+ }
+
+ def repo_revision(self):
+ return self.repo['tip'].rev()
+
+ def _changectx(self):
+ return self.repo['tip']
+
+ def page_mime(self, title):
+ """
+ Guess page's mime type based on corresponding file name.
+ Default ot text/x-wiki for files without an extension.
+ """
+ return guess_mime(self._file_path(title))
+
+ def _find_filectx(self, title, rev=None):
+ """Find the last revision in which the file existed."""
+ repo_file = self._title_to_file(title)
+ changectx = self._changectx() # start with tip
+ visited = set()
+
+ stack = [changectx]
+ visited.add(changectx)
+
+ while repo_file not in changectx:
+ if not stack:
+ raise DocumentNotFound(title)
+
+ changectx = stack.pop()
+ for parent in changectx.parents():
+ if parent not in visited:
+ stack.append(parent)
+ visited.add(parent)
+
+ try:
+ fctx = changectx[repo_file]
+
+ if rev is not None:
+ fctx = fctx.filectx(rev)
+ fctx.filerev()
+
+ return fctx
+ except (IndexError, LookupError) as e:
+ raise DocumentNotFound(title)
+
+ def page_history(self, title):
+ """Iterate over the page's history."""
+
+ filectx_tip = self._find_filectx(title)
+
+ maxrev = filectx_tip.filerev()
+ minrev = 0
+ for rev in range(maxrev, minrev - 1, -1):
+ filectx = filectx_tip.filectx(rev)
+ date = datetime.datetime.fromtimestamp(filectx.date()[0])
+ author = filectx.user().decode('utf-8', 'replace')
+ comment = filectx.description().decode("utf-8", 'replace')
+ tags = [t.rsplit('#', 1)[-1] for t in filectx.changectx().tags() if '#' in t]
+
+ yield {
+ "version": rev,
+ "date": date,
+ "author": author,
+ "description": comment,
+ "tag": tags,
+ }
+
+ @with_working_copy_locked
+ def add_page_tag(self, title, rev, tag, user, doctag=True):
+ ctitle = self._title_to_file(title)
+
+ if doctag:
+ tag = u"{ctitle}#{tag}".format(**locals()).encode('utf-8')
+
+ message = u"Assigned tag {tag!r} to version {rev!r} of {ctitle!r}".format(**locals()).encode('utf-8')
+
+ fctx = self._find_filectx(title, rev)
+ self.repo.tag(
+ names=tag, node=fctx.node(), local=False,
+ user=user, message=message, date=None,
+ )
+
+ def history(self):
+ """Iterate over the history of entire wiki."""
+
+ changectx = self._changectx()
+ maxrev = changectx.rev()
+ minrev = 0
+ for wiki_rev in range(maxrev, minrev - 1, -1):
+ change = self.repo.changectx(wiki_rev)
+ date = datetime.datetime.fromtimestamp(change.date()[0])
+ author = change.user().decode('utf-8', 'replace')
+ comment = change.description().decode("utf-8", 'replace')
+ for repo_file in change.files():
+ if repo_file.startswith(self.repo_prefix):
+ title = self._file_to_title(repo_file)
+ try:
+ rev = change[repo_file].filerev()
+ except mercurial.revlog.LookupError:
+ rev = -1
+ yield title, rev, date, author, comment
+
+ def all_pages(self):
+ tip = self.repo['tip']
+ """Iterate over the titles of all pages in the wiki."""
+ return [urlunquote(filename) for filename in tip]
+
+ def changed_since(self, rev):
+ """Return all pages that changed since specified repository revision."""
+
+ try:
+ last = self.repo.lookup(int(rev))
+ except IndexError:
+ for page in self.all_pages():
+ yield page
+ return
+ current = self.repo.lookup('tip')
+ status = self.repo.status(current, last)
+ modified, added, removed, deleted, unknown, ignored, clean = status
+ for filename in modified + added + removed + deleted:
+ if filename.startswith(self.repo_prefix):
+ yield self._file_to_title(filename)
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+
+import os
+import tempfile
+from nose.tools import *
+from nose.core import runmodule
+
+import vstorage
+
+NULL_PARENT = -1
+
+
+def clear_directory(top):
+ for root, dirs, files in os.walk(top, topdown=False):
+ for name in files:
+ os.remove(os.path.join(root, name))
+ for name in dirs:
+ os.rmdir(os.path.join(root, name))
+ try:
+ os.removedirs(top)
+ except OSError:
+ pass
+
+
+class TestVersionedStorage(object):
+ def setUp(self):
+ self.repo_path = tempfile.mkdtemp()
+ self.repo = vstorage.VersionedStorage(self.repo_path)
+
+ def tearDown(self):
+ clear_directory(self.repo_path)
+
+ def test_save_text(self):
+ text = u"test text"
+ title = u"test title"
+ author = u"test author"
+ comment = u"test comment"
+
+ self.repo.save_text(
+ title=title,
+ text=text,
+ author=author,
+ comment=comment,
+ parent=NULL_PARENT,
+ )
+
+ saved = self.repo.open_page(title).read()
+ assert_equal(saved, text)
+
+ def test_save_text_noparent(self):
+ text = u"test text"
+ title = u"test title"
+ author = u"test author"
+ comment = u"test comment"
+
+ self.repo.save_text(title=title,
+ text=text, author=author,
+ comment=comment, parent=None)
+
+ saved = self.repo.open_page(title).read()
+ assert_equal(saved, text)
+
+ def test_save_merge_no_conflict(self):
+ text = u"test\ntext"
+ title = u"test title"
+ author = u"test author"
+ comment = u"test comment"
+ self.repo.save_text(title=title,
+ text=text, author=author,
+ comment=comment, parent=NULL_PARENT)
+ self.repo.save_text(title=title,
+ text=text, author=author,
+ comment=comment, parent=NULL_PARENT)
+ saved = self.repo.open_page(title).read()
+ assert_equal(saved, text)
+
+ def test_save_merge_line_conflict(self):
+ text = u"test\ntest\n"
+ text1 = u"test\ntext\n"
+ text2 = u"text\ntest\n"
+ title = u"test title"
+ author = u"test author"
+ comment = u"test comment"
+
+ self.repo.save_text(title=title,
+ text=text, author=author,
+ comment=comment, parent=NULL_PARENT)
+
+ self.repo.save_text(title=title,
+ text=text1, author=author,
+ comment=comment, parent=0)
+
+ self.repo.save_text(title=title,
+ text=text2, author=author,
+ comment=comment, parent=0)
+
+ saved = self.repo.open_page(title).read()
+
+ # Other conflict markers placement can also be correct
+ assert_equal(saved, u'''\
+text
+test
+<<<<<<< local
+=======
+text
+>>>>>>> other
+''')
+
+ def test_delete(self):
+ text = u"text test"
+ title = u"test title"
+ author = u"test author"
+ comment = u"test comment"
+ self.repo.save_text(title=title,
+ text=text, author=author,
+ comment=comment, parent=NULL_PARENT)
+
+ assert title in self.repo
+
+ self.repo.delete_page(title, author, comment)
+
+ assert title not in self.repo
+
+ @raises(vstorage.DocumentNotFound)
+ def test_document_not_found(self):
+ self.repo.open_page(u'unknown entity')
+
+ def test_open_existing_repository(self):
+ self.repo.save_text(title=u'Python!', text=u'ham and spam')
+ current_repo_revision = self.repo.repo_revision()
+ same_repo = vstorage.VersionedStorage(self.repo_path)
+ assert_equal(same_repo.repo_revision(), current_repo_revision)
+
+ def test_history(self):
+ COMMITS = [
+ {"author": "bunny", "text":"1", "comment": "Oh yeah!"},
+ {"author": "frank", "text":"2", "comment": "Second is the best!"},
+ {"text":"3", "comment": "Third"},
+ {"author": "welma", "text":"4", "comment": "Fourth"},
+ ]
+
+ for commit in COMMITS:
+ self.repo.save_text(title=u"Sample", **commit)
+
+ for n, entry in enumerate(reversed(list(self.repo.page_history(u"Sample")))):
+ assert_equal(entry["version"], n)
+ assert_equal(entry["author"], COMMITS[n].get("author", "anonymous"))
+ assert_equal(entry["description"], COMMITS[n]["comment"])
+ assert_equal(entry["tag"], [])
+
+
+class TestVSTags(object):
+
+ TITLE_1 = "Sample"
+
+ COMMITS = [
+ {"author": "bunny", "text":"1", "comment": "Oh yeah!"},
+ {"author": "frank", "text":"2", "comment": "Second is the best!"},
+ {"text":"3", "comment": "Third"},
+ {"author": "welma", "text":"4", "comment": "Fourth"},
+ ]
+
+ def setUp(self):
+ self.repo_path = tempfile.mkdtemp()
+ self.repo = vstorage.VersionedStorage(self.repo_path)
+
+ # generate some history
+ for commit in self.COMMITS:
+ self.repo.save_text(title=u"Sample", **commit)
+
+ # verify
+ for n, entry in enumerate(reversed(list(self.repo.page_history(self.TITLE_1)))):
+ assert_equal(entry["tag"], [])
+
+ def tearDown(self):
+ clear_directory(self.repo_path)
+
+ def test_add_tag(self):
+ TAG_USER = "mike_the_tagger"
+ TAG_NAME = "production"
+ TAG_VERSION = 2
+
+ # Add tag
+ self.repo.add_page_tag(self.TITLE_1, TAG_VERSION, TAG_NAME, TAG_USER)
+
+ # check history again
+ history = list(self.repo.page_history(self.TITLE_1))
+ for entry in reversed(history):
+ if entry["version"] == TAG_VERSION:
+ assert_equal(entry["tag"], [TAG_NAME])
+ else:
+ assert_equal(entry["tag"], [])
+
+ def test_add_many_tags(self):
+ TAG_USER = "mike_the_tagger"
+ tags = [
+ (2, "production", "mike"),
+ (2, "finished", "jeremy"),
+ (0, "original", "jeremy"),
+ ]
+
+ for rev, name, user in tags:
+ self.repo.add_page_tag(self.TITLE_1, rev, name, user)
+
+ # check history again
+ history = list(self.repo.page_history(self.TITLE_1))
+ for entry in reversed(history):
+ expected = [tag[1] for tag in tags if tag[0] == entry["version"]]
+ assert_equal(set(entry["tag"]), set(expected))
+
+
+if __name__ == '__main__':
+ runmodule()
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-"""
- Abstraction over API for wolnelektury.pl
-"""
-import urllib2
-import functools
-import django.utils.simplejson as json
-import logging
-logger = logging.getLogger("fnp.lib.wlapi")
-
-
-class APICallException(Exception):
-
- def __init__(self, cause=None):
- super(Exception, self).__init__()
- self.cause = cause
-
- def __unicode__(self):
- return u"%s, cause: %s" % (type(self).__name__, repr(self.cause))
-
- def __str__(self):
- return self.__unicode__().encode('utf-8')
-
-
-def api_call(path, format="json"):
- def wrapper(func):
-
- @functools.wraps(func)
- def wrapped(self, *args, **kwargs):
- generator = func(self, *args, **kwargs)
-
- data = generator.next()
-
- # prepare request
- rq = urllib2.Request(self.base_url + path + ".json")
-
- # will send POST when there is data, GET otherwise
- if data is not None:
- rq.add_data(json.dumps(data))
- rq.add_header("Content-Type", "application/json")
-
- try:
- anwser = json.load(self.opener.open(rq))
- return generator.send(anwser)
- except StopIteration:
- # by default, just return the anwser as a shorthand
- return anwser
- except urllib2.HTTPError, error:
- return self._http_error(error)
- except Exception, error:
- return self._error(error)
- return wrapped
-
- return wrapper
-
-
-class WLAPI(object):
-
- def __init__(self, **config_dict):
- self.base_url = config_dict['URL']
- self.auth_realm = config_dict['AUTH_REALM']
- self.auth_user = config_dict['AUTH_USER']
-
- digest_handler = urllib2.HTTPDigestAuthHandler()
- digest_handler.add_password(
- realm=self.auth_realm, uri=self.base_url,
- user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
-
- basic_handler = urllib2.HTTPBasicAuthHandler()
- basic_handler.add_password(
- realm=self.auth_realm, uri=self.base_url,
- user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
-
- self.opener = urllib2.build_opener(digest_handler, basic_handler)
-
- def _http_error(self, error):
- message = error.read()
- logger.debug("HTTP ERROR: %s", message)
- return self._error(message)
-
- def _error(self, error):
- raise APICallException(error)
-
- @api_call("books")
- def list_books(self):
- yield
-
- @api_call("books")
- def publish_book(self, document):
- yield {"text": document.text, "compressed": False}
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""
+ Abstraction over API for wolnelektury.pl
+"""
+import urllib2
+import functools
+import django.utils.simplejson as json
+import logging
+logger = logging.getLogger("fnp.lib.wlapi")
+
+
+class APICallException(Exception):
+
+ def __init__(self, cause=None):
+ super(Exception, self).__init__()
+ self.cause = cause
+
+ def __unicode__(self):
+ return u"%s, cause: %s" % (type(self).__name__, repr(self.cause))
+
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
+
+
+def api_call(path, format="json"):
+ def wrapper(func):
+
+ @functools.wraps(func)
+ def wrapped(self, *args, **kwargs):
+ generator = func(self, *args, **kwargs)
+
+ data = generator.next()
+
+ # prepare request
+ rq = urllib2.Request(self.base_url + path + ".json")
+
+ # will send POST when there is data, GET otherwise
+ if data is not None:
+ rq.add_data(json.dumps(data))
+ rq.add_header("Content-Type", "application/json")
+
+ try:
+ anwser = json.load(self.opener.open(rq))
+ return generator.send(anwser)
+ except StopIteration:
+ # by default, just return the anwser as a shorthand
+ return anwser
+ except urllib2.HTTPError, error:
+ return self._http_error(error)
+ except Exception, error:
+ return self._error(error)
+ return wrapped
+
+ return wrapper
+
+
+class WLAPI(object):
+
+ def __init__(self, **config_dict):
+ self.base_url = config_dict['URL']
+ self.auth_realm = config_dict['AUTH_REALM']
+ self.auth_user = config_dict['AUTH_USER']
+
+ digest_handler = urllib2.HTTPDigestAuthHandler()
+ digest_handler.add_password(
+ realm=self.auth_realm, uri=self.base_url,
+ user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
+
+ basic_handler = urllib2.HTTPBasicAuthHandler()
+ basic_handler.add_password(
+ realm=self.auth_realm, uri=self.base_url,
+ user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
+
+ self.opener = urllib2.build_opener(digest_handler, basic_handler)
+
+ def _http_error(self, error):
+ message = error.read()
+ logger.debug("HTTP ERROR: %s", message)
+ return self._error(message)
+
+ def _error(self, error):
+ raise APICallException(error)
+
+ @api_call("books")
+ def list_books(self):
+ yield
+
+ @api_call("books")
+ def publish_book(self, document):
+ yield {"text": document.text, "compressed": False}
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+
+from nose.tools import *
+from nose.core import runmodule
+
+import wlapi
+
+
+class FakeDocument():
+
+ def __init__(self):
+ self.text = "Some Text"
+
+
+class TestWLAPI(object):
+
+ def setUp(self):
+ self.api = wlapi.WLAPI(
+ URL="http://localhost:7000/api/",
+ AUTH_REALM="WL API",
+ AUTH_USER="platforma",
+ AUTH_PASSWD="platforma",
+ )
+
+ def test_basic_call(self):
+ assert_equal(self.api.list_books(), [])
+
+ def test_publish_book(self):
+ self.api.publish_book(FakeDocument())
+
+if __name__ == '__main__':
+ runmodule()
Alias /media $MEDIA_ROOT
<Directory $MEDIA_ROOT >
- Options Indexes, FollowLinks
Order allow,deny
Allow from all
</Directory>
+++ /dev/null
-# -*- coding: utf-8 -*-
-import os.path
-
-PROJECT_ROOT = os.path.realpath(os.path.dirname(__file__))
-
-DEBUG = False
-TEMPLATE_DEBUG = DEBUG
-
-MAINTENANCE_MODE = False
-
-ADMINS = (
- # (u'Marek Stępniowski', 'marek@stepniowski.com'),
- (u'Łukasz Rekucki', 'lrekucki@gmail.com'),
-)
-
-MANAGERS = ADMINS
-
-DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-DATABASE_NAME = PROJECT_ROOT + '/dev.sqlite' # Or path to database file if using sqlite3.
-DATABASE_USER = '' # Not used with sqlite3.
-DATABASE_PASSWORD = '' # Not used with sqlite3.
-DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
-DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = 'Europe/Warsaw'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'pl'
-
-#import locale
-#locale.setlocale(locale.LC_ALL, '')
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# Absolute path to the directory that holds media.
-# Example: "/home/media/media.lawrence.com/"
-MEDIA_ROOT = PROJECT_ROOT + '/media/dynamic'
-STATIC_ROOT = PROJECT_ROOT + '/static/'
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash if there is a path component (optional in other cases).
-# Examples: "http://media.lawrence.com", "http://example.com/media/"
-MEDIA_URL = '/media/dynamic/'
-STATIC_URL = '/media/static/'
-
-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
-# trailing slash.
-# Examples: "http://foo.com/media/", "/media/".
-ADMIN_MEDIA_PREFIX = '/media/admin-media/'
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = 'ife@x^_lak+x84=lxtr!-ur$5g$+s6xt85gbbm@e_fk6q3r8=+'
-SESSION_COOKIE_NAME = "redakcja_sessionid"
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
-)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- "django.core.context_processors.auth",
- "django.core.context_processors.debug",
- "django.core.context_processors.i18n",
- "redakcja.context_processors.settings", # this is instead of media
- "django.core.context_processors.request",
-)
-
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
-
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django_cas.middleware.CASMiddleware',
-
- 'django.middleware.doc.XViewMiddleware',
- 'maintenancemode.middleware.MaintenanceModeMiddleware',
-)
-
-AUTHENTICATION_BACKENDS = (
- 'django.contrib.auth.backends.ModelBackend',
- 'django_cas.backends.CASBackend',
-)
-
-ROOT_URLCONF = 'redakcja.urls'
-
-TEMPLATE_DIRS = (
- PROJECT_ROOT + '/templates',
-)
-
-FIREPYTHON_LOGGER_NAME = "fnp"
-
-#
-# Central Auth System
-#
-## Set this to where the CAS server lives
-# CAS_SERVER_URL = "http://cas.fnp.pl/
-CAS_LOGOUT_COMPLETELY = True
-
-from compress_settings import *
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.admin',
- 'django.contrib.admindocs',
-
- 'django_cas',
- 'compress',
- 'south',
- 'sorl.thumbnail',
- 'filebrowser',
-
- 'wiki',
- 'toolbar',
-)
-
-
-#
-# Nose tests
-#
-
-TEST_RUNNER = 'django_nose.run_tests'
-TEST_MODULES = ('wiki', 'toolbar', 'vstorage')
-NOSE_ARGS = (
- '--tests=' + ','.join(TEST_MODULES),
- '--cover-package=' + ','.join(TEST_MODULES),
- '-d',
- '--with-coverage',
- '--with-doctest',
-)
-
-
-FILEBROWSER_URL_FILEBROWSER_MEDIA = STATIC_URL + 'filebrowser/'
-FILEBROWSER_DIRECTORY = 'images/'
-FILEBROWSER_ADMIN_VERSIONS = []
-FILEBROWSER_VERSIONS_BASEDIR = 'thumbnails/'
-FILEBROWSER_DEFAULT_ORDER = "path_relative"
-
-# REPOSITORY_PATH = '/Users/zuber/Projekty/platforma/files/books'
-IMAGE_DIR = 'images'
-
-
-WL_API_CONFIG = {
- "URL": "http://localhost:7000/api/",
- "AUTH_REALM": "WL API",
- "AUTH_USER": "platforma",
- "AUTH_PASSWD": "platforma",
-}
-
-# Import localsettings file, which may override settings defined here
-try:
- from localsettings import *
-except ImportError:
- pass
-
-try:
- LOGGING_CONFIG_FILE
-except NameError:
- LOGGING_CONFIG_FILE = os.path.join(PROJECT_ROOT, 'config',
- ('logging.cfg' if not DEBUG else 'logging.cfg.dev'))
-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 ImportError as exc:
- raise
--- /dev/null
+from __future__ import absolute_import
+from .common import *
+
+DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = PROJECT_ROOT + '/dev.sqlite' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+try:
+ from localsettings import *
+except ImportError:
+ pass
+
+try:
+ LOGGING_CONFIG_FILE
+except NameError:
+ LOGGING_CONFIG_FILE = os.path.join(PROJECT_ROOT, 'config',
+ ('logging.cfg' if not DEBUG else 'logging.cfg.dev'))
+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 ImportError as exc:
+ raise
+
--- /dev/null
+# -*- coding: utf-8 -*-
+import os.path
+
+PROJECT_ROOT = os.path.realpath(os.path.dirname(__file__))
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+MAINTENANCE_MODE = False
+
+ADMINS = (
+ # (u'Marek Stępniowski', 'marek@stepniowski.com'),
+ (u'Łukasz Rekucki', 'lrekucki@gmail.com'),
+)
+
+MANAGERS = ADMINS
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/Warsaw'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'pl'
+
+#import locale
+#locale.setlocale(locale.LC_ALL, '')
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = PROJECT_ROOT + '/media/dynamic'
+STATIC_ROOT = PROJECT_ROOT + '/static/'
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/media/dynamic/'
+STATIC_URL = '/media/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/admin-media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'ife@x^_lak+x84=lxtr!-ur$5g$+s6xt85gbbm@e_fk6q3r8=+'
+SESSION_COOKIE_NAME = "redakcja_sessionid"
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ "django.core.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "redakcja.context_processors.settings", # this is instead of media
+ "django.core.context_processors.request",
+)
+
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django_cas.middleware.CASMiddleware',
+
+ 'django.middleware.doc.XViewMiddleware',
+ 'maintenancemode.middleware.MaintenanceModeMiddleware',
+)
+
+AUTHENTICATION_BACKENDS = (
+ 'django.contrib.auth.backends.ModelBackend',
+ 'django_cas.backends.CASBackend',
+)
+
+ROOT_URLCONF = 'redakcja.urls'
+
+TEMPLATE_DIRS = (
+ PROJECT_ROOT + '/templates',
+)
+
+FIREPYTHON_LOGGER_NAME = "fnp"
+
+#
+# Central Auth System
+#
+## Set this to where the CAS server lives
+# CAS_SERVER_URL = "http://cas.fnp.pl/
+CAS_LOGOUT_COMPLETELY = True
+
+from compress_settings import *
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'django.contrib.admindocs',
+
+ 'django_cas',
+ 'compress',
+ 'south',
+ 'sorl.thumbnail',
+ 'filebrowser',
+
+ 'wiki',
+ 'toolbar',
+)
+
+FILEBROWSER_URL_FILEBROWSER_MEDIA = STATIC_URL + 'filebrowser/'
+FILEBROWSER_DIRECTORY = 'images/'
+FILEBROWSER_ADMIN_VERSIONS = []
+FILEBROWSER_VERSIONS_BASEDIR = 'thumbnails/'
+FILEBROWSER_DEFAULT_ORDER = "path_relative"
+
+# REPOSITORY_PATH = '/Users/zuber/Projekty/platforma/files/books'
+IMAGE_DIR = 'images'
+
+
+WL_API_CONFIG = {
+ "URL": "http://localhost:7000/api/",
+ "AUTH_REALM": "WL API",
+ "AUTH_USER": "platforma",
+ "AUTH_PASSWD": "platforma",
+}
+
+# Import localsettings file, which may override settings defined here
--- /dev/null
+#
+# Nose tests
+#
+
+from redakcja.settings.common import *
+
+# ROOT_URLCONF = 'yourapp.settings.test.urls'
+
+DATABASE_ENGINE = 'sqlite3'
+DATABASE_NAME = ':memory:'
+
+import tempfile
+
+WIKI_REPOSITORY_PATH = tempfile.mkdtemp(prefix='wikirepo')
+
+INSTALLED_APPS += ('django_nose',)
+
+TEST_RUNNER = 'django_nose.run_tests'
+TEST_MODULES = ('wiki', 'toolbar', 'vstorage')
+NOSE_ARGS = (
+ '--tests=' + ','.join(TEST_MODULES),
+ '--cover-package=' + ','.join(TEST_MODULES),
+ '-d',
+ '--with-coverage',
+ '--with-doctest',
+ '--with-xunit',
+ '--with-xcoverage',
+)
failure: noop
};
/*
- * Return absolute reverse path of given named view.
- * (at least he have it hard-coded in one place)
- *
+ * Return absolute reverse path of given named view. (at least he have it
+ * hard-coded in one place)
+ *
* TODO: think of a way, not to hard-code it here ;)
- *
+ *
*/
function reverse() {
var vname = arguments[0];
};
/*
* Fetch history of this document.
- *
- * from - First revision to fetch (default = 0)
- * upto - Last revision to fetch (default = tip)
- *
+ *
+ * from - First revision to fetch (default = 0) upto - Last revision to
+ * fetch (default = tip)
+ *
*/
WikiDocument.prototype.fetchHistory = function(params) {
/* this doesn't modify anything, so no locks */
# migrations
south>=0.6
-
-## Debugging utils, uncomment this if you want tests
-# django-nose>=0.0.3
-# django-debug-toolbar>=0.8
\ No newline at end of file