From f02d58db2f0a5f06c38d669c4b4c4fff95e55395 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Rekucki?= Date: Fri, 28 Aug 2009 16:22:38 +0200 Subject: [PATCH 1/1] =?utf8?q?Dodanie=20login=5Frequired.=20Automatyczne?= =?utf8?q?=20tworzenie=20branchy,=20przy=20pierwszym=20u=C5=BCyciu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- apps/explorer/forms.py | 2 +- apps/explorer/models.py | 16 ++++ apps/explorer/views.py | 68 ++++++++++----- lib/hg.py | 84 +++++-------------- project/static/js/editor.js | 53 ++++++++---- project/templates/explorer/editor.html | 1 + .../templates/explorer/panels/xmleditor.html | 4 +- .../templates/registration/head_login.html | 10 ++- project/templates/registration/login.html | 2 +- project/urls.py | 7 +- 10 files changed, 140 insertions(+), 107 deletions(-) diff --git a/apps/explorer/forms.py b/apps/explorer/forms.py index f7fcf07e..48b410b5 100644 --- a/apps/explorer/forms.py +++ b/apps/explorer/forms.py @@ -49,7 +49,7 @@ class DublinCoreForm(forms.Form): 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() diff --git a/apps/explorer/models.py b/apps/explorer/models.py index f867189f..10491f6a 100644 --- a/apps/explorer/models.py +++ b/apps/explorer/models.py @@ -19,6 +19,22 @@ class PanelSettings(models.Model): 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('.')) diff --git a/apps/explorer/views.py b/apps/explorer/views.py index 896ed09c..c1d8fb47 100644 --- a/apps/explorer/views.py +++ b/apps/explorer/views.py @@ -10,12 +10,30 @@ from django.contrib.auth.decorators import login_required 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): @@ -26,6 +44,7 @@ def file_list(request, repo): # Edit the file # +@ajax_login_required @with_repo def file_xml(request, repo, path): if request.method == 'POST': @@ -45,13 +64,13 @@ def file_xml(request, repo, path): 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': @@ -64,16 +83,14 @@ def file_dc(request, path, repo): 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'], @@ -83,6 +100,7 @@ def display_editor(request, path): # = Panel views = # =============== +@ajax_login_required @with_repo def xmleditor_panel(request, path, repo): form = forms.BookForm() @@ -94,12 +112,14 @@ def xmleditor_panel(request, path, repo): }) +@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) @@ -109,6 +129,7 @@ def htmleditor_panel(request, path, repo): }) +@ajax_login_required @with_repo def dceditor_panel(request, path, repo): user_branch = models.user_branch(request.user) @@ -124,6 +145,7 @@ def dceditor_panel(request, path, repo): # ================= # = 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), @@ -147,3 +169,11 @@ def _get_issues_for_file(path): 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()} ) diff --git a/lib/hg.py b/lib/hg.py index 60e9f80e..b8795b71 100644 --- a/lib/hg.py +++ b/lib/hg.py @@ -58,70 +58,10 @@ class Repository(object): # 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'): @@ -136,14 +76,30 @@ class Repository(object): 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: diff --git a/project/static/js/editor.js b/project/static/js/editor.js index f238e18b..131c8632 100644 --- a/project/static/js/editor.js +++ b/project/static/js/editor.js @@ -23,15 +23,14 @@ Panel.prototype.callHook = function() { 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) { @@ -69,9 +68,10 @@ Panel.prototype.unload = function(event, data) { } 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; } @@ -130,11 +130,10 @@ Editor.prototype.setupUI = function() { }); }); - $(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() }); } @@ -223,9 +222,11 @@ Editor.prototype.saveToBranch = function() { 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); @@ -235,13 +236,18 @@ Editor.prototype.saveToBranch = function() { }); }; +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 @@ -249,6 +255,25 @@ Editor.prototype.refreshPanels = function(goodPanel) { }); }; + +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(); diff --git a/project/templates/explorer/editor.html b/project/templates/explorer/editor.html index 7be73b34..ac496db0 100644 --- a/project/templates/explorer/editor.html +++ b/project/templates/explorer/editor.html @@ -14,6 +14,7 @@ {% block breadcrumbs %}Platforma Redakcyjna ❯ plik {{ hash }}{% endblock breadcrumbs %} {% block header-toolbar %} + {% endblock %} {% block maincontent %} diff --git a/project/templates/explorer/panels/xmleditor.html b/project/templates/explorer/panels/xmleditor.html index 7651ffb1..f93e6eaa 100644 --- a/project/templates/explorer/panels/xmleditor.html +++ b/project/templates/explorer/panels/xmleditor.html @@ -82,9 +82,7 @@ panel_hooks = { }, - refresh: function() { - return false; - }, + //refresh: function() { }, // no support for refresh saveInfo: function(saveInfo) { var myInfo = { diff --git a/project/templates/registration/head_login.html b/project/templates/registration/head_login.html index d6df7fe7..ded03e97 100644 --- a/project/templates/registration/head_login.html +++ b/project/templates/registration/head_login.html @@ -1,6 +1,10 @@ {% if user.is_authenticated %} -{{ user.get_full_name }} | -Wyloguj +{{ user.username }} | +Wyloguj {% else %} -Logowanie +{% url django.contrib.auth.views.login as login_url %} +{% ifnotequal login_url request.path %} + Logowanie +{% endifnotequal %} + {% endif %} diff --git a/project/templates/registration/login.html b/project/templates/registration/login.html index 8bf89675..810bd4ec 100644 --- a/project/templates/registration/login.html +++ b/project/templates/registration/login.html @@ -8,7 +8,7 @@
{{ form.as_p }}

- +
diff --git a/project/urls.py b/project/urls.py index 4e672f3e..22627ac6 100644 --- a/project/urls.py +++ b/project/urls.py @@ -22,13 +22,16 @@ urlpatterns = patterns('', 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': '/'}), ) -- 2.20.1