changeset 320:05b212954275

Implemented owner settings, as separete posibility to edit repositry by non administrative owner of repository
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 30 Jun 2010 15:35:10 +0200
parents c12f4d19c950
children 81e18dc718c7
files pylons_app/config/routing.py pylons_app/controllers/settings.py pylons_app/model/forms.py pylons_app/templates/base/base.html pylons_app/templates/settings/repo_settings.html pylons_app/templates/shortlog/shortlog.html pylons_app/tests/functional/test_settings.py
diffstat 7 files changed, 378 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/pylons_app/config/routing.py	Tue Jun 29 20:45:35 2010 +0200
+++ b/pylons_app/config/routing.py	Wed Jun 30 15:35:10 2010 +0200
@@ -115,4 +115,12 @@
     map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
                 controller='files', action='archivefile', revision='tip',
                 conditions=dict(function=check_repo))
+    map.connect('repo_settings_update', '/{repo_name:.*}/settings',
+                controller='settings', action="update",
+                conditions=dict(method=["PUT"], function=check_repo))
+    map.connect('repo_settings_home', '/{repo_name:.*}/settings',
+                controller='settings', action='index',
+                conditions=dict(function=check_repo))
+
+    
     return map
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pylons_app/controllers/settings.py	Wed Jun 30 15:35:10 2010 +0200
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# settings controller for pylons
+# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
+ 
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License or (at your opinion) any later version of the license.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+"""
+Created on June 30, 2010
+settings controller for pylons
+@author: marcink
+"""
+from formencode import htmlfill
+from pylons import tmpl_context as c, request, url
+from pylons.controllers.util import redirect
+from pylons.i18n.translation import _
+from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
+from pylons_app.lib.base import BaseController, render
+from pylons_app.lib.utils import invalidate_cache
+from pylons_app.model.forms import RepoSettingsForm
+from pylons_app.model.repo_model import RepoModel
+import formencode
+import logging
+import pylons_app.lib.helpers as h
+log = logging.getLogger(__name__)
+
+class SettingsController(BaseController):
+
+    @LoginRequired()
+    @HasRepoPermissionAllDecorator('repository.admin')           
+    def __before__(self):
+        super(SettingsController, self).__before__()
+        
+    def index(self, repo_name):
+        repo_model = RepoModel()
+        c.repo_info = repo = repo_model.get(repo_name)
+        if not repo:
+            h.flash(_('%s repository is not mapped to db perhaps' 
+                      ' it was created or renamed from the filesystem'
+                      ' please run the application again'
+                      ' in order to rescan repositories') % repo_name,
+                      category='error')
+        
+            return redirect(url('repos'))        
+        defaults = c.repo_info.__dict__
+        defaults.update({'user':c.repo_info.user.username})
+        c.users_array = repo_model.get_users_js()
+        
+        for p in c.repo_info.repo2perm:
+            defaults.update({'perm_%s' % p.user.username: 
+                             p.permission.permission_name})
+            
+        return htmlfill.render(
+            render('settings/repo_settings.html'),
+            defaults=defaults,
+            encoding="UTF-8",
+            force_defaults=False
+        )  
+
+    def update(self, repo_name):
+        print request.POST
+        print 'x' * 110
+        repo_model = RepoModel()
+        _form = RepoSettingsForm(edit=True)()
+        try:
+            form_result = _form.to_python(dict(request.POST))
+            repo_model.update(repo_name, form_result)
+            invalidate_cache('cached_repo_list')
+            h.flash(_('Repository %s updated succesfully' % repo_name),
+                    category='success')
+                           
+        except formencode.Invalid as errors:
+            c.repo_info = repo_model.get(repo_name)
+            c.users_array = repo_model.get_users_js()
+            errors.value.update({'user':c.repo_info.user.username})
+            c.form_errors = errors.error_dict
+            return htmlfill.render(
+                 render('admin/repos/repo_edit.html'),
+                defaults=errors.value,
+                encoding="UTF-8")
+        except Exception:
+            h.flash(_('error occured during update of repository %s') \
+                    % form_result['repo_name'], category='error')
+                    
+        return redirect(url('repo_settings_home', repo_name=repo_name))
--- a/pylons_app/model/forms.py	Tue Jun 29 20:45:35 2010 +0200
+++ b/pylons_app/model/forms.py	Wed Jun 30 15:35:10 2010 +0200
@@ -182,7 +182,15 @@
                                      state=State_obj)
                 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})            
         return value
