__date__ = "$2009-09-20 21:34:52$"
__doc__ = "Micro-forms for the API."
-
from django import forms
+from api.models import PullRequest
+from django.contrib.auth.models import User
+
+import re
+from django.utils import simplejson as json
class MergeRequestForm(forms.Form):
# should the target document revision be updated or shared
type = forms.ChoiceField(choices=(('update', 'Update'), ('share', 'Share')) )
-
- # which revision to update/share
- target_revision = forms.RegexField('[0-9a-f]{40}')
+
+ #
+ # if type == update:
+ # * user's revision which is the base of the merge
+ # if type == share:
+ # * revision which will be pulled to the main branch
+ #
+ # NOTE: the revision doesn't have to be owned by the user
+ # who requests the merge:
+ # a) Every user can update his branch
+ # b) Some users can push their changes
+ # -> if they can't, they leave a PRQ
+ # c) Some users can pull other people's changes
+ # d) Some users can update branches owned by special
+ # users associated with PRQ's
+ revision = forms.RegexField('[0-9a-f]{40}')
# any additional comments that user wants to add to the change
message = forms.CharField(required=False)
raise forms.ValidationError(
"You must either provide file descriptor or raw data." )
- return clean_data
\ No newline at end of file
+ return clean_data
+
+PRQ_USER_RE = re.compile(r"^\$prq-(\d{1,10})$", re.UNICODE)
+
+class DocumentRetrieveForm(forms.Form):
+ revision = forms.RegexField(regex=r'latest|[0-9a-z]{40}', required=False)
+ user = forms.CharField(required=False)
+
+ def clean_user(self):
+ # why, oh why does django doesn't implement this!!!
+ # value = super(DocumentRetrieveForm, self).clean_user()
+ value = self.cleaned_data['user']
+
+ if value.startswith('$'):
+ # library user (... maybe later)
+ if value == '$library':
+ raise forms.ValidationError("Invalid user name '%s'" % value)
+
+ m = PRQ_USER_RE.match(value)
+
+ if m:
+ try:
+ return value
+ except:
+ raise forms.ValidationError("User doesn't exist.")
+ raise forms.ValidationError("Invalid user name '%s'" % value)
+ try:
+ return value
+ except:
+ raise forms.ValidationError("User doesn't exist.")
+
+
+class TextRetrieveForm(DocumentRetrieveForm):
+ part = forms.CharField(required=False)
+
+class TextUpdateForm(DocumentRetrieveForm):
+ message = forms.CharField(required=False)
+ contents = forms.CharField(required=False)
+ chunks = forms.CharField(required=False)
+
+ def clean_message(self):
+ value = self.cleaned_data['message']
+
+ if value:
+ return u"$USER$ " + request.POST['message']
+ else:
+ return u"$AUTO$ XML content update."
+
+ def clean_chunks(self):
+ value = self.cleaned_data['chunks']
+
+ try:
+ return json.loads(value)
+ except Exception, e:
+ forms.ValidationError("Invalid JSON: " + e.message)
+
+
+ def clean(self):
+ if self.cleaned_data['contents'] and self.cleaned_data['chunks']:
+ raise forms.ValidationError("Pass either contents or chunks - not both ")
+
+ if not self.cleaned_data['contents'] and not self.cleaned_data['chunks']:
+ raise forms.ValidationError("You must pass contents or chunks.")
+
+ return self.cleaned_data
\ No newline at end of file
# -*- encoding: utf-8 -*-
import os.path
+
import logging
+log = logging.getLogger('platforma.api.library')
__author__= "Łukasz Rekucki"
__date__ = "$2009-09-25 15:49:50$"
from librarian import dcparser, parser
from wlrepo import *
-from explorer.models import PullRequest, GalleryForDocument
+from api.models import PullRequest
+from explorer.models import GalleryForDocument
# internal imports
import api.forms as forms
import settings
-log = logging.getLogger('platforma.api')
+def is_prq(username):
+ return username.startswith('$prq-')
+def check_user(request, user):
+ log.info("user: %r, perm: %r" % (request.user, request.user.get_all_permissions()) )
+ #pull request
+ if is_prq(user):
+ if not request.user.has_perm('api.pullrequest.can_view'):
+ yield response.AccessDenied().django_response({
+ 'reason': 'access-denied',
+ 'message': "You don't have enough priviliges to view pull requests."
+ })
+ # other users
+ elif request.user.username != user:
+ if not request.user.has_perm('api.document.can_view_other'):
+ yield response.AccessDenied().django_response({
+ 'reason': 'access-denied',
+ 'message': "You don't have enough priviliges to view other people's document."
+ })
+ pass
#
# Document List Handlers
document_list = [{
'url': reverse('document_view', args=[docid]),
'name': docid } for docid in lib.documents() ]
-
return {'documents' : document_list}
-
#
# This handler controlls the document collection
#
allowed_methods = ('GET', 'POST')
anonymous = BasicLibraryHandler
-
@hglibrary
def read(self, request, lib):
"""Return the list of documents."""
return {'documents': sorted(document_tree.itervalues(),
key=natural_order(lambda d: d['name']) ) }
+
@validate_form(forms.DocumentUploadForm, 'POST')
@hglibrary
def create(self, request, form, lib):
allowed_methods = ('GET', 'PUT')
anonymous = BasicDocumentHandler
+ @validate_form(forms.DocumentRetrieveForm, 'GET')
@hglibrary
- def read(self, request, docid, lib):
+ def read(self, request, form, docid, lib):
"""Read document's meta data"""
- log.info(u"Read %s (%s)" % (docid, type(docid)) )
- try:
- doc = lib.document(docid)
- udoc = doc.take(request.user.username)
- except RevisionNotFound, e:
- return response.EntityNotFound().django_response({
- 'exception': type(e), 'message': e.message,
- 'docid': docid })
+ log.info(u"User '%s' wants to %s(%s) as %s" % \
+ (request.user.username, docid, form.cleaned_data['revision'], form.cleaned_data['user']) )
- # is_shared = udoc.ancestorof(doc)
- # is_uptodate = is_shared or shared.ancestorof(document)
+ user = form.cleaned_data['user'] or request.user.username
+ rev = form.cleaned_data['revision'] or 'latest'
- result = {
- 'name': udoc.id,
- 'html_url': reverse('dochtml_view', args=[udoc.id]),
- 'text_url': reverse('doctext_view', args=[udoc.id]),
- 'dc_url': reverse('docdc_view', args=[udoc.id]),
- 'gallery_url': reverse('docgallery_view', args=[udoc.id]),
- 'merge_url': reverse('docmerge_view', args=[udoc.id]),
- 'user_revision': udoc.revision,
- 'user_timestamp': udoc.revision.timestamp,
- 'public_revision': doc.revision,
- 'public_timestamp': doc.revision.timestamp,
- }
+ for error in check_user(request, user):
+ return error
+
+ try:
+ doc = lib.document(docid, user, rev=rev)
+ except RevisionMismatch, e:
+ # the document exists, but the revision is bad
+ return response.EntityNotFound().django_response({
+ 'reason': 'revision-mismatch',
+ 'message': e.message,
+ 'docid': docid,
+ 'user': user,
+ })
+ except RevisionNotFound, e:
+ # the user doesn't have this document checked out
+ # or some other weird error occured
+ # try to do the checkout
+ if is_prq(user) or (user == request.user.username):
+ try:
+ mdoc = lib.document(docid)
+ doc = mdoc.take(user)
+
+ if is_prq(user):
+ # source revision, should probably change
+ # but there are no changes yet, so...
+ pass
+
+ except RevisionNotFound, e:
+ return response.EntityNotFound().django_response({
+ 'reason': 'document-not-found',
+ 'message': e.message,
+ 'docid': docid
+ })
+ else:
+ return response.EntityNotFound().django_response({
+ 'reason': 'document-not-found',
+ 'message': e.message,
+ 'docid': docid,
+ 'user': user,
+ })
- return result
+ return {
+ 'name': doc.id,
+ 'user': user,
+ 'html_url': reverse('dochtml_view', args=[doc.id]),
+ 'text_url': reverse('doctext_view', args=[doc.id]),
+ # 'dc_url': reverse('docdc_view', args=[doc.id]),
+ 'gallery_url': reverse('docgallery_view', args=[doc.id]),
+ 'merge_url': reverse('docmerge_view', args=[doc.id]),
+ 'user_revision': doc.revision,
+ 'user_timestamp': doc.revision.timestamp,
+ # 'public_revision': doc.revision,
+ # 'public_timestamp': doc.revision.timestamp,
+ }
- @hglibrary
- def update(self, request, docid, lib):
- """Update information about the document, like display not"""
- return
+
+# @hglibrary
+# def update(self, request, docid, lib):
+# """Update information about the document, like display not"""
+# return
#
#
#
class DocumentHTMLHandler(BaseHandler):
allowed_methods = ('GET')
+ @validate_form(forms.DocumentRetrieveForm, 'GET')
@hglibrary
- def read(self, request, docid, lib, stylesheet='partial'):
+ def read(self, request, form, docid, lib, stylesheet='partial'):
"""Read document as html text"""
try:
- revision = request.GET.get('revision', 'latest')
-
- if revision == 'latest':
- document = lib.document(docid)
- else:
- document = lib.document_for_rev(revision)
+ revision = form.cleaned_data['revision']
+ user = form.cleaned_data['user'] or request.user.username
+ document = lib.document_for_rev(revision)
if document.id != docid:
- return response.BadRequest().django_response({'reason': 'name-mismatch',
- 'message': 'Provided revision refers, to document "%s", but provided "%s"' % (document.id, docid) })
+ return response.BadRequest().django_response({
+ 'reason': 'name-mismatch',
+ 'message': 'Provided revision is not valid for this document'
+ })
+
+ if document.owner != user:
+ return response.BadRequest().django_response({
+ 'reason': 'user-mismatch',
+ 'message': "Provided revision doesn't belong to user %s" % user
+ })
+
+ for error in check_user(request, user):
+ return error
return librarian.html.transform(document.data('xml'), is_file=False, \
parse_dublincore=False, stylesheet=stylesheet,\
gallery = {'name': assoc.name, 'pages': []}
- for file in os.listdir(dirpath):
+ for file in sorted(os.listdir(dirpath)):
if not isinstance(file, unicode):
- log.warn(u"File %r is gallery %r is not unicode. Ommiting."\
- % (file, dirpath) )
- continue
-
- name, ext = os.path.splitext(os.path.basename(file))
+ try:
+ file = file.decode('utf-8')
+ except:
+ log.warn(u"File %r in gallery %r is not unicode. Ommiting."\
+ % (file, dirpath) )
+ file = None
- if ext.lower() not in [u'.png', u'.jpeg', u'.jpg']:
- log.info(u"Ignoring: %s %s", name, ext)
- continue
+ if file is not None:
+ name, ext = os.path.splitext(os.path.basename(file))
- url = settings.MEDIA_URL + assoc.subpath + u'/' + file;
+ if ext.lower() not in [u'.png', u'.jpeg', u'.jpg']:
+ log.warn(u"Ignoring: %s %s", name, ext)
+ url = None
+
+ url = settings.MEDIA_URL + assoc.subpath + u'/' + file
+
+ if url is None:
+ url = settings.MEDIA_URL + u'/missing.png'
+
gallery['pages'].append( quote(url.encode('utf-8')) )
- gallery['pages'].sort()
+# gallery['pages'].sort()
galleries.append(gallery)
return galleries
class DocumentTextHandler(BaseHandler):
allowed_methods = ('GET', 'POST')
+ @validate_form(forms.TextRetrieveForm, 'GET')
@hglibrary
- def read(self, request, docid, lib):
- """Read document as raw text"""
- revision = request.GET.get('revision', 'latest')
- part = request.GET.get('part', False)
-
+ def read(self, request, form, docid, lib):
+ """Read document as raw text"""
try:
- if revision == 'latest':
- document = lib.document(docid)
- else:
- document = lib.document_for_rev(revision)
-
+ revision = form.cleaned_data['revision']
+ part = form.cleaned_data['part']
+ user = form.cleaned_data['user'] or request.user.username
+
+ document = lib.document_for_rev(revision)
+
if document.id != docid:
- return response.BadRequest().django_response({'reason': 'name-mismatch',
- 'message': 'Provided revision is not valid for this document'})
+ return response.BadRequest().django_response({
+ 'reason': 'name-mismatch',
+ 'message': 'Provided revision is not valid for this document'
+ })
+
+ if document.owner != user:
+ return response.BadRequest().django_response({
+ 'reason': 'user-mismatch',
+ 'message': "Provided revision doesn't belong to user %s" % user
+ })
+
+ for error in check_user(request, user):
+ return error
- # TODO: some finer-grained access control
- if part is False:
- # we're done :)
+ if not part:
return document.data('xml')
- else:
- xdoc = parser.WLDocument.from_string(document.data('xml'),\
- parse_dublincore=False)
- ptext = xdoc.part_as_text(part)
+
+ xdoc = parser.WLDocument.from_string(document.data('xml'),\
+ parse_dublincore=False)
+ ptext = xdoc.part_as_text(part)
- if ptext is None:
- return response.EntityNotFound().django_response({
+ if ptext is None:
+ return response.EntityNotFound().django_response({
'reason': 'no-part-in-document'
- })
+ })
- return ptext
- except librarian.ParseError:
+ return ptext
+ except librarian.ParseError, e:
return response.EntityNotFound().django_response({
'reason': 'invalid-document-state',
- 'exception': type(e), 'message': e.message
+ 'exception': type(e),
+ 'message': e.message
})
except (EntryNotFound, RevisionNotFound), e:
return response.EntityNotFound().django_response({
'exception': type(e), 'message': e.message
})
+ @validate_form(forms.TextUpdateForm, 'POST')
@hglibrary
- def create(self, request, docid, lib):
+ def create(self, request, form, docid, lib):
try:
- revision = request.POST['revision']
+ revision = form.cleaned_data['revision']
+ msg = form.cleaned_data['message']
+ user = form.cleaned_data['user'] or request.user.username
- current = lib.document(docid, request.user.username)
+ # do not allow changing not owned documents
+ # (for now... )
+
+
+ if user != request.user.username:
+ return response.AccessDenied().django_response({
+ 'reason': 'insufficient-priviliges',
+ })
+
+ current = lib.document(docid, user)
orig = lib.document_for_rev(revision)
if current != orig:
"reason": "out-of-date",
"provided_revision": orig.revision,
"latest_revision": current.revision })
-
- if request.POST.has_key('message'):
- msg = u"$USER$ " + request.POST['message']
- else:
- msg = u"$AUTO$ XML content update."
-
- if request.POST.has_key('contents'):
- data = request.POST['contents']
- else:
- if not request.POST.has_key('chunks'):
- # bad request
- return response.BadRequest().django_response({'reason': 'invalid-arguments',
- 'message': 'No contents nor chunks specified.'})
-
- # TODO: validate
- parts = json.loads(request.POST['chunks'])
+
+ if form.cleaned_data.has_key('contents'):
+ data = form.cleaned_data['contents']
+ else:
+ chunks = form.cleaned_data['chunks']
xdoc = parser.WLDocument.from_string(current.data('xml'))
-
- errors = xdoc.merge_chunks(parts)
+ errors = xdoc.merge_chunks(chunks)
if len(errors):
return response.EntityConflict().django_response({
ndoc = None
ndoc = current.invoke_and_commit(\
- xml_update_action, lambda d: (msg, current.owner) )
+ xml_update_action, lambda d: (msg, user) )
try:
# return the new revision number
return response.SuccessAllOk().django_response({
"document": ndoc.id,
+ "user": user,
"subview": "xml",
"previous_revision": current.revision,
"revision": ndoc.revision,
#
# @requires librarian
#
-class DocumentDublinCoreHandler(BaseHandler):
- allowed_methods = ('GET', 'POST')
-
- @hglibrary
- def read(self, request, docid, lib):
- """Read document as raw text"""
- try:
- revision = request.GET.get('revision', 'latest')
-
- if revision == 'latest':
- doc = lib.document(docid)
- else:
- doc = lib.document_for_rev(revision)
-
-
- if document.id != docid:
- return response.BadRequest().django_response({'reason': 'name-mismatch',
- 'message': 'Provided revision is not valid for this document'})
-
- bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
- return bookinfo.serialize()
- except (EntryNotFound, RevisionNotFound), e:
- return response.EntityNotFound().django_response({
- 'exception': type(e), 'message': e.message})
-
- @hglibrary
- def create(self, request, docid, lib):
- try:
- bi_json = request.POST['contents']
- revision = request.POST['revision']
-
- if request.POST.has_key('message'):
- msg = u"$USER$ " + request.PUT['message']
- else:
- msg = u"$AUTO$ Dublin core update."
-
- current = lib.document(docid, request.user.username)
- orig = lib.document_for_rev(revision)
-
- if current != orig:
- return response.EntityConflict().django_response({
- "reason": "out-of-date",
- "provided": orig.revision,
- "latest": current.revision })
-
- xmldoc = parser.WLDocument.from_string(current.data('xml'))
- document.book_info = dcparser.BookInfo.from_json(bi_json)
-
- # zapisz
- ndoc = current.quickwrite('xml', \
- document.serialize().encode('utf-8'),\
- message=msg, user=request.user.username)
-
- try:
- # return the new revision number
- return {
- "document": ndoc.id,
- "subview": "dc",
- "previous_revision": current.revision,
- "revision": ndoc.revision,
- 'timestamp': ndoc.revision.timestamp,
- "url": reverse("docdc_view", args=[ndoc.id])
- }
- except Exception, e:
- if ndoc: lib._rollback()
- raise e
- except RevisionNotFound:
- return response.EntityNotFound().django_response()
+#class DocumentDublinCoreHandler(BaseHandler):
+# allowed_methods = ('GET', 'POST')
+#
+# @hglibrary
+# def read(self, request, docid, lib):
+# """Read document as raw text"""
+# try:
+# revision = request.GET.get('revision', 'latest')
+#
+# if revision == 'latest':
+# doc = lib.document(docid)
+# else:
+# doc = lib.document_for_rev(revision)
+#
+#
+# if document.id != docid:
+# return response.BadRequest().django_response({'reason': 'name-mismatch',
+# 'message': 'Provided revision is not valid for this document'})
+#
+# bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
+# return bookinfo.serialize()
+# except (EntryNotFound, RevisionNotFound), e:
+# return response.EntityNotFound().django_response({
+# 'exception': type(e), 'message': e.message})
+#
+# @hglibrary
+# def create(self, request, docid, lib):
+# try:
+# bi_json = request.POST['contents']
+# revision = request.POST['revision']
+#
+# if request.POST.has_key('message'):
+# msg = u"$USER$ " + request.PUT['message']
+# else:
+# msg = u"$AUTO$ Dublin core update."
+#
+# current = lib.document(docid, request.user.username)
+# orig = lib.document_for_rev(revision)
+#
+# if current != orig:
+# return response.EntityConflict().django_response({
+# "reason": "out-of-date",
+# "provided": orig.revision,
+# "latest": current.revision })
+#
+# xmldoc = parser.WLDocument.from_string(current.data('xml'))
+# document.book_info = dcparser.BookInfo.from_json(bi_json)
+#
+# # zapisz
+# ndoc = current.quickwrite('xml', \
+# document.serialize().encode('utf-8'),\
+# message=msg, user=request.user.username)
+#
+# try:
+# # return the new revision number
+# return {
+# "document": ndoc.id,
+# "subview": "dc",
+# "previous_revision": current.revision,
+# "revision": ndoc.revision,
+# 'timestamp': ndoc.revision.timestamp,
+# "url": reverse("docdc_view", args=[ndoc.id])
+# }
+# except Exception, e:
+# if ndoc: lib._rollback()
+# raise e
+# except RevisionNotFound:
+# return response.EntityNotFound().django_response()
class MergeHandler(BaseHandler):
allowed_methods = ('POST',)
@hglibrary
def create(self, request, form, docid, lib):
"""Create a new document revision from the information provided by user"""
+ revision = form.cleaned_data['revision']
- target_rev = form.cleaned_data['target_revision']
-
+ # fetch the main branch document
doc = lib.document(docid)
- udoc = doc.take(request.user.username)
- if target_rev == 'latest':
- target_rev = udoc.revision
+ # fetch the base document
+ user_doc = lib.document_for_rev(revision)
+ base_doc = user_doc.latest()
- if str(udoc.revision) != target_rev:
- # user think doesn't know he has an old version
- # of his own branch.
-
- # Updating is teorericly ok, but we need would
- # have to force a refresh. Sharing may be not safe,
- # 'cause it doesn't always result in update.
-
- # In other words, we can't lie about the resource's state
- # So we should just yield and 'out-of-date' conflict
- # and let the client ask again with updated info.
-
- # NOTE: this could result in a race condition, when there
- # are 2 instances of the same user editing the same document.
- # Instance "A" trying to update, and instance "B" always changing
- # the document right before "A". The anwser to this problem is
- # for the "A" to request a merge from 'latest' and then
- # check the parent revisions in response, if he actually
- # merge from where he thinks he should. If not, the client SHOULD
- # update his internal state.
+ if base_doc != user_doc:
return response.EntityConflict().django_response({
- "reason": "out-of-date",
- "provided": target_rev,
- "latest": udoc.revision })
+ "reason": "out-of-date",
+ "provided": str(user_doc.revision),
+ "latest": str(base_doc.revision)
+ })
if form.cleaned_data['type'] == 'update':
# update is always performed from the file branch
# to the user branch
- success, changed = udoc.update(request.user.username)
+ changed, clean = base_doc.update(request.user.username)
+
+ # update user document
+ if changed:
+ user_doc_new = user_doc.latest()
+
+ # shared document is the same
+ doc_new = doc
+
+ if form.cleaned_data['type'] == 'share':
+ if not base_doc.up_to_date():
+ return response.BadRequest().django_response({
+ "reason": "not-fast-forward",
+ "message": "You must first update yout branch to the latest version."
+ })
+
+ # check for unresolved conflicts
+ if base_doc.has_conflict_marks():
+ return response.BadRequest().django_response({
+ "reason": "unresolved-conflicts",
+ "message": "There are unresolved conflicts in your file. Fix them, and try again."
+ })
- if form.cleaned_data['type'] == 'share':
- if not request.user.has_perm('explorer.document.can_share'):
+ if not request.user.has_perm('api.document.can_share'):
# User is not permitted to make a merge, right away
# So we instead create a pull request in the database
try:
prq, created = PullRequest.objects.get_or_create(
- source_revision = str(udoc.revision),
+ comitter = request.user,
+ document = docid,
+ status = "N",
defaults = {
- 'comitter': request.user,
- 'document': docid,
- 'status': "N",
+ 'source_revision': str(base_doc.revision),
'comment': form.cleaned_data['message'] or '$AUTO$ Document shared.',
}
)
+ # there can't be 2 pending request from same user
+ # for the same document
+ if not created:
+ prq.source_revision = str(base_doc.revision)
+ prq.comment = prq.comment + 'u\n\n' + (form.cleaned_data['message'] or u'')
+ prq.save()
+
return response.RequestAccepted().django_response(\
ticket_status=prq.status, \
ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
return response.EntityConflict().django_response({
'reason': 'request-already-exist'
})
- else:
- success, changed = udoc.share(form.cleaned_data['message'])
- if not success:
- return response.EntityConflict().django_response({
- 'reason': 'merge-failure',
- })
+ changed = base_doc.share(form.cleaned_data['message'])
- if not changed:
- return response.SuccessNoContent().django_response()
+ # update shared version if needed
+ if changed:
+ doc_new = doc.latest()
- nudoc = udoc.latest()
+ # the user wersion is the same
+ user_doc_new = base_doc
+ # The client can compare parent_revision to revision
+ # to see if he needs to update user's view
+ # Same goes for shared view
+
return response.SuccessAllOk().django_response({
- "name": nudoc.id,
- "parent_user_resivion": udoc.revision,
- "parent_revision": doc.revision,
- "revision": nudoc.revision,
- 'timestamp': nudoc.revision.timestamp,
- })
+ "name": user_doc_new.id,
+ "user": user_doc_new.owner,
+ "parent_revision": user_doc_new.revision,
+ "parent_shared_revision": doc.revision,
+ "revision": user_doc_new.revision,
+ "shared_revision": doc_new.revision,
+ 'timestamp': user_doc_new.revision.timestamp,
+ })
\ No newline at end of file
# -*- encoding: utf-8 -*-
+import logging
+log = logging.getLogger('platforma.api.manage')
+
__author__= "Łukasz Rekucki"
__date__ = "$2009-09-25 15:49:50$"
__doc__ = "Module documentation."
-from piston.handler import BaseHandler, AnonymousBaseHandler
+from piston.handler import BaseHandler
from api.utils import hglibrary
-from explorer.models import PullRequest
+from api.models import PullRequest
from api.response import *
+from datetime import datetime
class PullRequestListHandler(BaseHandler):
allowed_methods = ('GET',)
def read(self, request):
- if request.user.has_perm('explorer.book.can_share'):
+ if request.user.has_perm('api.pullrequest.can_change'):
return PullRequest.objects.all()
else:
return PullRequest.objects.filter(commiter=request.user)
def update(self, request, prq_id):
"""Change the status of request"""
- if not request.user.has_perm('explorer.document.can_share'):
+ if not request.user.has_perm('api.pullrequest.can_change'):
return AccessDenied().django_response("Insufficient priviliges")
prq = PullRequest.objects.get(id=prq_id)
if not prq:
return EntityNotFound().django_response()
-
action = request.PUT.get('action', None)
- if action == 'accept' and prq.status == 'N':
- return self.accept_merge(prq)
+ if action == 'accept':
+ return self.accept_merge(request.user, prq)
elif action == 'reject' and prq.status in ['N', 'R']:
- return self.reject_merge(prq)
+ return self.reject_merge(request.user, prq)
else:
return BadRequest().django_response()
@hglibrary
- def accept_merge(self, prq, lib):
- doc = lib.document( prq.document )
- udoc = doc.take( prq.comitter.username )
- success, changed = udoc.share(prq.comment)
-
- if not success:
- return EntityConflict().django_response()
-
- doc = doc.latest()
-
- prq.status = 'A'
- prq.merged_revisions = unicode(doc.revision)
- prq.save()
-
- return SuccessAllOk().django_response({
- 'status': prq.status
- })
-
+ def accept_merge(self, user, prq, lib):
+ if prq.status not in ['N', 'E']:
+ return BadRequest().django_response({
+ 'reason': 'invalid-state',
+ 'message': "This pull request is alredy resolved. Can't accept."
+ })
+
+ src_doc = lib.document( prq.source_revision )
+
+ lock = lib.lock()
+ try:
+ if not src_doc.up_to_date():
+ # This revision is no longer up to date, thus
+ # it needs to be updated, before push:
+ #
+ # Q: where to put the updated revision ?
+ # A: create a special user branch named prq-#prqid
+ prq_doc = src_doc.take("$prq-%d" % prd.id)
+
+ # This could be not the first time we try this,
+ # so the prq_doc could already be there
+ # and up to date
+
+ success, changed = prq_doc.update(user.username)
+ prq.status = 'E'
+
+ if not success:
+ prq.save()
+ # this can happen only if the merge program
+ # is misconfigured - try returning an entity conflict
+ # TODO: put some useful infor here
+ return EntityConflict().django_response()
+
+ if changed:
+ prq_doc = prq_doc.latest()
+
+ prq.source_revision = str(prq_doc.revision)
+ src_doc = prq_doc
+
+ # check if there are conflicts
+ if prq_doc.has_conflict_marks():
+ prq.status = 'E'
+ prq.save()
+ # Now, the user must resolve the conflict
+ return EntityConflict().django_response({
+ "reason": "unresolved-conflicts",
+ "message": "There are conflict in the document. Resolve the conflicts retry accepting."
+ })
+
+ # So, we have an up-to-date, non-conflicting document
+ changed = src_doc.share(prq.comment)
+
+ if not changed:
+ # this is actually very bad, but not critical
+ log.error("Unsynched pull request: %d" % prq.id)
+
+ # sync state with repository
+ prq.status = 'A'
+ prq.merged_revision = str(src_doc.shared().revision)
+ prq.merged_timestamp = datetime()
+ prq.save()
+
+ return SuccessAllOk().django_response({
+ 'status': prq.status,
+ 'merged_into': prq.merged_revision,
+ 'merged_at': prq.merged_timestamp
+ })
+ finally:
+ lock.release()
def reject_merge(self, prq, lib):
prq.status = 'R'
# -*- encoding: utf-8 -*-
+import logging
+log = logging.getLogger('platforma.api.toolbar')
+
__author__= "Łukasz Rekucki"
__date__ = "$2009-09-25 15:55:33$"
__doc__ = "Module documentation."
from django.db import models
+from django.contrib.auth.models import User
# Create your models here.
class PartCache(models.Model):
me.objects.create(user_id=userid, document_id=docid, part_id=part)
-
-
+class PullRequest(models.Model):
+ REQUEST_STATUSES = {
+ "N": "New",
+ "E": "Edited/Conflicting",
+ "R": "Rejected",
+ "A": "Accepted & merged",
+ }
-
+ comitter = models.ForeignKey(User) # the user who request the pull
+ comment = models.TextField() # addtional comments to the request
-
-
-
-
\ No newline at end of file
+ timestamp = models.DateTimeField(auto_now_add=True)
+
+ # document to merge
+ document = models.CharField(max_length=255)
+
+ # revision to be merged into the main branch
+ source_revision = models.CharField(max_length=40, unique=True)
+ target_revision = models.CharField(max_length=40)
+
+ # current status
+ status = models.CharField(max_length=1, choices=REQUEST_STATUSES.items())
+
+ # comment to the status change of request (if applicable)
+ response_comment = models.TextField(blank=True)
+
+ # revision number in which the changes were merged (if any)
+ merged_revision = models.CharField(max_length=40, blank=True, null=True)
+ merge_timestamp = models.DateTimeField(blank=True, null=True)
+
+ def __unicode__(self):
+ return unicode(self.comitter) + u':' + self.document
+
+
+ class Meta:
+ permissions = (
+ ("pullrequest.can_view", "Can view pull request's contents."),
+ )
+
+
+# This is actually an abstract Model, but if we declare
+# it as so Django ignores the permissions :(
+class Document(models.Model):
+ class Meta:
+ permissions = (
+ ("document.can_share", "Can share documents without pull requests."),
+ ("document.can_view_other", "Can view other's documents."),
+ )
\ No newline at end of file
document_resource = Resource(dh.DocumentHandler, **authdata)
document_text_resource = Resource(dh.DocumentTextHandler, **authdata)
document_html_resource = Resource(dh.DocumentHTMLHandler, **authdata)
-document_dc_resource = Resource(dh.DocumentDublinCoreHandler, **authdata)
+# document_dc_resource = Resource(dh.DocumentDublinCoreHandler, **authdata)
document_gallery = Resource(dh.DocumentGalleryHandler, **authdata)
document_merge = Resource(dh.MergeHandler, **authdata)
'document_resource',
'document_text_resource',
'document_html_resource',
- 'document_dc_resource',
+# 'document_dc_resource',
'document_gallery',
'document_merge',
'toolbar_buttons',
def __init__(self, **kwargs):
ResponseObject.__init__(self, 403, **kwargs)
-
- def django_response(self, reason):
- return ResponseObject.django_response(self, \
- body={'reason': reason})
+
class EntityNotFound(ResponseObject):
# document_dc_resource,
# name="docdc_view_withformat"),
- url(urlpath(r'documents', DOC, 'dc', format=False),
- document_dc_resource, {'emitter_format': 'json'},
- name="docdc_view"),
+# url(urlpath(r'documents', DOC, 'dc', format=False),
+# document_dc_resource, {'emitter_format': 'json'},
+# name="docdc_view"),
# MERGE
url(urlpath(r'documents', DOC, 'revision', format=False),
def decorator(func):
@wraps(func)
- def decorated(self, request, * args, ** kwargs):
+ def decorated(self, request, *args, **kwargs):
form = formclass(getattr(request, source), request.FILES)
if not form.is_valid():
admin.site.register(explorer.models.EditorSettings)
admin.site.register(explorer.models.EditorPanel)
-admin.site.register(explorer.models.PullRequest)
admin.site.register(explorer.models.GalleryForDocument)
\ No newline at end of file
def __unicode__(self):
return self.display_name
-
-class Document(models.Model):
- class Meta:
- permissions = (
- ("can_share", "Can share documents without pull requests."),
- )
-
- pass
-
-class PullRequest(models.Model):
- REQUEST_STATUSES = (
- ("N", "Pending for resolution"),
- ("R", "Rejected"),
- ("A", "Accepted & merged"),
- )
-
- comitter = models.ForeignKey(User) # the user who request the pull
- comment = models.TextField() # addtional comments to the request
-
- # document to merge
- document = models.CharField(max_length=255)
-
- # revision to be merged into the main branch
- source_revision = models.CharField(max_length=40, unique=True)
-
- # current status
- status = models.CharField(max_length=1, choices=REQUEST_STATUSES)
-
- # comment to the status change of request (if applicable)
- response_comment = models.TextField(blank=True)
-
- # revision number in which the changes were merged (if any)
- merged_rev = models.CharField(max_length=40, blank=True, null=True)
-
- def __unicode__(self):
- return unicode(self.comitter) + u':' + self.document
+
# Yes, this is intentionally unnormalized !
class GalleryForDocument(models.Model):
# -*- coding: utf-8 -*-
import urllib2
-import hg, re
-from datetime import date
-import librarian
-
-from librarian import html, parser, dcparser
-from librarian import ParseError, ValidationError
+import logging
+log = logging.getLogger('platforma.explorer.views')
from django.conf import settings
from django.contrib.auth.decorators import login_required, permission_required
from django.core.urlresolvers import reverse
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseNotFound
+from django.http import HttpResponse
from django.utils import simplejson as json
from django.views.generic.simple import direct_to_template
from django.contrib.auth.decorators import login_required
-from explorer import forms, models
-from toolbar import models as toolbar_models
-
-from django.forms.util import ErrorList
-
-import wlrepo
-
-#
-# Some useful decorators
-
-def file_branch(fileid, user=None):
- parts = fileid.split('$')
- return ('personal_'+ user.username + '_' if user is not None else '') \
- + 'file_' + parts[0]
-
-def file_path(fileid):
- return 'pub_'+fileid+'.xml'
+from api.models import PullRequest
-def with_repo(view):
- """Open a repository for this view"""
- def view_with_repo(request, *args, **kwargs):
- kwargs['repo'] = wlrepo.open_library(settings.REPOSITORY_PATH, 'hg')
- return view(request, *args, **kwargs)
- return view_with_repo
-
-#
def ajax_login_required(view):
"""Similar ro @login_required, but instead of redirect,
just return some JSON stuff with error."""
return view_with_auth
@login_required
-# @with_repo
def display_editor(request, path):
- # this is the only entry point where we create an autobranch for the user
- # if it doesn't exists. All other views SHOULD fail.
- #def ensure_branch_exists():
- # parent = repo.get_branch_tip('default')
- # repo._create_branch(file_branch(path, request.user), parent)
-
-# try:
- # repo.with_wlock(ensure_branch_exists)
-
+ user = request.GET.get('user', request.user.username)
+ log.info(user)
+
return direct_to_template(request, 'explorer/editor.html', extra_context={
- 'fileid': path,
- 'panel_list': ['lewy', 'prawy'],
- 'availble_panels': models.EditorPanel.objects.all(),
- # 'scriptlets': toolbar_models.Scriptlet.objects.all()
+ 'fileid': path,
+ 'euser': user
})
#
# View all files
#
-@with_repo
-def file_list(request, repo):
+def file_list(request):
import api.forms
from api.resources import library_resource
'filetree': doctree['documents'], 'bookform': bookform,
})
-@permission_required('explorer.can_add_files')
-@with_repo
+@permission_required('api.document.can_add')
def file_upload(request, repo):
from api.resources import library_resource
from api.forms import DocumentUploadForm
# =================
# = Pull requests =
# =================
-def pull_requests(request):
- from explorer.models import PullRequest
-
+def pull_requests(request):
objects = PullRequest.objects.order_by('status')
if not request.user.has_perm('explorer.book.can_share'):
+++ /dev/null
-# -*- coding: utf-8 -*-
-import os
-from mercurial import localrepo, ui, encoding, util
-import mercurial.merge, mercurial.error
-
-encoding.encoding = 'utf-8'
-
-X = 'g\xc5\xbceg\xc5\xbc\xc3\xb3\xc5\x82ka'
-
-def sanitize_string(path):
- if isinstance(path, unicode): #
- return path.encode('utf-8')
- else: # it's a string, so we have no idea what encoding it is
- return path
-
-class Repository(object):
- """Abstrakcja repozytorium Mercurial. Działa z Mercurial w wersji 1.3.1."""
-
- def __init__(self, path, create=False):
- self.ui = ui.ui()
- self.ui.config('ui', 'quiet', 'true')
- self.ui.config('ui', 'interactive', 'false')
-
- self.real_path = sanitize_string(os.path.realpath(path))
- self.repo = self._open_repository(self.real_path, create)
-
- def _open_repository(self, path, create=False):
- if os.path.isdir(path):
- try:
- return localrepo.localrepository(self.ui, path)
- except mercurial.error.RepoError:
- # dir is not an hg repo, we must init it
- if create:
- return localrepo.localrepository(self.ui, path, create=1)
- elif create:
- os.makedirs(path)
- return localrepo.localrepository(self.ui, path, create=1)
- raise RepositoryDoesNotExist("Repository %s does not exist." % path)
-
- def file_list(self, branch):
- return self.in_branch(lambda: self._file_list(), branch)
-
- def _file_list(self):
- return list(self.repo[None])
-
- def get_file(self, path, branch):
- return self.in_branch(lambda: self._get_file(path), branch)
-
- def _get_file(self, path):
- path = sanitize_string(path)
- if not self._file_exists(path):
- raise RepositoryException("File not availble in this branch.")
-
- return self.repo.wread(path)
-
- def file_exists(self, path, branch):
- return self.in_branch(lambda: self._file_exists(path), branch)
-
- def _file_exists(self, path):
- path = sanitize_string(path)
- return self.repo.dirstate[path] != "?"
-
- def write_file(self, path, value, branch):
- return self.in_branch(lambda: self._write_file(path, value), branch)
-
- def _write_file(self, path, value):
- path = sanitize_string(path)
- return self.repo.wwrite(path, value, [])
-
- def add_file(self, path, value, branch):
- return self.in_branch(lambda: self._add_file(path, value), branch)
-
- def _add_file(self, path, value):
- path = sanitize_string(path)
- self._write_file(path, value)
- return self.repo.add( [path] )
-
- def _commit(self, message, user=None):
- return self.repo.commit(text=sanitize_string(message), user=sanitize_string(user))
-
- def commit(self, message, branch, user=None):
- return self.in_branch(lambda: self._commit(message, key=key, user=user), branch)
-
- def in_branch(self, action, bname):
- wlock = self.repo.wlock()
- try:
- old = self._switch_to_branch(bname)
- try:
- # do some stuff
- return action()
- finally:
- self._switch_to_branch(old)
- finally:
- wlock.release()
-
- def merge_branches(self, bnameA, bnameB, user, message):
- wlock = self.repo.wlock()
- try:
- return self.merge_revisions(self.get_branch_tip(bnameA),
- self.get_branch_tip(bnameB), user, message)
- finally:
- wlock.release()
-
- def diff(self, revA, revB):
- return UpdateStatus(self.repo.status(revA, revB))
-
- def merge_revisions(self, revA, revB, user, message):
- wlock = self.repo.wlock()
- try:
- old = self.repo[None]
-
- self._checkout(revA)
- mergestatus = self._merge(revB)
- if not mergestatus.isclean():
- # revert the failed merge
- self.repo.recover()
- raise UncleanMerge(u'Failed to merge %d files.' % len(mergestatus.unresolved))
-
- # commit the clean merge
- self._commit(message, user)
-
- # cleanup after yourself
- self._checkout(old.rev())
- except util.Abort, ae:
- raise RepositoryException(u'Failed merge: ' + ae.message)
- finally:
- wlock.release()
-
- def common_ancestor(self, revA, revB):
- return self.repo[revA].ancestor(self.repo[revB])
-
- def _checkout(self, rev, force=True):
- return MergeStatus(mercurial.merge.update(self.repo, rev, False, force, None))
-
- def _merge(self, rev):
- """ Merge the revision into current working directory """
- return MergeStatus(mercurial.merge.update(self.repo, rev, True, False, None))
-
- def _switch_to_branch(self, bname):
- bname = sanitize_string(bname)
- wlock = self.repo.wlock()
- try:
- current = self.repo[None].branch()
- if current == bname:
- return current
-
- tip = self.get_branch_tip(bname)
- status = self._checkout(tip)
-
- if not status.isclean():
- raise RepositoryException("Unclean branch switch. This IS REALLY bad.")
-
- return current
- except KeyError, ke:
- raise RepositoryException((u"Can't switch to branch '%s': no such branch." % bname) , ke)
- except util.Abort, ae:
- raise RepositoryException(u"Can't switch to branch '%s': %s" % (bname, ae.message), ae)
- finally:
- wlock.release()
-
- def with_wlock(self, action):
- wlock = self.repo.wlock()
- try:
- action()
- finally:
- wlock.release()
-
- def _create_branch(self, name, parent_rev, msg=None, before_commit=None):
- """WARNING: leaves the working directory in the new branch"""
- name = sanitize_string(name)
-
- if self.has_branch(name): return # just exit
-
- self._checkout(parent_rev)
- self.repo.dirstate.setbranch(name)
-
- if msg is None:
- msg = "Initial commit for branch '%s'." % name
-
- if before_commit: before_commit()
- self._commit(msg, user='platform')
- return self.get_branch_tip(name)
-
- def write_lock(self):
- """Returns w write lock to the repository."""
- return self.repo.wlock()
-
- def has_branch(self, name):
- name = sanitize_string(name)
- return (name in self.repo.branchmap().keys())
-
- def get_branch_tip(self, name):
- name = sanitize_string(name)
- return self.repo.branchtags()[name]
-
- def getnode(self, rev):
- return self.repo[rev]
-
-class MergeStatus(object):
-
- def __init__(self, mstatus):
- self.updated = mstatus[0]
- self.merged = mstatus[1]
- self.removed = mstatus[2]
- self.unresolved = mstatus[3]
-
- def isclean(self):
- return self.unresolved == 0
-
-class UpdateStatus(object):
-
- def __init__(self, mstatus):
- self.modified = mstatus[0]
- self.added = mstatus[1]
- self.removed = mstatus[2]
- self.deleted = mstatus[3]
- self.untracked = mstatus[4]
- self.ignored = mstatus[5]
- self.clean = mstatus[6]
-
- def has_changes(self):
- return bool( len(self.modified) + len(self.added) + \
- len(self.removed) + len(self.deleted) )
-
-class RepositoryException(Exception):
- def __init__(self, msg, cause=None):
- Exception.__init__(self, msg)
- self.cause = cause
-
-class UncleanMerge(RepositoryException):
- pass
-
-class RepositoryDoesNotExist(RepositoryException):
- pass
-
"""Retrieve a document in the specified revision."""
pass
- def document(self, docid, user=None):
+ def document(self, docid, user=None, rev='latest'):
"""Retrieve a document from a library."""
pass
class RevisionNotFound(LibraryException):
def __init__(self, rev):
LibraryException.__init__(self, "Revision %r not found." % rev)
+
+class RevisionMismatch(LibraryException):
+ def __init__(self, fdi, rev):
+ LibraryException.__init__(self, "No revision %r for document %r." % (rev, fdi))
class EntryNotFound(LibraryException):
def __init__(self, rev, entry, guesses=[]):
# -*- encoding: utf-8 -*-
+import logging
+log = logging.getLogger('ral.mercurial')
+
__author__= "Łukasz Rekucki"
__date__ = "$2009-09-25 09:20:22$"
__doc__ = "Module documentation."
# -*- encoding: utf-8 -*-
+import logging
+log = logging.getLogger('ral.mercurial')
+
__author__ = "Łukasz Rekucki"
__date__ = "$2009-09-25 09:35:06$"
__doc__ = "Module documentation."
import wlrepo
import mercurial.error
+import re
+
+import logging
+log = logging.getLogger('wlrepo.document')
class MercurialDocument(wlrepo.Document):
def ismain(self):
return self._revision.user_name is None
+ def islatest(self):
+ return (self == self.latest())
+
def shared(self):
if self.ismain():
return self
def take_action(library, resolve):
# branch from latest
- library._create_branch(fullid, parent=self._revision)
+ library._set_branchname(fullid)
if not self._library.has_revision(fullid):
+ log.info("Checking out document %s" % fullid)
+
self.invoke_and_commit(take_action, \
lambda d: ("$AUTO$ File checkout.", user) )
return self._library.document_for_rev(fullid)
+
+ def up_to_date(self):
+ return self.ismain() or (\
+ self.shared().ancestorof(self) )
+
def update(self, user):
"""Update parts of the document."""
return (True, False)
if self._revision.has_children():
- print 'Update failed: has children.'
+ log.info('Update failed: has children.')
# can't update non-latest revision
return (False, False)
if sv.ancestorof(self):
return (True, False)
-
return self._revision.merge_with(sv._revision, user=user,
message="$AUTO$ Personal branch update.")
finally:
lock = self.library.lock()
try:
if self.ismain():
- return (True, False) # always shared
+ return False # always shared
user = self._revision.user_name
main = self.shared()._revision
# so we don't need to update yet again, but we need to
# merge down to default branch, even if there was
# no commit's since last update
-
- if main.ancestorof(local):
- print "case 1"
- success, changed = main.merge_with(local, user=user, message=message)
- # Case 2:
- #
- # main * * local
- # |\ |
- # | \|
- # | *
- # | |
#
- # Default has no changes, to update from this branch
- # since the last merge of local to default.
- elif local.has_common_ancestor(main):
- print "case 2"
- if not local.parentof(main):
- success, changed = main.merge_with(local, user=user, message=message)
+ # This is actually the only good case!
+ if main.ancestorof(local):
+ success, changed = main.merge_with(local, user=user, message=message)
- success = True
- changed = False
+ if not success:
+ raise LibraryException("Merge failed.")
- # Case 3:
+ return changed
+
+ # Case 2:
# main *
# |
# * <- this case overlaps with previos one
# There was a recent merge to the defaul branch and
# no changes to local branch recently.
#
- # Use the fact, that user is prepared to see changes, to
- # update his branch if there are any
- elif local.ancestorof(main):
- print "case 3"
- if not local.parentof(main):
- success, changed = local.merge_with(main, user=user, \
- message='$AUTO$ Local branch update during share.')
-
- success = True
- changed = False
-
- else:
- print "case 4"
- success, changed = local.merge_with(main, user=user, \
- message='$AUTO$ Local branch update during share.')
-
- if not success:
- return False
+ # Nothing to do
+ elif local.ancestorof(main):
+ return False
- if changed:
- local = self.latest()._revision
-
- success, changed = main.merge_with(local, user=user,\
- message=message)
-
- return success, changed
+ # In all other cases, the local needs an update
+ # and possibly conflict resolution, so fail
+ raise LibraryExcepton("Document not prepared for sharing.")
+
finally:
- lock.release()
+ lock.release()
+
+
+ def has_conflict_marks(self):
+ return re.search("^(?:<<<<<<< .*|=======|>>>>>>> .*)$", self.data('xml'), re.MULTILINE)
def __unicode__(self):
- return u"Document(%s:%s)" % (self.name, self.owner)
+ return u"Document(%s:%s)" % (self.id, self.owner)
def __str__(self):
return self.__unicode__().encode('utf-8')
def __eq__(self, other):
- return (self._revision == other._revision) and (self.name == other.name)
+ return (self._revision == other._revision)
# -*- encoding: utf-8 -*-
+import logging
+log = logging.getLogger('ral.mercurial')
+
__author__= "Łukasz Rekucki"
__date__ = "$2009-09-25 09:33:02$"
__doc__ = "Module documentation."
# every revision is a document
return self._doccache[str(rev)]
- def document(self, docid, user=None):
- return self.document_for_rev(self.fulldocid(docid, user))
+ def document(self, docid, user=None, rev=u'latest'):
+ rev = self._sanitize_string(rev)
+
+ if rev != u'latest':
+ doc = self.document_for_rev(rev)
+
+ if doc.id != docid or (doc.owner != user):
+ raise wlrepo.RevisionMismatch(self.fulldocid(docid, user)+u'@'+unicode(rev))
+
+ return doc
+ else:
+ return self.document_for_rev(self.fulldocid(docid, user))
def get_revision(self, revid):
revid = self._sanitize_string(revid)
- print "Looking up rev %r (%s)" %(revid, type(revid))
+ log.info("Looking up rev %r (%s)" %(revid, type(revid)) )
try:
ctx = self._changectx( revid )
name = self._sanitize_string(name)
return self._hgrepo.branchtags()[name]
- def _create_branch(self, name, parent=None, before_commit=None):
+ def _create_branch(self, name, parent=None, before_commit=None, message=None):
name = self._sanitize_string(name)
if self._has_branch(name): return # just exit
parentrev = parent.hgrev()
self._checkout(parentrev)
- self._hgrepo.dirstate.setbranch(name)
+ self._set_branchname(name)
if before_commit: before_commit(self)
- self._commit("$ADMN$ Initial commit for branch '%s'." % name, user='$library')
+ message = message or "$ADMN$ Initial commit for branch '%s'." % name
+ self._commit(message, user='$library')
+
+ def _set_branchname(self, name):
+ name = self._sanitize_string(name)
+ self._hgrepo.dirstate.setbranch(name)
def _switch_to_branch(self, branchname):
current = self._hgrepo[None].branch()
/* default form style hacks */
select {
border: none;
- margin-left: 0.1em;
+ margin-left: 0.1em;f
}
#body-wrap {
margin: 0px;
- padding: 0px;
+ padding: 0px;
+ position: fixed;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
+ top: 0px;
}
#header {
vertical-align: middle;
}
-/* ========== */
-/* = Panels = */
-/* ========== */
-
-/* #panels {
- height: 100%;
- width: 100%;
-}
-
-.panel-wrap {
- overflow: hidden;
- position: absolute;
- top: 0px;
- bottom: 0px;
-}
-
-#left-panel-wrap {
- left: 0px;
- width: 8px;
-}
-
-#right-panel-wrap {
- right: 0px;
- width: auto;
- left: 8px;
-}
-
-.panel-content {
- position: absolute;
- overflow: auto;
- overflow-x: hidden;
- top: 25px; left: 0px; bottom:0px; right: 0px;
-}
-
-.panel-overlay {
- position: absolute;
- top: 0px; bottom: 0px; left: 0px; right: 0px;
- z-index: 100;
- background: gray;
- opacity: 0.8;
- text-align: center;
- overflow: hidden;
- display: none;
- cursor: col-resize;
-}
-
-.panel-content-overlay {
-}
-
-.panel-wrap.last-panel .panel-content {
- right: 0px;
-}
-
-.panel-wrap.last-panel .panel-slider {
- display: none;
-}
-
-.panel-wrap .panel-toolbar {
- position: absolute;
- top: 0px; left:0px; right: 0px; height: 26px;
- padding: 0px;
-
- border-bottom: 1px solid #AAA;
- z-index: 80;
-}
-.panel-wrap .panel-slider {
- position: absolute;
- background-color: #DDD;
-
- top: 0px; bottom: 0px; right: 0px; width: 4px;
-
- border-left: 1px solid #999;
- border-right: 1px solid #999;
- border-top: none;
- border-bottom: none;
-
- z-index: 90;
- cursor: col-resize;
-}
-
-.panel-wrap .panel-slider:hover {
- background-color: #999;
-}
-
-.panel-content-overlay.panel-wrap .panel-slider {
- background-color: #DDD;
-}
-
-*/
-
-/* OLD STUFF AGAIN */
-
/* Commit dialog */
#commit-dialog-error-empty-message {
color: red;
background: #FFF;
opacity: 0.8;
text-align: center;
- text-valign: center;
+ vertical-align: middle;
+
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+
+ user-select: 'none';
+ -webkit-user-select: 'none';
+ -khtml-user-select: 'none';
+ -moz-user-select: 'none';
+ overflow: 'hidden';
}
-.view-overlay p {
- display: block;
- position: relative;
- top: auto;
- bottom: auto;
- height: 40px;
+.view-overlay div {
+ position: absolute;
}
/* .buttontoolbarview {
}
});
+// Handle JSON error responses in uniform way
+function parseXHRError(response)
+{
+ var message = ""
+ try {
+ var json = $.evalJSON(response.responseText);
+
+ if(json.reason == 'xml-parse-error') {
+ message = json_response.message.replace(/(line\s+)(\d+)(\s+)/i,
+ "<a class='xml-editor-ref' href='#xml-$2-1'>$1$2$3</a>");
+
+ message = message.replace(/(line\s+)(\d+)(\,\s*column\s+)(\d+)/i,
+ "<a class='xml-editor-ref' href='#xml-$2-$4'>$1$2$3$4</a>");
+ }
+ message = json_response.message || json_response.reason || "Nieznany błąd :((";
+ } catch(e) {
+ // not a valid JSON response
+ message = response.statusText;
+ }
+ return message;
+}
+
Editor.Object._lastGuid = 0;
-var panels = [];
+var panels = [];
\ No newline at end of file
/*global Editor*/
Editor.MessageCenter = Editor.Object.extend({
- init: function() {
- this.messages = [];
- this.flashMessages = [];
- this.firstFlashMessage = null;
- },
+ init: function() {
+ this.messages = [];
+ this.flashMessages = [];
+ this.firstFlashMessage = null;
+ this.timeout = null;
+ console.log("MSC-init:", Date(), this);
+ },
- addMessage: function(type, text, flash) {
- if (!flash) {
- flash = text;
- }
- this.messages.push({type: type, text: text});
- this.flashMessages.push({type: type, text: flash});
- if (this.flashMessages.length == 1) {
- this.set('firstFlashMessage', this.flashMessages[0]);
- setTimeout(this.changeFlashMessage.bind(this), 1000 * 10);
- }
- },
+ addMessage: function(type, tag, text, flash)
+ {
+ if (!tag) tag = '#default'
+
+ if (!flash) {
+ flash = text;
+ }
+
+ this.messages.push({
+ type: type,
+ text: text
+ });
+
+ this.flashMessages.push({
+ type: type,
+ text: flash,
+ tag: tag
+ });
+
+ if(this.timeout) {
+ if(this.flashMessages[0] && (this.flashMessages[0].tag == tag))
+ {
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ this.changeFlashMessage();
+ }
+ }
+
+ else {
+ /* queue was empty at the start */
+ if (this.flashMessages.length == 1) {
+ console.log("MSC-added-fisrt", Date(), this);
+ this.set('firstFlashMessage', this.flashMessages[0]);
+ this.timeout = setTimeout(this.changeFlashMessage.bind(this), 3000);
+ }
+
+ }
+
+ },
- changeFlashMessage: function() {
- this.flashMessages.splice(0, 1);
- if (this.flashMessages.length > 0) {
- this.set('firstFlashMessage', this.flashMessages[0]);
- setTimeout(this.changeFlashMessage.bind(this), 1000 * 3); // 3 seconds
- } else {
- this.set('firstFlashMessage', null);
+ changeFlashMessage: function()
+ {
+ console.log("MSC-change", Date(), this);
+ var previous = this.flashMessages.splice(0, 1);
+
+ if (this.flashMessages.length > 0)
+ {
+ console.log("MSC-chaning-first", Date(), this);
+ this.set('firstFlashMessage', this.flashMessages[0]);
+ this.timeout = setTimeout(this.changeFlashMessage.bind(this), 3000);
+ } else {
+ console.log("MSC-emptying", Date(), this);
+ this.set('firstFlashMessage', null);
+ }
}
- }
});
data: null
});
-
Editor.ToolbarButtonsModel = Editor.Model.extend({
className: 'Editor.ToolbarButtonsModel',
buttons: {},
load: function() {
if (!this.get('buttons').length) {
$.ajax({
- url: toolbarUrl,
+ url: documentInfo.toolbarURL,
dataType: 'json',
success: this.loadSucceeded.bind(this)
});
data: '',
state: 'empty',
- init: function(serverURL, revision) {
+ init: function(document, serverURL) {
this._super();
this.set('state', 'empty');
- this.set('revision', revision);
+ this.set('revision', document.get('revision'));
+ this.document = document;
this.serverURL = serverURL;
this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
this.addObserver(this, 'data', this.dataChanged.bind(this));
load: function(force) {
if (force || this.get('state') == 'empty') {
this.set('state', 'loading');
- messageCenter.addMessage('info', 'Wczytuję XML...');
+ messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
$.ajax({
url: this.serverURL,
dataType: 'text',
data: {
- revision: this.get('revision')
+ revision: this.get('revision'),
+ user: this.document.get('user')
},
success: this.loadingSucceeded.bind(this),
error: this.loadingFailed.bind(this)
}
this.set('data', data);
this.set('state', 'synced');
- messageCenter.addMessage('success', 'Wczytałem XML :-)');
+ messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
},
loadingFailed: function() {
if (this.get('state') != 'loading') {
alert('erroneous state:', this.get('state'));
}
- this.set('error', 'Nie udało się załadować panelu');
+ var message = parseXHRError(response);
+
+ this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
this.set('state', 'error');
- messageCenter.addMessage('error', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
+ messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
},
- update: function(message) {
+ save: function(message) {
if (this.get('state') == 'dirty') {
this.set('state', 'updating');
- messageCenter.addMessage('info', 'Zapisuję XML...');
+ messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
var payload = {
contents: this.get('data'),
- revision: this.get('revision')
+ revision: this.get('revision'),
+ user: this.document.get('user')
};
if (message) {
payload.message = message;
type: 'post',
dataType: 'json',
data: payload,
- success: this.updatingSucceeded.bind(this),
- error: this.updatingFailed.bind(this)
+ success: this.saveSucceeded.bind(this),
+ error: this.saveFailed.bind(this)
});
return true;
}
return false;
},
- updatingSucceeded: function(data) {
+ saveSucceeded: function(data) {
if (this.get('state') != 'updating') {
alert('erroneous state:', this.get('state'));
}
this.set('revision', data.revision);
this.set('state', 'updated');
- messageCenter.addMessage('success', 'Zapisałem XML :-)');
+ messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
},
- updatingFailed: function() {
+ saveFailed: function() {
if (this.get('state') != 'updating') {
alert('erroneous state:', this.get('state'));
}
- messageCenter.addMessage('error', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
+ messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
this.set('state', 'dirty');
},
xmlParts: {},
state: 'empty',
- init: function(htmlURL, revision, dataURL) {
+ init: function(document, dataURL, htmlURL) {
this._super();
this.set('state', 'empty');
- this.set('revision', revision);
+ this.set('revision', document.get('revision'));
+
+ this.document = document;
this.htmlURL = htmlURL;
this.dataURL = dataURL;
- this.renderURL = "http://localhost:8000/api/render";
+ this.renderURL = documentInfo.renderURL;
this.xmlParts = {};
},
url: this.htmlURL,
dataType: 'text',
data: {
- revision: this.get('revision')
+ revision: this.get('revision'),
+ user: this.document.get('user')
},
success: this.loadingSucceeded.bind(this),
error: this.loadingFailed.bind(this)
});
}
- },
+ },
loadingSucceeded: function(data) {
if (this.get('state') != 'loading') {
}
this.set('data', data);
this.set('state', 'synced');
- // messageCenter.addMessage('success', 'Wczytałem HTML :-)');
},
loadingFailed: function(response) {
if (this.get('state') != 'loading') {
alert('erroneous state:', this.get('state'));
}
-
- var json_response = null;
- var message = "";
-
- try {
- json_response = $.evalJSON(response.responseText);
-
- if(json_response.reason == 'xml-parse-error') {
-
- message = json_response.message.replace(/(line\s+)(\d+)(\s+)/i,
- "<a class='xml-editor-ref' href='#xml-$2-1'>$1$2$3</a>");
-
- message = message.replace(/(line\s+)(\d+)(\,\s*column\s+)(\d+)/i,
- "<a class='xml-editor-ref' href='#xml-$2-$4'>$1$2$3$4</a>");
-
-
- }
- else {
- message = json_response.message || json_response.reason || "nieznany błąd.";
- }
- }
- catch (e) {
- message = response.statusText;
- }
-
+
+ var message = parseXHRError(response);
+
this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + message);
-
- this.set('state', 'error');
- // messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
+ this.set('state', 'error');
},
getXMLPart: function(elem, callback)
dataType: 'text',
data: {
revision: this.get('revision'),
+ user: this.document.get('user'),
part: path
},
success: function(data) {
});
},
- update: function(message) {
+ save: function(message) {
if (this.get('state') == 'dirty') {
this.set('state', 'updating');
var payload = {
chunks: $.toJSON(this.xmlParts),
- revision: this.get('revision')
+ revision: this.get('revision'),
+ user: this.document.get('user')
};
if (message) {
type: 'post',
dataType: 'json',
data: payload,
- success: this.updatingSucceeded.bind(this),
- error: this.updatingFailed.bind(this)
+ success: this.saveSucceeded.bind(this),
+ error: this.saveFailed.bind(this)
});
return true;
}
},
- updatingSucceeded: function(data) {
+ saveSucceeded: function(data) {
if (this.get('state') != 'updating') {
alert('erroneous state:', this.get('state'));
}
this.set('state', 'updated');
},
- updatingFailed: function() {
+ saveFailed: function() {
if (this.get('state') != 'updating') {
alert('erroneous state:', this.get('state'));
- }
- messageCenter.addMessage('error', 'Uaktualnienie nie powiodło się', 'Uaktualnienie nie powiodło się');
+ }
this.set('state', 'dirty');
},
if (data.length === 0) {
this.set('data', []);
- } else {
- console.log('dupa');
+ } else {
this.set('data', data[0].pages);
}
data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
contentModels: {},
state: 'empty',
+ errors: '',
+ revision: '',
+ user: '',
init: function() {
this._super();
- this.set('state', 'empty');
- this.load();
+ this.set('state', 'empty');
},
load: function() {
if (this.get('state') == 'empty') {
this.set('state', 'loading');
- messageCenter.addMessage('info', 'Ładuję dane dokumentu...');
+ messageCenter.addMessage('info', 'docload', 'Ładuję dane dokumentu...');
$.ajax({
cache: false,
- url: documentsUrl + fileId,
+ url: documentInfo.docURL,
dataType: 'json',
- success: this.successfulLoad.bind(this)
+ success: this.successfulLoad.bind(this),
+ error: this.failedLoad.bind(this)
});
}
},
successfulLoad: function(data) {
this.set('data', data);
this.set('state', 'synced');
+
+ this.set('revision', data.user_revision);
+ this.set('user', data.user);
+
this.contentModels = {
- 'xml': new Editor.XMLModel(data.text_url, data.user_revision),
- 'html': new Editor.HTMLModel(data.html_url, data.user_revision, data.text_url),
- 'gallery': new Editor.ImageGalleryModel(data.gallery_url)
- };
+ 'xml': new Editor.XMLModel(this, data.text_url),
+ 'html': new Editor.HTMLModel(this, data.text_url, data.html_url),
+ 'gallery': new Editor.ImageGalleryModel(this, data.gallery_url)
+ };
+
for (var key in this.contentModels) {
this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
}
- messageCenter.addMessage('success', 'Dane dokumentu zostały załadowane :-)');
+
+ this.error = '';
+
+ messageCenter.addMessage('success', 'docload', 'Dokument załadowany poprawnie :-)');
+ },
+
+ failedLoad: function(response) {
+ if (this.get('state') != 'loading') {
+ alert('erroneous state:', this.get('state'));
+ }
+
+ var message = parseXHRError(response);
+ this.set('error', '<h2>Nie udało się wczytać dokumentu</h2><p>'+message+"</p>");
+ this.set('state', 'error');
},
contentModelStateChanged: function(property, value, contentModel) {
saveDirtyContentModel: function(message) {
for (var key in this.contentModels) {
if (this.contentModels[key].get('state') == 'dirty') {
- this.contentModels[key].update(message);
+ this.contentModels[key].save(message);
break;
}
}
type: 'post',
data: {
type: 'update',
- target_revision: this.data.user_revision
+ revision: this.revision,
+ user: this.user
},
complete: this.updateCompleted.bind(this),
success: function(data) {
updateCompleted: function(xhr, textStatus) {
console.log(xhr.status, textStatus);
if (xhr.status == 200) { // Sukces
- this.data.user_revision = this.get('updateData').revision;
- messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
+ this.data = this.get('updateData');
+ this.revision = this.data.user_revision;
+ this.user = this.data.user;
+
+ messageCenter.addMessage('info', null, 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision);
for (var key in this.contentModels) {
this.contentModels[key].set('revision', this.data.user_revision);
this.contentModels[key].set('state', 'empty');
}
- messageCenter.addMessage('success', 'Uaktualniłem dokument do najnowszej wersji :-)');
+ messageCenter.addMessage('success', null, 'Uaktualniłem dokument do najnowszej wersji :-)');
} else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
} else if (xhr.status == 204) { // Nic nie zmieniono
- messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
+ messageCenter.addMessage('info', null, 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
} else if (xhr.status == 409) { // Konflikt podczas operacji
- messageCenter.addMessage('error', 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
+ messageCenter.addMessage('error', null, 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
} else if (xhr.status == 500) {
- messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
+ messageCenter.addMessage('critical', null, 'Błąd serwera. Pędź po programistów! :-(');
}
this.set('state', 'synced');
this.set('updateData', null);
merge: function(message) {
this.set('state', 'loading');
- messageCenter.addMessage('info', 'Scalam dokument z głównym repozytorium...');
+ messageCenter.addMessage('info', null, 'Scalam dokument z głównym repozytorium...');
$.ajax({
url: this.data.merge_url,
type: 'post',
dataType: 'json',
data: {
type: 'share',
- target_revision: this.data.user_revision,
+ revision: this.revision,
+ user: this.user,
message: message
},
complete: this.mergeCompleted.bind(this),
mergeCompleted: function(xhr, textStatus) {
console.log(xhr.status, textStatus);
if (xhr.status == 200) { // Sukces
- this.data.user_revision = this.get('mergeData').revision;
+ this.data = this.get('updateData');
+ this.revision = this.data.user_revision;
+ this.user = this.data.user;
+
for (var key in this.contentModels) {
- this.contentModels[key].set('revision', this.data.user_revision);
+ this.contentModels[key].set('revision', this.revision);
this.contentModels[key].set('state', 'empty');
}
- messageCenter.addMessage('success', 'Scaliłem dokument z głównym repozytorium :-)');
+
+ messageCenter.addMessage('success', null, 'Scaliłem dokument z głównym repozytorium :-)');
} else if (xhr.status == 202) { // Wygenerowano PullRequest
- messageCenter.addMessage('success', 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
+ messageCenter.addMessage('success', null, 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
} else if (xhr.status == 204) { // Nic nie zmieniono
- messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
+ messageCenter.addMessage('info', null, 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
} else if (xhr.status == 409) { // Konflikt podczas operacji
- messageCenter.addMessage('error', 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
+ messageCenter.addMessage('error', null, 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
} else if (xhr.status == 500) {
- messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
+ messageCenter.addMessage('critical', null, 'Błąd serwera. Pędź po programistów! :-(');
}
this.set('state', 'synced');
this.set('mergeData', null);
$(function()
{
- documentsUrl = $('#api-base-url').text() + '/';
- toolbarUrl = $('#api-toolbar-url').text();
-
+ var flashView = new FlashView('#flashview', messageCenter);
+
doc = new Editor.DocumentModel();
EditorView = new EditorView('#body-wrap', doc);
- EditorView.freeze();
+ EditorView.freeze("<h1>Wczytuję dokument...</h1>");
leftPanelView = new PanelContainerView('#left-panel-container', doc);
rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
- var flashView = new FlashView('#flashview', messageCenter);
+
});
$('#commit-dialog-error-empty-message').hide();
$('#commit-dialog').jqmHide();
});
+
// $('#split-dialog').jqm({
// modal: true,
this.commitButton.attr('disabled', null);
this.updateButton.attr('disabled', 'disabled');
this.mergeButton.attr('disabled', 'disabled');
- } else if (value == 'synced') {
+ } else if (value == 'synced') {
this.quickSaveButton.attr('disabled', 'disabled');
this.commitButton.attr('disabled', 'disabled');
this.updateButton.attr('disabled', null);
this.mergeButton.attr('disabled', null);
+ this.unfreeze();
} else if (value == 'empty') {
this.quickSaveButton.attr('disabled', 'disabled');
this.commitButton.attr('disabled', 'disabled');
this.updateButton.attr('disabled', 'disabled');
this.mergeButton.attr('disabled', 'disabled');
+ } else if (value == 'error') {
+ this.freeze(this.model.get('error'));
+ this.quickSaveButton.attr('disabled', 'disabled');
+ this.commitButton.attr('disabled', 'disabled');
+ this.updateButton.attr('disabled', 'disabled');
+ this.mergeButton.attr('disabled', 'disabled');
+
}
},
render: function() {
this.element.html(render_template(this.template, this));
+ setTimeout(function() {}, 0);
},
- modelFirstFlashMessageChanged: function(property, value) {
- this.element.fadeOut('slow', function() {
- this.element.css({'z-index': 0});
- this.shownMessage = value;
- this.render();
+ modelFirstFlashMessageChanged: function(property, value) {
+ this.element.fadeOut(200, (function() {
+
+ this.element.css({'z-index': 0});
+ this.shownMessage = value;
+ this.render();
- if(this.shownMessage) {
+ if(this.shownMessage) {
this.element.css({'z-index': 1000});
- this.element.fadeIn('slow');
- }
- }.bind(this));
+ this.element.fadeIn();
+ };
+ }).bind(this));
}
});
modelDataChanged: function(property, value) {
$('.htmlview', this.element).html(value);
+ this.updatePrintLink();
+ },
+ updatePrintLink: function() {
var base = this.$printLink.attr('ui:baseref');
- this.$printLink.attr('href', base + "?revision=" + this.model.get('revision'));
+ this.$printLink.attr('href', base + "?user="+this.model.document.get('user')+"&revision=" + this.model.get('revision'));
},
modelStateChanged: function(property, value)
if(this.$printLink) this.$printLink.unbind();
this._super();
this.$printLink = $('.html-print-link', this.element);
-
- var base = this.$printLink.attr('ui:baseref');
- this.$printLink.attr('href', base + "?revision=" + this.model.get('revision'));
+ this.updatePrintLink();
this.element.bind('click', this.itemClicked.bind(this));
},
if (this.frozen()) {
this.unfreeze();
}
- this.overlay = this.overlay
- || $('<div><div>' + message + '</div></div>')
- .addClass(this.overlayClass)
+ this.overlay = this.overlay || $('<div><div>' + message + '</div></div>');
+
+ this.overlay.addClass(this.overlayClass)
.css({
- position: 'absolute',
- width: this.element.width(),
- height: this.element.height(),
- top: this.element.position().top,
- left: this.element.position().left,
- 'user-select': 'none',
- '-webkit-user-select': 'none',
- '-khtml-user-select': 'none',
- '-moz-user-select': 'none',
- overflow: 'hidden'
- })
- .attr('unselectable', 'on')
- .appendTo(this.element.parent());
+ }).attr('unselectable', 'on')
+
+ this.overlay.appendTo(this.element);
+
+ var ovc = this.overlay.children('div');
+ var padV = (this.overlay.height() - ovc.outerHeight())/2;
+ var padH = (this.overlay.width() - ovc.outerWidth())/2;
+
this.overlay.children('div').css({
- position: 'relative',
- top: this.overlay.height() / 2 - 20
- });
+ top: padV, left: padH
+ });
},
unfreeze: function() {
},
resized: function(event) {
- if (this.frozen()) {
- this.overlay.css({
- position: 'absolute',
- width: this.element.width(),
- height: this.element.height(),
- top: this.element.position().top,
- left: this.element.position().left
- }).children('div').css({
- position: 'relative',
- top: this.overlay.height() / 2 - 20
+ if(this.overlay) {
+ var ovc = this.overlay.children('div');
+ var padV = (this.overlay.height() - ovc.outerHeight())/2;
+ var padH = (this.overlay.width() - ovc.outerWidth())/2;
+
+ this.overlay.children('div').css({
+ top: padV,
+ left: padH
});
}
},
this.unfreeze();
this.element.html('');
}
-});
+});
\ No newline at end of file
$(document).bind('xml-scroll-request', this.scrollCallback);
this.parent.freeze('Ładowanie edytora...');
+
+ setTimeout((function(){
+
this.editor = new CodeMirror($('.xmlview', this.element).get(0), {
parserfile: 'parsexml.js',
path: "/static/js/lib/codemirror/",
onChange: this.editorDataChanged.bind(this),
initCallback: this.editorDidLoad.bind(this)
});
+
+ }).bind(this), 0);
},
resized: function(event) {
this.model
.addObserver(this, 'data', this.modelDataChanged.bind(this))
.addObserver(this, 'state', this.modelStateChanged.bind(this))
- .load();
-
- this.parent.unfreeze();
+ .load();
this.editor.setCode(this.model.get('data'));
this.modelStateChanged('state', this.model.get('state'));
editor.grabKeys(
this.hotkeyPressed.bind(this),
this.isHotkey.bind(this)
- );
+ );
+
+ this.parent.unfreeze();
},
editorDataChanged: function() {
} else if (value == 'unsynced') {
this.freeze('Niezsynchronizowany...');
} else if (value == 'loading') {
- this.freeze('Ładowanie...');
+ this.freeze('Ładowanie danych...');
} else if (value == 'saving') {
this.freeze('Zapisywanie...');
} else if (value == 'error') {
+++ /dev/null
-<form action="" method="POST">
-{{ form.as_p }}
-<input type="submit" value="Submit" />
-</form>
+++ /dev/null
-<form action="" method="POST">
-{{ form.as_p }}
-<input type="submit" value="Submit" />
-</form>
<link rel="stylesheet" href="{{STATIC_URL}}css/autumn.css" type="text/css" media="screen" title="Autumn colors" charset="utf-8">
<script type="text/javascript" charset="utf-8">
- var fileId = '{{ fileid }}';
+ var documentInfo = {
+ docID: '{{ fileid }}',
+ userID: '{{ euser }}',
+ docURL: '{% url document_view fileid %}{% if euser %}?user={{ euser|urlencode }}{% endif %}',
+ toolbarURL: '{% url toolbar_buttons %}',
+ renderURL: '{% url api.views.render %}'
+ }
+
</script>
{# Libraries #}
- <script src="{{STATIC_URL}}js/lib/codemirror/codemirror.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{STATIC_URL}}js/lib/codemirror/codemirror.js" type="text/javascript" charset="utf-8"></script>
<script src="{{STATIC_URL}}js/lib/jquery.modal.js" type="text/javascript" charset="utf-8"></script>
<script src="{{STATIC_URL}}js/lib/jquery.json.js" type="text/javascript" charset="utf-8"></script>
+
{# Scriptlets #}
<script src="{{STATIC_URL}}js/button_scripts.js" type="text/javascript" charset="utf-8"></script>
</script>
{% endblock extrahead %}
-{% block breadcrumbs %}<a href="{% url file_list %}">Platforma Redakcyjna</a> > {{ fileid }}{% endblock breadcrumbs %}
+{% block breadcrumbs %}<a href="{% url file_list %}">Platforma</a> > {{euser}} > {{ fileid }}{% endblock breadcrumbs %}
{% block header-toolbar %}
<a href="http://stigma.nowoczesnapolska.org.pl/platforma-hg/ksiazki/log/tip/{{ fileid }}.xml" target="_new" >Historia</a>
<button id="action-quick-save">Quick Save</button>
{% endblock %}
-{% block maincontent %}
- <p style="display: none;" id="api-base-url">{% url document_list_view %}</p>
- <p style="display: none;" id="api-toolbar-url">{% url toolbar_buttons %}</p>
+{% block maincontent %}
+
<div id="splitview">
<div id="left-panel-container" class='panel-container'></div>
+++ /dev/null
-{% extends "base.html" %}
-
-{% block extrahead %}
- <script src="/static/js/jquery.lazyload.js" type="text/javascript" charset="utf-8"></script>
- <script type="text/javascript" charset="utf-8">
- $(function() {
- $('#id_folders').change(function() {
- $('#images').load('/images/' + $('#id_folders').val() + '/', function() {
- $('#images').data('lastScroll', -1000);
- });
- });
-
- function resizePanels() {
- $('#images-wrap').height($(window).height() - 80);
- $('#file-text').height($(window).height() - 80);
- }
-
- $(window).resize(resizePanels)
- resizePanels();
-
- $('#images-wrap').lazyload('.image-box', {threshold: 600});
- });
- </script>
-{% endblock extrahead %}
-
-{% block breadcrumbs %}<a href="/">Platforma Redakcyjna</a> ❯ plik {{ hash }}{% endblock breadcrumbs %}
-
-{% block maincontent %}
- <div id="tabs"><a href="{% url file_xml hash %}">Źródło</a><a href="{% url file_html hash %}" class="active">HTML</a><div style="padding: 3px; margin-left: 10px">{{ image_folders_form.folders }}</div><div style="clear: both; height: 0; width: 0"> </div></div>
-
- <div id="sidebar">
- <div id="images-wrap">
- <div id="images">
- <p>Aby zobaczyć obrazki wybierz folder z obrazkami powyżej.</p>
- </div>
- </div>
- <div id="toggle-sidebar"></div>
- </div>
- <div id="file-text">
- {{ object|safe }}
- </div>
-{% endblock maincontent %}
\ No newline at end of file
+++ /dev/null
-<form action="{% url explorer.views.split_text fileid %}" method="POST">\r
- <fieldset>\r
- <legend>Split options</legend>\r
- {% for field in splitform %}\r
- {% if field.is_hidden %}\r
- {{ field}}\r
- {% else %}\r
- {{ field.errors }}\r
- {% ifequal field.html_name 'splitform-autoxml' %}\r
- <p><label>{{ field }} {{ field.label }}</label></p>\r
- {% else %}\r
- <p><label>{{ field.label }}: {{ field }}</label></p>\r
- {% endifequal %}\r
- {% endif %}\r
- {% endfor %} \r
- </fieldset>\r
-\r
- <fieldset id="split-form-dc-subform" style="display: none;">\r
- <legend>Dublin Core</legend>\r
- {{ dcform.as_p }}\r
- </fieldset>\r
- <p>\r
- <button type="submit" id="split-dialog-button-accept">Split</button>\r
- <button type="button" id="split-dialog-button-close">Close</button>\r
- </p>\r
-</form>\r
+++ /dev/null
-<p>Split successful. You can edit the new part here:\r
-<a href="{% url editor_view cfileid %}" target="_new">{% url editor_view cfileid %}</a></p>\r
-<p><button type="button" id="split-dialog-button-dismiss">Close</button></p>
\ No newline at end of file
<table class="request-report" cellspacing="0">
<tr>
<th>Utwór</th><th>Użytkownik</th><th>Komentarz</th><th>Stan</th>
- <th>Akcje</th>
+ <th>Zgłoszono</th><th>Akcje</th>
</tr>
{% if objects %}
{% for pullreq in objects %}
<td class="column-user">{{ pullreq.comitter }}</td>
<td class="column-comment">{{ pullreq.comment }}</td>
<td class="column-status"> {{ pullreq.status }}</td>
- <td><button type="button" class="accept-button" title="{{pullreq.id}}">Akceptuj</button></td>
+ <td class="column-data">{{ pullreq.timestamp }}</td>
+ <td>
+ <button type="button" class="accept-button" title="{{pullreq.id}}">Akceptuj</button>
+ <a href="{% url editor_view pullreq.document %}?user=$prq-{{pullreq.id}}">Zobacz</a>
+ </td>
</tr>
{% endfor %}
{% else %}
- <tr><td colspan="*">Brak żądań</td></tr>
+ <tr><td colspan="6">Brak żądań</td></tr>
{% endif %}
</table>