You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2016/11/08 19:26:23 UTC
[4/4] allura git commit: [#8135] Make category selection awesome
[#8135] Make category selection awesome
* Omit unnecessary top-level category name
* Nicer separator characters
* Make drop-downs searchable
* Configurable order of categories
* Configurable help text for each
* Configurable recommended choices for each
Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/07b45f37
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/07b45f37
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/07b45f37
Branch: refs/heads/db/8135
Commit: 07b45f37cd4794002d7ac5f9941af3d4290bc6eb
Parents: 4d28325
Author: Dave Brondsema <da...@brondsema.net>
Authored: Wed Oct 12 12:47:50 2016 -0400
Committer: Dave Brondsema <da...@brondsema.net>
Committed: Tue Nov 8 14:26:06 2016 -0500
----------------------------------------------------------------------
Allura/allura/controllers/project.py | 1 -
Allura/allura/ext/admin/admin_main.py | 30 ++++--
.../ext/admin/templates/project_trove.html | 107 +++++++++++++++----
Allura/allura/model/project.py | 12 ++-
Allura/allura/tests/functional/test_admin.py | 7 +-
.../tests/functional/test_neighborhood.py | 5 +-
Allura/development.ini | 4 +
7 files changed, 129 insertions(+), 37 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 5e9a510..5f956d9 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -60,7 +60,6 @@ class W:
add_project = plugin.ProjectRegistrationProvider.get().add_project_widget(antispam=True)
page_list = ffw.PageList()
page_size = ffw.PageSize()
- project_select = ffw.NeighborhoodProjectSelect
neighborhood_overview_form = ff.NeighborhoodOverviewForm()
award_grant_form = ff.AwardGrantForm
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 3759098..1bdc58b 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -16,10 +16,11 @@
# under the License.
import logging
+from collections import OrderedDict
from datetime import datetime
from urlparse import urlparse
import json
-from operator import itemgetter
+from operator import itemgetter, attrgetter
import pkg_resources
from pylons import tmpl_context as c, app_globals as g, response
from pylons import request
@@ -253,13 +254,24 @@ class ProjectAdminController(BaseController):
@expose('jinja:allura.ext.admin:templates/project_trove.html')
def trove(self):
c.label_edit = W.label_edit
- base_troves = M.TroveCategory.query.find(
- dict(trove_parent_id=0)).sort('fullname').all()
- topic_trove = M.TroveCategory.query.get(
- trove_parent_id=0, shortname='topic')
- license_trove = M.TroveCategory.query.get(
- trove_parent_id=0, shortname='license')
- return dict(base_troves=base_troves, license_trove=license_trove, topic_trove=topic_trove)
+ base_troves_by_name = {t.shortname: t
+ for t in M.TroveCategory.query.find(dict(trove_parent_id=0))}
+ first_troves = aslist(config.get('trovecategories.admin.order', 'topic,license,os'), ',')
+ base_troves = [
+ base_troves_by_name.pop(t) for t in first_troves
+ ] + sorted(base_troves_by_name.values(), key=attrgetter('fullname'))
+
+ trove_recommendations = {}
+ for trove in base_troves:
+ config_name = 'trovecategories.admin.recommended.{}'.format(trove.shortname)
+ recommendation_pairs = aslist(config.get(config_name, []), ',')
+ trove_recommendations[trove.shortname] = OrderedDict()
+ for pair in recommendation_pairs:
+ trove_id, label = pair.split('=')
+ trove_recommendations[trove.shortname][trove_id] = label
+
+ return dict(base_troves=base_troves,
+ trove_recommendations=trove_recommendations)
@expose('jinja:allura.ext.admin:templates/project_tools_moved.html')
def tools_moved(self, **kw):
@@ -472,7 +484,7 @@ class ProjectAdminController(BaseController):
def add_trove_js(self, type, new_trove, **kw):
require_access(c.project, 'update')
trove_obj, error_msg = self._add_trove(type, new_trove)
- return dict(trove_full_path=trove_obj.fullpath, trove_cat_id=trove_obj.trove_cat_id, error_msg=error_msg)
+ return dict(trove_full_path=trove_obj.fullpath_within_type, trove_cat_id=trove_obj.trove_cat_id, error_msg=error_msg)
@expose()
@require_post()
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/allura/ext/admin/templates/project_trove.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/templates/project_trove.html b/Allura/allura/ext/admin/templates/project_trove.html
index dd7bd51..81173cf 100644
--- a/Allura/allura/ext/admin/templates/project_trove.html
+++ b/Allura/allura/ext/admin/templates/project_trove.html
@@ -24,10 +24,17 @@
{% macro show_trove_base_cat(base) %}
<h3>{{base.fullname}}</h3>
+ {% set help_text = config.get('trovecategories.admin.help.'+base.shortname, '') %}
+ {% if help_text %}
+ <div class="grid-19">
+ {{ help_text|safe }}
+ <br><br>
+ </div>
+ {% endif %}
<div id="trove_existing_{{base.shortname}}" class="trove_existing grid-19">
{% for cat in c.project.troves_by_type(base.shortname)|sort(attribute='fullpath') %}
<div style="clear: both">
- <span class="trove_fullpath">{{cat.fullpath}}</span>
+ <span class="trove_fullpath">{{cat.fullpath_within_type}}</span>
<form id="delete_trove_{{base.shortname}}_{{cat.trove_cat_id}}"
action="delete_trove" method="post" class="trove_deleter">
<input type="hidden" name="type" value="{{base.shortname}}">
@@ -41,14 +48,27 @@
{% endfor %}
</div>
<div class="grid-19 trove_add_container">
+ {% if trove_recommendations[base.shortname] %}
+ Recommended to choose from:
+ {% for trove_id, label in trove_recommendations[base.shortname].iteritems() %}
+ <a href="#" data-id="{{ trove_id }}" data-trove="{{ base.shortname }}" class="recommended_trove"><i class="fa fa-plus-circle"></i> {{ label }}</a>
+ {% endfor %}
+ <br>
+ Or <a href="#" class="choose_other">choose from other options...</a>
+ {% else %}
+ <label for="new_trove_{{base.shortname}}">Add a new {{base.fullname}} category:</label>
+ <br>
+ {% endif %}
+
<form id="add_trove_{{base.shortname}}"
- action="add_trove" method="post" class="trove_adder">
+ action="add_trove" method="post" class="trove_adder"
+ {% if trove_recommendations[base.shortname] %}style="display:none"{% endif -%}
+ >
<input type="hidden" name="type" value="{{base.shortname}}">
- <label for="new_trove_{{base.shortname}}">Add a new {{base.fullname}} category:</label>
- <br>
- <select name="new_trove" id="new_trove_{{base.shortname}}">
+ <select name="new_trove" id="new_trove_{{base.shortname}}" data-placeholder="Choose one...">
+ <option value=""></option>
{% for cat in base.children if not cat.parent_only %}
- <option value="{{cat.trove_cat_id}}">{{cat.fullpath}}</option>
+ <option value="{{cat.trove_cat_id}}">{{cat.fullpath_within_type}}</option>
{% endfor %}
</select>
<br>
@@ -63,6 +83,7 @@
<div class="notice">This project has been deleted and is not visible to non-admin users</div>
{% endif %}
+ <div class="project_labels">
<h3>Project Labels</h3>
<div class="grid-19 trove_add_container">
<form method="POST" class="can-retry" action="update_labels" id="label_edit_form">
@@ -72,23 +93,22 @@
{{lib.csrf_token()}}
</form>
</div>
- {{show_trove_base_cat(topic_trove)}}
- {{show_trove_base_cat(license_trove)}}
- {% for base in base_troves if base.shortname != 'topic' and base.shortname != 'license' %}
+ </div>
+ {% for base in base_troves %}
{{show_trove_base_cat(base)}}
{% endfor %}
{% endblock %}
+{% do g.register_forge_js('js/chosen.jquery.min.js') %}
+{% do g.register_forge_css('css/chosen.min.css') %}
+
{% block extra_js %}
<script type="text/javascript">
$(document).ready(function () {
- var session_id = $('input[name=_session_id]').val();
- var del_btn = '<a href="#" class="del_btn" title="Delete"><b data-icon="{{g.icons["delete"].char}}" class="ico {{g.icons["delete"].css}}"></b></a>';
- $('form.trove_adder').submit(function(evt){
- evt.preventDefault();
- var $this = $(this);
- var type = $this.find('input[name=type]').val();
- var new_id = $this.find('select').last().val();
+ var chosen_opts = {search_contains:true};
+ $('.trove_add_container form:visible select').chosen(chosen_opts);
+
+ function add_trove(session_id, type, new_id) {
$.post('add_trove_js',{
_session_id:session_id,
type:type,
@@ -116,6 +136,16 @@
}
}
});
+ }
+
+ var session_id = $('input[name=_session_id]').val();
+ var del_btn = '<a href="#" class="del_btn" title="Delete"><b data-icon="{{g.icons["delete"].char}}" class="ico {{g.icons["delete"].css}}"></b></a>';
+ $('form.trove_adder').submit(function(evt){
+ evt.preventDefault();
+ var $this = $(this);
+ var type = $this.find('input[name=type]').val();
+ var new_id = $this.find('select').last().val();
+ add_trove(session_id, type, new_id);
});
$('form.trove_deleter').each(function(){
$(this).find('input[type="submit"]').remove();
@@ -136,15 +166,24 @@
}
});
});
+ $('a.choose_other').on('click', function(e){
+ e.preventDefault();
+ var $form = $(this).next('form');
+ $form.show();
+ $('select', $form).chosen(chosen_opts);
+ });
+ $('a.recommended_trove').on('click', function(e) {
+ e.preventDefault();
+ var type = $(this).data('trove');
+ var new_id = $(this).data('id');
+ add_trove(session_id, type, new_id);
+ })
});
</script>
{% endblock %}
{% block extra_css %}
<style type="text/css">
- .trove_adder select {
- font: 90% sans-serif;
- }
.trove_deleter{
display:inline;
}
@@ -157,11 +196,39 @@
.trove_existing{
margin-bottom: 1em;
}
- .trove_add_container{
+ .trove_add_container {
margin-bottom: 1em;
padding-bottom: 1em;
border: 0 solid #ccc;
border-width: 0 0 1px 0;
}
+ .recommended_trove {
+ margin-right: 0.5em;
+ }
+
+ /* for Chosen plugin to display well. Super-long thread with other possible options: https://github.com/harvesthq/chosen/issues/86 */
+ .trove_add_container{
+ overflow: visible;
+ }
+ :not(.project_labels) > .trove_add_container:last-of-type {
+ margin-bottom: 250px;
+ }
+ /* for Chosen plugin, use Font-Awesome for icons instead of their sprite
+ Because when we have debug=False in the .ini file, the CSS concatenation makes the path to the sprite incorrect */
+ .chosen-container-single .chosen-search:after {
+ font-family: FontAwesome;
+ content: "\f002"; /* fa-search */
+ position: relative;
+ left: -1.5em;
+ }
+ .chosen-container-single .chosen-single div b {
+ background: none; /* cancel out existing sprite, so its not duplicated */
+ }
+ .chosen-container-single .chosen-single div b:after {
+ content: "\u25be";
+ }
+ .chosen-container-single.chosen-with-drop .chosen-single div b:after {
+ content: "\u25b4";
+ }
</style>
{% endblock %}
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 963145e..3b36ea2 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -1,3 +1,4 @@
+# coding=utf-8
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -131,11 +132,13 @@ class TroveCategory(MappedClass):
@property
def subcategories(self):
- return self.query.find(dict(trove_parent_id=self.trove_cat_id)).sort('fullname').all()
+ return sorted(self.query.find(dict(trove_parent_id=self.trove_cat_id)).all(),
+ key=lambda t: t.fullname.lower())
@property
def children(self):
- return self.query.find({'fullpath': re.compile('^' + re.escape(self.fullpath) + ' ::')}).sort('fullpath')
+ return sorted(self.query.find({'fullpath': re.compile('^' + re.escape(self.fullpath) + ' ::')}).all(),
+ key=lambda t: t.fullpath.lower())
@property
def type(self):
@@ -162,6 +165,11 @@ class TroveCategory(MappedClass):
crumbs.append((trove.fullname, url))
return crumbs
+ @property
+ def fullpath_within_type(self):
+ 'remove first section of full path, and use nicer separator'
+ return u' � '.join(self.fullpath.split(' :: ')[1:])
+
def __json__(self):
return dict(
id=self.trove_cat_id,
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/allura/tests/functional/test_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index f5f49e9..98dda1b 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -1,3 +1,4 @@
+# coding=utf-8
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -548,7 +549,7 @@ class TestProjectAdmin(TestController):
r = self.app.get('/admin/trove')
assert 'No Database Environment categories have been selected.' in r
- assert '<span class="trove_fullpath">Database Environment :: Database API</span>' not in r
+ assert '<span class="trove_fullpath">Database API</span>' not in r
# add a cat
with audits('add trove root_database: Database Environment :: Database API'):
form = r.forms['add_trove_root_database']
@@ -556,13 +557,13 @@ class TestProjectAdmin(TestController):
r = form.submit().follow()
# make sure it worked
assert 'No Database Environment categories have been selected.' not in r
- assert '<span class="trove_fullpath">Database Environment :: Database API :: Python Database API</span>' in r
+ assert u'<span class="trove_fullpath">Database API � Python Database API</span>' in r
# delete the cat
with audits('remove trove root_database: Database Environment :: Database API'):
r = r.forms['delete_trove_root_database_506'].submit().follow()
# make sure it worked
assert 'No Database Environment categories have been selected.' in r
- assert '<span class="trove_fullpath">Database Environment :: Database API :: Python Database API</span>' not in r
+ assert u'<span class="trove_fullpath">Database API � Python Database API</span>' not in r
def test_add_remove_label(self):
setup_trove_categories()
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/allura/tests/functional/test_neighborhood.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index fb48faa..e11dbac 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -1,3 +1,4 @@
+# coding=utf-8
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -741,8 +742,8 @@ class TestNeighborhood(TestController):
# check the labels and trove cats
r = self.app.get('/adobe/testtemp/admin/trove')
assert 'mmi' in r
- assert 'Topic :: Communications :: Telephony' in r
- assert 'Development Status :: 5 - Production/Stable' in r
+ assert u'Communications � Telephony' in r
+ assert '5 - Production/Stable' in r
# check the wiki text
r = self.app.get('/adobe/testtemp/wiki/').follow()
assert "My home text!" in r
http://git-wip-us.apache.org/repos/asf/allura/blob/07b45f37/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 6e9c1d3..b5d6072 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -427,6 +427,10 @@ trovecategories.enableediting = true
; Site admins only:
;trovecategories.enableediting = admin
+trovecategories.admin.recommended.license = 188=MIT,401=Apache,679=GPL,680=LGPL,670=AGPL
+trovecategories.admin.recommended.os = 65=Windows,309=Mac OSX,201=Linux,728=Android,780=iOS
+trovecategories.admin.help.license = For help choosing a license, visit <a href="http://choosealicense.com/">http://choosealicense.com/</a>
+
; ActivityStream
activitystream.master = mongodb://127.0.0.1:27017
activitystream.database = activitystream