-                
+    
+class ValidSettings(formencode.validators.FancyValidator):
+    
+    def to_python(self, value, state):
+        #settings  form can't edit user
+        if value.has_key('user'):
+            del['value']['user']
+        
+        return value                
 #===============================================================================
 # FORMS        
 #===============================================================================
@@ -240,3 +248,18 @@
         
         chained_validators = [ValidPerms]
     return _RepoForm
+
+def RepoSettingsForm(edit=False):
+    class _RepoForm(formencode.Schema):
+        allow_extra_fields = True
+        filter_extra_fields = False
+        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
+        description = UnicodeString(strip=True, min=3, not_empty=True)
+        private = StringBoolean(if_missing=False)
+        
+        chained_validators = [ValidPerms, ValidSettings]
+    return _RepoForm
+
+
+
+
--- a/pylons_app/templates/base/base.html	Tue Jun 29 20:45:35 2010 +0200
+++ b/pylons_app/templates/base/base.html	Wed Jun 30 15:35:10 2010 +0200
@@ -107,7 +107,7 @@
 	            <li ${is_current('tags')}>${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name))}</li>
 	            <li ${is_current('files')}>${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name))}</li>
 				%if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
-					<li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name))}</li>
+					<li ${is_current('settings')}>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name))}</li>
 				%endif					        
 	        </ul>
 		%else:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pylons_app/templates/settings/repo_settings.html	Wed Jun 30 15:35:10 2010 +0200
