self.fields[name].initial = value
def save(self, repository, path):
- file_contents = repository.get_file(path).data()
+ file_contents = repository.get_file(path)
doc = etree.fromstring(file_contents)
book_info = dcparser.BookInfo()
return u"Panel settings for %s" % self.user.name
+class PullRequest(models.Model):
+ comitter = models.ForeignKey(User) # the user who request the pull
+ file = models.CharField(max_length=256) # the file to request
+ source_rev = models.CharField(max_length=40) # revision number of the commiter
+
+ comment = models.TextField() # addtional comments to the request
+
+ # revision number in which the changes were merged (if any)
+ merged_rev = models.CharField(max_length=40, null=True)
+
+ def __unicode__(self):
+ return u"Pull request from %s, source: %s %s, status: %s." % \
+ (self.commiter, self.file, self.source_rev, \
+ (("merged into "+self.merged_rev) if self.merged_rev else "pending") )
+
+
def get_image_folders():
return sorted(fn for fn in os.listdir(os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR)) if not fn.startswith('.'))
from explorer import forms, models
-
-def with_repo(func):
- def inner(request, *args, **kwargs):
+#
+# Some useful decorators
+#
+def with_repo(view):
+ """Open a repository for this view"""
+ def view_with_repo(request, *args, **kwargs):
kwargs['repo'] = hg.Repository(settings.REPOSITORY_PATH)
- return func(request, *args, **kwargs)
- return inner
+ 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."""
+ def view_with_auth(request, *args, **kwargs):
+ if request.user.is_authenticated():
+ return view(request, *args, **kwargs)
+ # not authenticated
+ return HttpResponse( json.dumps({'result': 'access_denied'}) );
+ return view_with_auth
+
+#
+# View all files
+#
@with_repo
def file_list(request, repo):
# Edit the file
#
+@ajax_login_required
@with_repo
def file_xml(request, repo, path):
if request.method == 'POST':
errors = dict( (field[0], field[1].as_text()) for field in form.errors.iteritems() )
return HttpResponse( json.dumps({'result': result, 'errors': errors}) );
- else:
- form = forms.BookForm()
- form.fields['content'].initial = repo.get_file(path, models.user_branch(request.user))
- return direct_to_template(request, 'explorer/edit_text.html', extra_context={
- 'form': form,
- })
+ form = forms.BookForm()
+ data = repo.get_file(path, models.user_branch(request.user))
+ form.fields['content'].initial = data
+ return HttpResponse( json.dumps({'result': 'ok', 'content': data}) )
+
+@ajax_login_required
@with_repo
def file_dc(request, path, repo):
if request.method == 'POST':
errors = dict( (field[0], field[1].as_text()) for field in form.errors.iteritems() )
return HttpResponse( json.dumps({'result': result, 'errors': errors}) );
- else:
- fulltext = repo.get_file(path, models.user_branch(request.user))
- form = forms.DublinCoreForm(text=fulltext)
-
- return direct_to_template(request, 'explorer/edit_dc.html', extra_context={
- 'form': form,
- 'fpath': path,
- })
+
+ fulltext = repo.get_file(path, models.user_branch(request.user))
+ form = forms.DublinCoreForm(text=fulltext)
+ return HttpResponse( json.dumps({'result': 'ok', 'content': fulltext}) )
# Display the main editor view
+
+@login_required
def display_editor(request, path):
return direct_to_template(request, 'explorer/editor.html', extra_context={
'hash': path, 'panel_list': ['lewy', 'prawy'],
# = Panel views =
# ===============
+@ajax_login_required
@with_repo
def xmleditor_panel(request, path, repo):
form = forms.BookForm()
})
+@ajax_login_required
def gallery_panel(request, path):
return direct_to_template(request, 'explorer/panels/gallery.html', extra_context={
'fpath': path,
'form': forms.ImageFoldersForm(),
})
+@ajax_login_required
@with_repo
def htmleditor_panel(request, path, repo):
user_branch = models.user_branch(request.user)
})
+@ajax_login_required
@with_repo
def dceditor_panel(request, path, repo):
user_branch = models.user_branch(request.user)
# =================
# = Utility views =
# =================
+@ajax_login_required
def folder_images(request, folder):
return direct_to_template(request, 'explorer/folder_images.html', extra_context={
'images': models.get_images_from_folder(folder),
return []
finally:
if uf: uf.close()
+
+
+# =================
+# = Pull requests =
+# =================
+def pull_requests(request):
+ return direct_to_template(request, 'manager/pull_request.html', extra_context = {
+ 'objects': models.PullRequest.objects.all()} )
# if path not in self._pending_files:
# self._pending_files.append(path)
- def _commit(self, message, user=None, key=None):
+ def _commit(self, message, user=None):
return self.repo.commit(text=message, user=user)
- def _commit2(self, message, key=None, user=None):
- """
- Commit unsynchronized data to disk.
- Arguments::
-
- - message: mercurial's changeset message
- - key: supply to sync only one key
- """
- if isinstance(message, unicode):
- message = message.encode('utf-8')
- if isinstance(user, unicode):
- user = user.encode('utf-8')
-
- commited = False
- rev = None
- files_to_add = []
- files_to_remove = []
- files_to_commit = []
-
- # first of all, add absent data and clean removed
- if key is None:
- # will commit all keys
- pending_files = self._pending_files
- else:
- if keys not in self._pending_files:
- # key isn't changed
- return None
- else:
- pending_files = [key]
- for path in pending_files:
- files_to_commit.append(path)
- if path in self.all_files():
- if not os.path.exists(os.path.join(self.real_path, path)):
- # file removed
- files_to_remove.append(path)
- else:
- # file added
- files_to_add.append(path)
- # hg add
- if files_to_add:
- self.repo.add(files_to_add)
- # hg forget
- if files_to_remove:
- self.repo.forget(files_to_remove)
- # ---- hg commit
- if files_to_commit:
- for i, f in enumerate(files_to_commit):
- if isinstance(f, unicode):
- files_to_commit[i] = f.encode('utf-8')
- matcher = match.match(self.repo.root, self.repo.root, files_to_commit, default='path')
- rev = self.repo.commit(message, user=user, match=matcher)
- commited = True
- # clean pending keys
- for key in pending_files:
- self._pending_files.remove(key)
- # if commited:
- # reread keys
- # self._keys = self.get_persisted_objects_keys()
- # return node.hex(rev)
-
- def commit(self, message, key=None, user=None, branch='default'):
+ def commit(self, message, user=None, branch='default'):
return self.in_branch(lambda: self._commit(message, key=key, user=user), branch)
def in_branch(self, action, bname='default'):
finally:
wlock.release()
- def _switch_to_branch(self, bname):
+ def _switch_to_branch(self, bname, create=True):
wlock = self.repo.wlock()
try:
current = self.repo[None].branch()
if current == bname:
return current
+ try:
+ tip = self.repo.branchtags()[bname]
+ except KeyError, ke:
+ if not create: raise ke
+
+ # create the branch on the fly
+
+ # first switch to default branch
+ default_tip = self.repo.branchtags()['default']
+ mercurial.merge.update(self.repo, default_tip, False, True, None)
+
+ # set the dirstate to new branch
+ self.repo.dirstate.setbranch(bname)
+ self._commit('Initial commit for automatic branch "%s".' % bname, user="django-admin")
+
+ # collect the new tip
+ tip = self.repo.branchtags()[bname]
- tip = self.repo.branchtags()[bname]
upstats = mercurial.merge.update(self.repo, tip, False, True, None)
return current
except KeyError, ke:
args = $.makeArray(arguments)
var hookName = args.splice(0,1)[0]
var noHookAction = args.splice(0,1)[0]
+ var result = false;
$.log('calling hook: ', hookName, 'with args: ', args);
if(this.hooks && this.hooks[hookName])
- {
- return this.hooks[hookName].apply(this, args);
- }
- else if (noHookAction instanceof Function)
- return noHookAction(args);
- else return false;
+ result = this.hooks[hookName].apply(this, args);
+ else if (noHookAction instanceof Function)
+ result = noHookAction(args);
+ return result;
}
Panel.prototype.load = function (url) {
}
Panel.prototype.refresh = function(event, data) {
+ var self = this;
reload = function() {
- $.log('hard reload for panel ', this.current_url);
- this.load(this.current_url);
+ $.log('hard reload for panel ', self.current_url);
+ self.load(self.current_url);
return true;
}
});
});
- $(document).bind('panel:contentChanged', function(event, data) {
- $('#toolbar-button-save').removeAttr('disabled');
- });
+ $(document).bind('panel:contentChanged', function() { self.onContentChanged.apply(self, arguments) });
$('#toolbar-button-save').click( function (event, data) { self.saveToBranch(); } );
+ $('#toolbar-button-commit').click( function (event, data) { self.sendPullRequest(); } );
self.rootDiv.bind('stopResize', function() { self.savePanelOptions() });
}
success: function(data, textStatus) {
if (data.result != 'ok')
$.log('save errors: ', data.errors)
- else
+ else {
self.refreshPanels(changed_panel);
- $('#toolbar-button-save').attr('disabled', 'disabled');
+ $('#toolbar-button-save').attr('disabled', 'disabled');
+ $('#toolbar-button-commit').removeAttr('disabled');
+ }
},
error: function(rq, tstat, err) {
$.log('save error', rq, tstat, err);
});
};
+Editor.prototype.onContentChanged = function(event, data) {
+ $('#toolbar-button-save').removeAttr('disabled');
+ $('#toolbar-button-commit').attr('disabled', 'disabled');
+};
+
Editor.prototype.refreshPanels = function(goodPanel) {
var self = this;
var panels = $('#' + self.rootDiv.attr('id') +' > *.panel-wrap', self.rootDiv.parent());
panels.each(function() {
var panel = $(this).data('ctrl');
- $.log(this, panel);
+ $.log('Refreshing: ', this, panel);
if ( panel.changed() )
panel.unmarkChanged();
else
});
};
+
+Editor.prototype.sendPullRequest = function () {
+ if( $('.panel-wrap.changed').length != 0)
+ alert("There are unsaved changes - can't make a pull request.");
+
+ $.ajax({
+ url: '/pull-request',
+ dataType: 'json',
+ success: function(data, textStatus) {
+ $.log('data: ' + data);
+ },
+ error: function(rq, tstat, err) {
+ $.log('commit error', rq, tstat, err);
+ },
+ type: 'POST',
+ data: {}
+ });
+}
+
$(function() {
editor = new Editor();
{% block breadcrumbs %}<a href="{% url file_list %}">Platforma Redakcyjna</a> ❯ plik {{ hash }}{% endblock breadcrumbs %}
{% block header-toolbar %}
+ <button type="button" class="toolbar-button" id="toolbar-button-commit">Commit</button>
<button type="button" class="toolbar-button" id="toolbar-button-save" disabled="disabled">Zapisz</button>
{% endblock %}
{% block maincontent %}
},
- refresh: function() {
- return false;
- },
+ //refresh: function() { }, // no support for refresh
saveInfo: function(saveInfo) {
var myInfo = {
{% if user.is_authenticated %}
-<span class="user_name">{{ user.get_full_name }}</span> |
-<a href='{% url django.contrib.auth.views.logout %}?next_page={{request.get_full_path}}'>Wyloguj</a>
+<span class="user_name">{{ user.username }}</span> |
+<a href='{% url django.contrib.auth.views.logout %}?next={{request.get_full_path}}'>Wyloguj</a>
{% else %}
-<a href='{% url django.contrib.auth.views.login %}?next_page={{request.get_full_path}}'>Logowanie</a>
+{% url django.contrib.auth.views.login as login_url %}
+{% ifnotequal login_url request.path %}
+ <a href='{% url django.contrib.auth.views.login %}?next={{request.get_full_path}}'>Logowanie</a>
+{% endifnotequal %}
+
{% endif %}
<form method="POST" action="{% url django.contrib.auth.views.login %}">
{{ form.as_p }}
<p><input type="submit" value="Login" /></p>
-<input type="hidden" name="next_page" value="{{ next_page }}" />
+<input type="hidden" name="next" value="{{ next }}" />
</form>
</div>
url(r'^editor/panel/dceditor/'+PATH_END, 'explorer.views.dceditor_panel', name='dceditor_panel'),
url(r'^editor/'+PATH_END, 'explorer.views.display_editor', name='editor_view'),
+ # Task managment
+ url(r'^manager/pull-requests$', 'explorer.views.pull_requests'),
+
# Admin panel
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/(.*)', admin.site.root),
# Authorization
- url(r'^accounts/login/$', 'django.contrib.auth.views.login', {'redirect_field_name': 'next_page'}),
- url(r'^accounts/logout$', 'django.contrib.auth.views.logout', {'next_page': '/'}), # {'redirect_field_name': 'next_page'}),
+ url(r'^accounts/login/$', 'django.contrib.auth.views.login', {'redirect_field_name': 'next'}),
+ url(r'^accounts/logout$', 'django.contrib.auth.views.logout', {'next_page': '/'}),
)