+
+ {% else %}
+
+
+
+
{% trans "New Page" %}
+
{% trans "Delete Page" %}
+
{% trans "Edit Page" %}
+
{% trans "Page History" %}
+
+
+
+
{{ page|capfirst }}
+
+
{% blocktrans with modifier=latest_contributor|email2nickname modify_time=last_modified|translate_seahub_time %}Last modified by {{modifier}}, {{modify_time}}{% endblocktrans %}
+
+
+
+ {% endif %}
+{% endblock main_panel %}
+
+{% block extra_script %}
+
+
+
+{% endblock %}
diff --git a/templates/wiki/personal_wiki_pages.html b/templates/wiki/personal_wiki_pages.html
new file mode 100644
index 0000000000..a5b4a248ff
--- /dev/null
+++ b/templates/wiki/personal_wiki_pages.html
@@ -0,0 +1,53 @@
+{% extends "myhome_base.html" %}
+
+{% load seahub_tags avatar_tags group_avatar_tags i18n %}
+{% load url from future %}
+
+{% block sub_title %}{% trans "Personal Wiki" %}{% endblock %}
+{% block nav_myhome_class %}class="cur"{% endblock %}
+
+{% block title_panel %}
+
+
+
+{% endblock %}
+
+{% block main_panel %}
+
+
+
+
{% trans "New Page" %}
+
+
+
+ {% for page_slug, page in pages.items %}
+ {{ page }}
+ {% endfor %}
+
+
+
+ {% trans "New Page"%}
+ {% trans "Name"%}
+
+
+
+
+
+{% endblock main_panel %}
+
+{% block extra_script %}
+
+{% endblock %}
diff --git a/urls.py b/urls.py
index f566387e37..4bb4f8c748 100644
--- a/urls.py
+++ b/urls.py
@@ -9,6 +9,9 @@ from seahub.views.repo import RepoView, RepoHistoryView
from seahub.views.search import search
from notifications.views import notification_list
from group.views import group_list
+from seahub.views.wiki import personal_wiki, personal_wiki_pages, \
+ personal_wiki_create, personal_wiki_page_new, personal_wiki_page_edit, \
+ personal_wiki_page_delete
# Uncomment the next two lines to enable the admin:
#from django.contrib import admin
@@ -30,6 +33,14 @@ urlpatterns = patterns('',
(r'^$', root),
#url(r'^home/$', direct_to_template, { 'template': 'home.html' } ),
url(r'^home/my/$', myhome, name='myhome'),
+ url(r'^home/wiki/$', personal_wiki, name='personal_wiki'),
+ url(r'^home/wiki/(?P
[^/]+)/$', personal_wiki, name='personal_wiki'),
+ url(r'^home/wiki_pages/$', personal_wiki_pages, name='personal_wiki_pages'),
+ url(r'^home/wiki_create/$', personal_wiki_create, name='personal_wiki_create'),
+ url(r'^home/wiki_page_new/$', personal_wiki_page_new, name='personal_wiki_page_new'),
+ url(r'^home/wiki_page_edit/(?P[^/]+)$', personal_wiki_page_edit, name='personal_wiki_page_edit'),
+ url(r'^home/wiki_page_delete/(?P[^/]+)$', personal_wiki_page_delete, name='personal_wiki_page_delete'),
+
url(r'^home/public/reply/(?P[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'),
url(r'^home/owner/(?P[^/]+)/$', ownerhome, name='ownerhome'),
diff --git a/views/wiki.py b/views/wiki.py
new file mode 100644
index 0000000000..970065d1a1
--- /dev/null
+++ b/views/wiki.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+"""
+File related views, including view_file, edit_file, view_history_file,
+view_trash_file, view_snapshot_file
+"""
+
+import os
+import simplejson as json
+import stat
+import tempfile
+import urllib
+import urllib2
+import chardet
+
+from django.contrib.sites.models import Site, RequestSite
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse, HttpResponseBadRequest, Http404, \
+ HttpResponseRedirect
+from django.shortcuts import render_to_response, redirect
+from django.template import Context, loader, RequestContext
+from django.template.loader import render_to_string
+from django.utils.hashcompat import md5_constructor
+from django.utils.http import urlquote
+from django.utils.translation import ugettext as _
+
+from auth.decorators import login_required
+
+import seaserv
+from pysearpc import SearpcError
+
+
+from seahub.wiki.models import PersonalWiki, WikiDoesNotExist, WikiPageMissing
+from seahub.wiki import get_personal_wiki_page, get_personal_wiki_repo, \
+ convert_wiki_link, get_wiki_pages
+from seahub.wiki.forms import WikiCreateForm, WikiNewPageForm
+from seahub.utils import get_file_contributors, render_error
+
+@login_required
+def personal_wiki(request, page_name="home"):
+ username = request.user.username
+ wiki_exists = True
+ try:
+ content, repo, dirent = get_personal_wiki_page(username, page_name)
+ except WikiDoesNotExist:
+ wiki_exists = False
+ return render_to_response("wiki/personal_wiki.html", {
+ "wiki_exists": wiki_exists,
+ }, context_instance=RequestContext(request))
+ except WikiPageMissing:
+ repo = get_personal_wiki_repo(username)
+ filename = clean_page_name(page_name) + '.md'
+ if not post_empty_file(repo.id, "/", filename, username):
+ return render_error(request, _("Failed to create wiki page. Please retry later."))
+ return HttpResponseRedirect(reverse('personal_wiki', args=[page_name]))
+ else:
+ url_prefix = reverse('personal_wiki', args=[])
+ content = convert_wiki_link(content, url_prefix, repo.id, username)
+
+ # fetch file latest contributor and last modified
+ path = '/' + dirent.obj_name
+ file_path_hash = md5_constructor(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12]
+ contributors, last_modified, last_commit_id = get_file_contributors(\
+ repo.id, path.encode('utf-8'), file_path_hash, dirent.obj_id)
+ latest_contributor = contributors[0] if contributors else None
+
+ return render_to_response("wiki/personal_wiki.html", {
+ "wiki_exists": wiki_exists,
+ "content": content,
+ "page": os.path.splitext(dirent.obj_name)[0],
+ "last_modified": last_modified,
+ "latest_contributor": latest_contributor,
+ "path": path,
+ "repo_id": repo.id,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def personal_wiki_pages(request):
+ """
+ List personal wiki pages.
+ """
+ try:
+ repo = get_personal_wiki_repo(request.user.username)
+ pages = get_wiki_pages(repo)
+ except SearpcError:
+ return render_error(request, _('Internal Server Error'))
+ except WikiDoesNotExist:
+ return render_error(request, _('Wiki does not exists.'))
+
+ return render_to_response("wiki/personal_wiki_pages.html", {
+ "pages": pages,
+ "repo_id": repo.id
+ }, context_instance=RequestContext(request))
+
+
+@login_required
+def personal_wiki_create(request):
+ if request.method != 'POST':
+ raise Http404
+
+ content_type = 'application/json; charset=utf-8'
+
+ def json_error(err_msg, status=400):
+ result = {'error': err_msg}
+ return HttpResponse(json.dumps(result), status=status,
+ content_type=content_type)
+
+ form = WikiCreateForm(request.POST)
+ if not form.is_valid():
+ return json_error(str(form.errors.values()[0]))
+
+ # create group repo in user context
+ repo_name = form.cleaned_data['repo_name']
+ repo_desc = form.cleaned_data['repo_desc']
+ username = request.user.username
+ passwd = None
+ permission = "rw"
+
+ repo_id = seaserv.create_repo(repo_name, repo_desc, username, passwd)
+ if not repo_id:
+ return json_error(_(u'Failed to create'), 500)
+
+ PersonalWiki.objects.save_personal_wiki(username=username, repo_id=repo_id)
+
+ # create home page
+ page_name = "home.md"
+ if not seaserv.post_empty_file(repo_id, "/", page_name, username):
+ return json_error(_(u'Failed to create home page. Please retry later'), 500)
+
+ next = reverse('personal_wiki', args=[])
+ return HttpResponse(json.dumps({'href': next}), content_type=content_type)
+
+@login_required
+def personal_wiki_page_new(request, page_name="home"):
+
+ if request.method == 'POST':
+ page_name = request.POST.get('page_name', '')
+ if not page_name:
+ return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
+ page_name = clean_page_name(page_name)
+
+ repo = find_wiki_repo(request, group)
+ if not repo:
+ return render_error(request, _('Wiki is not found.'))
+
+ filename = page_name + ".md"
+ filepath = "/" + page_name + ".md"
+
+ # check whether file exists
+ if get_file_id_by_path(repo.id, filepath):
+ return render_error(request, _('Page "%s" already exists.') % filename)
+
+ if not post_empty_file(repo.id, "/", filename, request.user.username):
+ return render_error(request, _('Failed to create wiki page. Please retry later.'))
+
+ url = "%s?p=%s&from=wiki_page_new&gid=%s" % (
+ reverse('file_edit', args=[repo.id]),
+ urllib2.quote(filepath.encode('utf-8')), group.id)
+ return HttpResponseRedirect(url)
+
+
+@login_required
+def personal_wiki_page_edit(request, page_name="home"):
+ try:
+ repo = get_personal_wiki_repo(request.user.username)
+ except WikiDoesNotExist:
+ return render_error(request, _('Wiki is not found.'))
+
+ filepath = "/" + page_name + ".md"
+ url = "%s?p=%s&from=personal_wiki_page_edit" % (
+ reverse('file_edit', args=[repo.id]),
+ urllib2.quote(filepath.encode('utf-8')))
+
+ return HttpResponseRedirect(url)
+
+
+@login_required
+def personal_wiki_page_delete(request, page_name):
+ try:
+ repo = get_personal_wiki_repo(request.user.username)
+ except WikiDoesNotExist:
+ return render_error(request, _('Wiki is not found.'))
+
+ file_name = page_name + '.md'
+ username = request.user.username
+ if del_file(repo.id, '/', file_name, username):
+ messages.success(request, 'Successfully deleted "%s".' % page_name)
+ else:
+ messages.error(request, 'Failed to delete "%s". Please retry later.' % page_name)
+
+ return HttpResponseRedirect(reverse('personal_wiki', args=[]))
diff --git a/wiki/__init__.py b/wiki/__init__.py
new file mode 100644
index 0000000000..10ded49623
--- /dev/null
+++ b/wiki/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from utils import get_personal_wiki_page, get_personal_wiki_repo, \
+ convert_wiki_link, get_wiki_pages
diff --git a/wiki/forms.py b/wiki/forms.py
new file mode 100644
index 0000000000..8834b89245
--- /dev/null
+++ b/wiki/forms.py
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+from django import forms
+from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+from seaserv import is_valid_filename
+
+from utils import clean_page_name
+
+class WikiCreateForm(forms.Form):
+ """
+ A form used to create wiki.
+ """
+ repo_name = forms.CharField(max_length=settings.MAX_FILE_NAME,
+ error_messages={
+ 'required': _(u'Name can\'t be empty'),
+ 'max_length': _(u'Name is too long (maximum is 255 characters)')
+ })
+ repo_desc = forms.CharField(max_length=100, error_messages={
+ 'required': _(u'Description can\'t be empty'),
+ 'max_length': _(u'Description is too long (maximum is 100 characters)')
+ })
+
+ def clean_repo_name(self):
+ repo_name = self.cleaned_data['repo_name']
+ if not is_valid_filename(repo_name):
+ error_msg = _(u'"%s" is not a valid name') % repo_name
+ raise forms.ValidationError(error_msg)
+ else:
+ return repo_name
+
+
+class WikiNewPageForm(forms.Form):
+ page_name = forms.CharField(max_length=500)
+
+ def clean_page_name(self):
+ page_name = self.cleaned_data['page_name']
diff --git a/wiki/models.py b/wiki/models.py
new file mode 100644
index 0000000000..67c7a803ff
--- /dev/null
+++ b/wiki/models.py
@@ -0,0 +1,25 @@
+from django.db import models
+
+class WikiDoesNotExist(Exception):
+ pass
+
+class WikiPageMissing(Exception):
+ pass
+
+class PersonalWikiManager(models.Manager):
+ def save_personal_wiki(self, username, repo_id):
+ """
+ Create or update group wiki.
+ """
+ try:
+ wiki = self.get(username=username)
+ wiki.repo_id = repo_id
+ except self.model.DoesNotExist:
+ wiki = self.model(username=username, repo_id=repo_id)
+ wiki.save(using=self._db)
+ return wiki
+
+class PersonalWiki(models.Model):
+ username = models.CharField(max_length=256, unique=True)
+ repo_id = models.CharField(max_length=36)
+ objects = PersonalWikiManager()
diff --git a/wiki/utils.py b/wiki/utils.py
new file mode 100644
index 0000000000..f4fd03a889
--- /dev/null
+++ b/wiki/utils.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+import os
+import stat
+import urllib2
+
+import seaserv
+from pysearpc import SearpcError
+from seahub.utils import EMPTY_SHA1
+from seahub.utils.repo import list_dir_by_path
+from seahub.utils.slugify import slugify
+from seahub.utils import render_error, render_permission_error, string2list, \
+ gen_file_get_url, get_file_type_and_ext, \
+ get_file_contributors
+
+from models import WikiPageMissing, WikiDoesNotExist, \
+ PersonalWiki
+
+__all__ = ["get_wiki_dirent"]
+
+
+
+SLUG_OK = "!@#$%^&()_+-,.;'"
+def normalize_page_name(page_name):
+ # Remove special characters. Lower page name and replace spaces with '-'.
+ return slugify(page_name, ok=SLUG_OK)
+
+def clean_page_name(page_name):
+ # Remove special characters. Do not lower page name and spaces are allowed.
+ return slugify(page_name, ok=SLUG_OK, lower=False, spaces=True)
+
+def get_wiki_dirent(repo_id, page_name):
+ file_name = page_name + ".md"
+ repo = seaserv.get_repo(repo_id)
+ if not repo:
+ raise WikiDoesNotExist
+ cmmt = seaserv.get_commits(repo.id, 0, 1)[0]
+ if cmmt is None:
+ raise WikiPageMissing
+ dirs = list_dir_by_path(cmmt, "/")
+ if not dirs:
+ raise WikiPageMissing
+ else:
+ for e in dirs:
+ if stat.S_ISDIR(e.mode):
+ continue # skip directories
+ if normalize_page_name(file_name) == normalize_page_name(e.obj_name):
+ return e
+ raise WikiPageMissing
+
+def get_file_url(repo, obj_id, file_name):
+ repo_id = repo.id
+ access_token = seaserv.seafserv_rpc.web_get_access_token(repo_id, obj_id,
+ 'view', '')
+ url = gen_file_get_url(access_token, file_name)
+ return url
+
+def get_wiki_page(request, page_name):
+ repo = find_wiki_repo(request, group)
+ dirent = get_wiki_dirent(repo.id, page_name)
+ if not dirent:
+ raise WikiPageMissing
+ url = get_file_url(repo, dirent.obj_id, dirent.obj_name)
+ file_response = urllib2.urlopen(url)
+ content = file_response.read()
+ return content, repo.id, dirent
+
+def get_personal_wiki_repo(username):
+ try:
+ wiki = PersonalWiki.objects.get(username=username)
+ except PersonalWiki.DoesNotExist:
+ raise WikiDoesNotExist
+ repo = seaserv.get_repo(wiki.repo_id)
+ if not repo:
+ raise WikiDoesNotExist
+ return repo
+
+def get_personal_wiki_page(username, page_name):
+ repo = get_personal_wiki_repo(username)
+ dirent = get_wiki_dirent(repo.id, page_name)
+ url = get_file_url(repo, dirent.obj_id, dirent.obj_name)
+ file_response = urllib2.urlopen(url)
+ content = file_response.read()
+ return content, repo, dirent
+
+def get_wiki_pages(repo):
+ """
+ return pages in hashtable {normalized_name: page_name}
+ """
+ dir_id = seaserv.seafserv_threaded_rpc.get_dir_id_by_path(repo.id, '/')
+ dirs = seaserv.seafserv_threaded_rpc.list_dir(dir_id)
+ pages = {}
+ for e in dirs:
+ if stat.S_ISDIR(e.mode):
+ continue # skip directories
+ name, ext = os.path.splitext(e.obj_name)
+ if ext == '.md':
+ key = normalize_page_name(name)
+ pages[key] = name
+ return pages
+
+
+def convert_wiki_link(content, url_prefix, repo_id, username):
+ import re
+
+ def repl(matchobj):
+ if matchobj.group(2): # return origin string in backquotes
+ return matchobj.group(2)
+
+ page_name = matchobj.group(1).strip()
+ filetype, fileext = get_file_type_and_ext(page_name)
+ if fileext == '':
+ # convert page_name that extension is missing to a markdown page
+ dirent = get_wiki_dirent(repo_id, page_name)
+ if dirent is not None:
+ a_tag = "%s "
+ return a_tag % (url_prefix + '/' + normalize_page_name(page_name), page_name)
+ else:
+ a_tag = '''%s '''
+ return a_tag % (url_prefix + '/' + page_name.replace('/', '-'), page_name)
+ elif filetype == IMAGE:
+ # load image to wiki page
+ path = "/" + page_name
+ filename = os.path.basename(path)
+ obj_id = get_file_id_by_path(repo_id, path)
+ if not obj_id:
+ # Replace '/' in page_name to '-', since wiki name can not
+ # contain '/'.
+ return '''%s ''' % \
+ (url_prefix + '/' + page_name.replace('/', '-'), page_name)
+
+ token = seaserv.web_get_access_token(repo_id, obj_id, 'view', username)
+ return ' ' % (gen_file_get_url(token, filename), filename)
+ else:
+ from base.templatetags.seahub_tags import file_icon_filter
+
+ # convert other types of filelinks to clickable links
+ path = "/" + page_name
+ icon = file_icon_filter(page_name)
+ s = reverse('repo_view_file', args=[repo_id]) + \
+ '?p=' + urllib2.quote(smart_str(path))
+ a_tag = ''' %s '''
+ return a_tag % (MEDIA_URL, icon, icon, s, page_name)
+
+ return re.sub(r'\[\[(.+)\]\]|(`.+`)', repl, content)
+
+