--- a/ForgeWiki/forgewiki/main.py
+++ b/ForgeWiki/forgewiki/main.py
@@ -1,5 +1,7 @@
#-*- python -*-
+import difflib
import logging
+from pprint import pformat
# Non-stdlib imports
import pkg_resources
@@ -22,12 +24,16 @@
log = logging.getLogger(__name__)
+# Will not be needed after _dispatch is fixed in tg 2.1
+from pyforge.lib.dispatch import _dispatch
+
class ForgeWikiApp(Application):
+ '''This is the Wiki app for PyForge'''
__version__ = version.__version__
- permissions = ['configure', 'read', 'write', 'comment']
+ permissions = [ 'configure', 'read', 'create', 'edit', 'delete', 'comment' ]
config_options = Application.config_options + [
- ConfigOption('some_str_config_option', str, 'some_str_config_option'),
- ConfigOption('some_int_config_option', int, 42),
+ ConfigOption('project_name', str, 'pname'),
+ ConfigOption('message', str, 'Custom message goes here'),
]
def __init__(self, project, config):
@@ -46,16 +52,20 @@
@property
def sitemap(self):
- menu_id = 'ForgeWiki (%s)' % self.config.options.mount_point
+ menu_id = 'ForgeWiki (%s)' % self.config.options.mount_point
with push_config(c, app=self):
+ pages = [
+ SitemapEntry(p.title, p.url())
+ for p in model.Page.m.find(dict(
+ app_config_id=self.config._id)) ]
return [
- SitemapEntry(menu_id, '.')[self.sidebar_menu()] ]
+ SitemapEntry(menu_id, '.')[SitemapEntry('Pages')[pages]] ]
def sidebar_menu(self):
return [
- SitemapEntry('Home', '.'),
- SitemapEntry('Search', 'search'),
- ]
+ SitemapEntry(p.title, p.url())
+ for p in model.Page.m.find(dict(
+ app_config_id=self.config._id)) ]
@property
def templates(self):
@@ -64,35 +74,49 @@
def install(self, project):
'Set up any default permissions and roles here'
+ self.config.options['project_name'] = project._id
self.uninstall(project)
# Give the installing user all the permissions
pr = c.user.project_role()
for perm in self.permissions:
self.config.acl[perm] = [ pr._id ]
self.config.acl['read'].append(
- ProjectRole.m.get(name='*anonymous')._id)
+ ProjectRole.m.get(name='*anonymous')._id)
self.config.acl['comment'].append(
ProjectRole.m.get(name='*authenticated')._id)
self.config.m.save()
- art = model.MyArtifact.make(dict(
- text='This is a sample artifact'))
- art.commit()
+ p = model.Page.upsert('Root')
+ p.text = 'This is the root page.'
+ p.commit()
def uninstall(self, project):
"Remove all the plugin's artifacts from the database"
- pass
+ model.Page.m.remove(dict(project_id=c.project._id))
+ model.Comment.m.remove(dict(project_id=c.project._id))
class RootController(object):
@expose('forgewiki.templates.index')
def index(self):
- return dict()
-
+ return dict(message=c.app.config.options['message'])
+
+ #Will not be needed after _dispatch is fixed in tg 2.1
+ def _dispatch(self, state, remainder):
+ return _dispatch(self, state, remainder)
+
+ #Instantiate a Page object, and continue dispatch there
+ def _lookup(self, pname, *remainder):
+ return PageController(pname), remainder
+
+ @expose()
+ def new_page(self, title):
+ redirect(title + '/')
+
@expose('forgewiki.templates.search')
@validate(dict(q=validators.UnicodeString(if_empty=None),
history=validators.StringBool(if_empty=False)))
def search(self, q=None, history=None):
- 'local plugin search'
+ 'local wiki search'
results = []
count=0
if not q:
@@ -100,63 +124,112 @@
else:
search_query = '''%s
AND is_history_b:%s
+ AND project_id_s:%s
AND mount_point_s:%s''' % (
- q, history, c.app.config.options.mount_point)
- results = search(search_query)
+ q, history, c.project._id, c.app.config.options.mount_point)
+ results = search.search(search_query)
if results: count=results.hits
return dict(q=q, history=history, results=results or [], count=count)
- def _lookup(self, id, *remainder):
- return ArtifactController(id), remainder
-
-class ArtifactController(object):
-
- def __init__(self, id):
- self.artifact = model.MyArtifact.m.get(_id=ObjectId.url_decode(id))
- self.comments = CommentController(self.artifact)
-
- @expose('forgewiki.templates.artifact')
+class PageController(object):
+
+ def __init__(self, title):
+ self.title = title
+ self.page = model.Page.upsert(title)
+ self.comments = CommentController(self.page)
+
+ def get_version(self, version):
+ if not version: return self.page
+ try:
+ return model.Page.upsert(self.title, version=int(version))
+ except ValueError:
+ return None
+
+ @expose('forgewiki.templates.page_view')
@validate(dict(version=validators.Int()))
def index(self, version=None):
- require(has_artifact_access('read', self.artifact))
- artifact = self.get_version(version)
- if artifact is None:
- if version:
- redirect('.?version=%d' % (version-1))
- elif version <= 0:
- redirect('.')
- else:
- redirect('..')
- cur = artifact.version
+ require(has_artifact_access('read', self.page))
+ page = self.get_version(version)
+ if page is None:
+ if version: redirect('.?version=%d' % (version-1))
+ else: redirect('.')
+ cur = page.version - 1
if cur > 0: prev = cur-1
else: prev = None
next = cur+1
- return dict(artifact=artifact,
+ return dict(page=page,
cur=cur, prev=prev, next=next)
- def get_version(self, version):
- if not version: return self.artifact
- ss = model.MyArtifactHistory.m.get(artifact_id=self.artifact._id,
- version=version)
- if ss is None: return None
- result = deepcopy(self.artifact)
- return result.update(ss.data)
-
+ @expose('helloforge.templates.page_edit')
+ def edit(self):
+ if self.page.version == 1:
+ require(has_artifact_access('create', self.page))
+ else:
+ require(has_artifact_access('edit', self.page))
+ return dict(page=self.page)
+
+ @expose('helloforge.templates.page_history')
+ def history(self):
+ require(has_artifact_access('read', self.page))
+ pages = self.page.history()
+ return dict(title=self.title, pages=pages)
+
+ @expose('helloforge.templates.page_diff')
+ def diff(self, v1, v2):
+ require(has_artifact_access('read', self.page))
+ p1 = self.get_version(int(v1))
+ p2 = self.get_version(int(v2))
+ p1.version -= 1
+ p2.version -= 1
+ t1 = p1.text
+ t2 = p2.text
+ differ = difflib.SequenceMatcher(None, p1.text, p2.text)
+ result = []
+ for tag, i1, i2, j1, j2 in differ.get_opcodes():
+ if tag in ('delete', 'replace'):
+ result += [ '<del>', t1[i1:i2], '</del>' ]
+ if tag in ('insert', 'replace'):
+ result += [ '<ins>', t2[j1:j2], '</ins>' ]
+ if tag == 'equal':
+ result += t1[i1:i2]
+ result = ''.join(result).replace('\n', '<br/>\n')
+ return dict(p1=p1, p2=p2, edits=result)
+
+ @expose(content_type='text/plain')
+ def raw(self):
+ require(has_artifact_access('read', self.page))
+ return pformat(self.page)
+
+ @expose()
+ def revert(self, version):
+ require(has_artifact_access('edit', self.page))
+ orig = self.get_version(version)
+ self.page.text = orig.text
+ self.page.commit()
+ redirect('.')
+
+ @expose()
+ def update(self, text):
+ require(has_artifact_access('edit', self.page))
+ self.page.text = text
+ self.page.commit()
+ redirect('.')
+
class CommentController(object):
- def __init__(self, artifact, comment_id=None):
- self.artifact = artifact
+ def __init__(self, page, comment_id=None):
+ self.page = page
self.comment_id = comment_id
- self.comment = model.MyArtifactComment.m.get(_id=self.comment_id)
+ self.comment = model.Comment.m.get(_id=self.comment_id)
@expose()
def reply(self, text):
- require(has_artifact_access('comment', self.artifact))
+ require(has_artifact_access('comment', self.page))
if self.comment_id:
c = self.comment.reply()
c.text = text
else:
- c = self.artifact.reply()
+ c = self.page.reply()
c.text = text
c.m.save()
redirect(request.referer)
@@ -167,14 +240,14 @@
self.comment.m.delete()
redirect(request.referer)
+ def _dispatch(self, state, remainder):
+ return _dispatch(self, state, remainder)
+
def _lookup(self, next, *remainder):
if self.comment_id:
return CommentController(
- self.artifact,
+ self.page,
self.comment_id + '/' + next), remainder
else:
return CommentController(
- self.artifact, next), remainder
-
-
-
+ self.page, next), remainder