@@ -0,0 +1,240 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/base/base.html"/>
+
+<%def name="title()">
+    ${_('Repository settings')}
+</%def>
+<%def name="breadcrumbs()">
+    ${h.link_to(u'Home',h.url('/'))}
+    / 
+    ${h.link_to(c.repo_name,h.url('shortlog_home',repo_name=c.repo_name))}
+    /
+    ${_('settings')}
+</%def>
+<%def name="page_nav()">
+	${self.menu('settings')}     
+</%def>
+<%def name="main()">
+	<h2 class="no-link no-border">${_('Settings')}</h2>
+	<div>
+        ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
+        <table>
+        	<tr>
+        		<td>${_('Name')}</td>
+        		<td>${h.text('repo_name',size="28")}</td>
+        		<td>${self.get_form_error('repo_name')}</td>
+        	</tr>
+        	<tr>
+        		<td>${_('Description')}</td>
+        		<td>${h.textarea('description',cols=32,rows=5)}</td>
+        		<td>${self.get_form_error('description')}</td>
+        	</tr>
+        	<tr>
+        		<td>${_('Private')}</td>
+        		<td>${h.checkbox('private',value="True")}</td>
+        		<td>${self.get_form_error('private')}</td>
+        	</tr>
+        	<tr>
+        		<td>${_('Permissions')}</td>
+        		<td>
+        			<table>
+        				<tr>
+        					<td>${_('none')}</td>
+        					<td>${_('read')}</td>
+        					<td>${_('write')}</td>
+        					<td>${_('admin')}</td>
+        					<td>${_('user')}</td>
+        				</tr>
+        				
+        				%for r2p in c.repo_info.repo2perm:
+        					%if r2p.user.username =='default' and c.repo_info.private:
+        						<tr>
+									<td colspan="4">
+										<span style="font-size: 0.8em">${_('disabled for private repository')}</span></td>
+									<td>${r2p.user.username}</td>
+								</tr>
+							%else:
+	        				<tr id=${id(r2p.user.username)}>
+	        					<td>${h.radio('perm_%s' % r2p.user.username,'repository.none')}</td>
+	        					<td>${h.radio('perm_%s' % r2p.user.username,'repository.read')}</td>
+	        					<td>${h.radio('perm_%s' % r2p.user.username,'repository.write')}</td>
+	        					<td>${h.radio('perm_%s' % r2p.user.username,'repository.admin')}</td>
+	        					<td>${r2p.user.username}</td>
+	        					<td>
+	        					  %if r2p.user.username !='default':
+				                  	<span class="delete_icon action_button" onclick="ajaxAction(${r2p.user.user_id},${id(r2p.user.username)})">
+				                  		<script type="text/javascript">
+											function ajaxAction(user_id,field_id){
+												var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
+												var callback = { success:function(o){
+																YAHOO.util.Dom.get(String(field_id)).innerHTML = '<td colspan="6"></td>';
+															 }};
+												var postData = '_method=delete&user_id='+user_id; 
+												var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData); 
+						                	};
+										</script>       	
+				                  	</span>
+				                  %endif					
+	        					</td>
+	        				</tr>
+	        				%endif
+						%endfor
+						<%
+							if not hasattr(c,'form_errors'):
+								d = 'display:none;'
+							else:
+								d=''
+						%>
+
+        				<tr id="add_perm_input" style="${d}">
+        					<td>${h.radio('perm_new_user','repository.none')}</td>
+        					<td>${h.radio('perm_new_user','repository.read')}</td>
+        					<td>${h.radio('perm_new_user','repository.write')}</td>
+        					<td>${h.radio('perm_new_user','repository.admin')}</td>
+        					<td class='ac'>
+        						<div id="perm_ac">
+        							${h.text('perm_new_user_name',class_='yui-ac-input')}
+									<div id="perm_container"></div>
+        						</div>
+        					</td>
+        					<td>${self.get_form_error('perm_new_user_name')}</td>     					
+        				</tr>
+        				<tr>
+        					<td colspan="4">
+        						<span id="add_perm" class="add_icon" style="cursor: pointer;">
+        						${_('Add another user')}
+        						</span>
+        					</td>
+        				</tr>
+        			</table>
+        		</td>
+        		
+        	</tr>
+        	<tr>
+        		<td></td>
+        		<td>${h.submit('update','update')}</td>
+        	</tr>
+        	        	        	
+        </table>
+        ${h.end_form()}
+        <script type="text/javascript">
+        	YAHOO.util.Event.onDOMReady(function(){
+				var D = YAHOO.util.Dom;
+				YAHOO.util.Event.addListener('add_perm','click',function(){
+					D.setStyle('add_perm_input','display','');
+					D.setStyle('add_perm','opacity','0.6');
+					D.setStyle('add_perm','cursor','default');
+				});
+            });
+        </script>
+		<script type="text/javascript">    
+		YAHOO.example.FnMultipleFields = function(){
+		    var myContacts = ${c.users_array|n}
+		    
+		    // Define a custom search function for the DataSource
+		    var matchNames = function(sQuery) {
+		        // Case insensitive matching
+		        var query = sQuery.toLowerCase(),
+		            contact,
+		            i=0,
+		            l=myContacts.length,
+		            matches = [];
+		        
+		        // Match against each name of each contact
+		        for(; i<l; i++) {
+		            contact = myContacts[i];
+		            if((contact.fname.toLowerCase().indexOf(query) > -1) ||
+		                (contact.lname.toLowerCase().indexOf(query) > -1) ||
+		                (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
+		                matches[matches.length] = contact;
+		            }
+		        }
+		
+		        return matches;
+		    };
+		
+		    // Use a FunctionDataSource
+		    var oDS = new YAHOO.util.FunctionDataSource(matchNames);
+		    oDS.responseSchema = {
+		        fields: ["id", "fname", "lname", "nname"]
+		    }
+		
+		    // Instantiate AutoComplete for perms
+		    var oAC_perms = new YAHOO.widget.AutoComplete("perm_new_user_name", "perm_container", oDS);
+		    oAC_perms.useShadow = false;
+		    oAC_perms.resultTypeList = false;
+		    
+		    // Instantiate AutoComplete for owner
+		 	var oAC_owner = new YAHOO.widget.AutoComplete("user", "owner_container", oDS);
+		 	oAC_owner.useShadow = false;
+		 	oAC_owner.resultTypeList = false;
+		    
+		    
+		    // Custom formatter to highlight the matching letters
+		    var custom_formatter = function(oResultData, sQuery, sResultMatch) {
+		        var query = sQuery.toLowerCase(),
+		            fname = oResultData.fname,
+		            lname = oResultData.lname,
+		            nname = oResultData.nname || "", // Guard against null value
+		            query = sQuery.toLowerCase(),
+		            fnameMatchIndex = fname.toLowerCase().indexOf(query),
+		            lnameMatchIndex = lname.toLowerCase().indexOf(query),
+		            nnameMatchIndex = nname.toLowerCase().indexOf(query),
+		            displayfname, displaylname, displaynname;
+		            
+		        if(fnameMatchIndex > -1) {
+		            displayfname = highlightMatch(fname, query, fnameMatchIndex);
+		        }
+		        else {
+		            displayfname = fname;
+		        }
+		
+		        if(lnameMatchIndex > -1) {
+		            displaylname = highlightMatch(lname, query, lnameMatchIndex);
+		        }
+		        else {
+		            displaylname = lname;
+		        }
+		
+		        if(nnameMatchIndex > -1) {
+		            displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
+		        }
+		        else {
+		            displaynname = nname ? "(" + nname + ")" : "";
+		        }
+		
+		        return displayfname + " " + displaylname + " " + displaynname;
+		        
+		    };
+		    oAC_perms.formatResult = custom_formatter; 
+		    oAC_owner.formatResult = custom_formatter;
+		    			    
+		    // Helper function for the formatter
+		    var highlightMatch = function(full, snippet, matchindex) {
+		        return full.substring(0, matchindex) + 
+		                "<span class='match'>" + 
+		                full.substr(matchindex, snippet.length) + 
+		                "</span>" +
+		                full.substring(matchindex + snippet.length);
+		    };
+		
+		    var myHandler = function(sType, aArgs) {
+		        var myAC = aArgs[0]; // reference back to the AC instance
+		        var elLI = aArgs[1]; // reference to the selected LI element
+		        var oData = aArgs[2]; // object literal of selected item's result data
+		        myAC.getInputEl().value = oData.nname;
+		    };
+
+		    oAC_perms.itemSelectEvent.subscribe(myHandler);
+		    oAC_owner.itemSelectEvent.subscribe(myHandler);
+		    
+		    return {
+		        oDS: oDS,
+		        oAC_perms: oAC_perms,
+		        oAC_owner: oAC_owner, 
+		    };
+		}();
+		    
+		</script>        
+    </div>
+</%def>   
--- a/pylons_app/templates/shortlog/shortlog.html	Tue Jun 29 20:45:35 2010 +0200
+++ b/pylons_app/templates/shortlog/shortlog.html	Wed Jun 30 15:35:10 2010 +0200
@@ -1,3 +1,4 @@
+## -*- coding: utf-8 -*-
 <%inherit file="/base/base.html"/>
 
 <%def name="title()">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pylons_app/tests/functional/test_settings.py	Wed Jun 30 15:35:10 2010 +0200
@@ -0,0 +1,7 @@
+from pylons_app.tests import *
+
+class TestSettingsController(TestController):
+
+    def test_index(self):
+        response = self.app.get(url(controller='settings', action='index'))
+        # Test response...