changeset 2639:85b9375a12c8

merged
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Thu, 03 Jul 2014 22:00:12 +0200
parents 466093278e4d (current diff) 74abb72ec251 (diff)
children b27a0c43dad0
files
diffstat 12 files changed, 256 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/apps/frontend/views.py	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/apps/frontend/views.py	Thu Jul 03 22:00:12 2014 +0200
@@ -40,6 +40,7 @@
 
 from whoosh.query import Term, Prefix, And, Or, DateRange, Every
 from whoosh.analysis import StandardAnalyzer
+from whoosh import sorting
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -2215,18 +2216,32 @@
     terms = [Term(ITEMTYPE, ITEMTYPE_TICKET)]
     if query:
         terms.append(qp.parse(query))
+
     if status == u'open':
         terms.append(Term(CLOSED, False))
     elif status == u'closed':
         terms.append(Term(CLOSED, True))
+
+    selected_tags = set(request.args.getlist(u'selected_tags'))
+    terms.extend(Term(TAGS, tag) for tag in selected_tags)
     q = And(terms)
 
     with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher:
-        results = searcher.search(q, limit=None)
+        sortedby = []
+        time_sorting = request.args.get(u'time_sorting')
+        if time_sorting == u'new':
+            sortedby.append(sorting.FieldFacet(u'mtime', reverse=True))
+        elif time_sorting == u'old':
+            sortedby.append(sorting.FieldFacet(u'mtime', reverse=False))
+        results = searcher.search(q, limit=None, sortedby=sortedby)
+        tags = list(searcher.field_terms(TAGS))
         return render_template('tickets.html',
                                results=results,
                                query=query,
                                status=status,
+                               tags=tags,
+                               selected_tags=selected_tags,
+                               time_sorting=time_sorting,
         )
 
 
--- a/MoinMoin/items/ticket.py	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/items/ticket.py	Thu Jul 03 22:00:12 2014 +0200
@@ -27,6 +27,7 @@
 from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 from MoinMoin.items import Item, Contentful, register, BaseModifyForm
 from MoinMoin.items.content import NonExistentContent
+from MoinMoin.constants.keys import LATEST_REVS, TAGS
 
 
 ITEMTYPE_TICKET = u'ticket'
@@ -34,7 +35,7 @@
 USER_QUERY = Term(CONTENTTYPE, CONTENTTYPE_USER)
 TICKET_QUERY = Term(ITEMTYPE, ITEMTYPE_TICKET)
 
-Rating = SmallNatural.using(optional=False).with_properties(lower=1, upper=5)
+Rating = SmallNatural.using(optional=True).with_properties(lower=1, upper=5)
 
 
 def get_itemid_short_summary(rev):
@@ -193,11 +194,14 @@
 
         # XXX When creating new item, suppress the "foo doesn't exist. Create it?" dummy content
         data_rendered = None if is_new else Markup(self.content._render_data())
+        with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher:
+            suggested_tags = list(searcher.field_terms(TAGS))
 
-        return render_template(self.submit_template if is_new else self.modify_template,
-                               is_new=is_new,
-                               closed=closed,
-                               item_name=self.name,
-                               data_rendered=data_rendered,
-                               form=form,
-                              )
+            return render_template(self.submit_template if is_new else self.modify_template,
+                                   is_new=is_new,
+                                   closed=closed,
+                                   item_name=self.name,
+                                   data_rendered=data_rendered,
+                                   form=form,
+                                   suggested_tags=suggested_tags,
+                                  )
--- a/MoinMoin/static/css/stylus/ticket.styl	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/static/css/stylus/ticket.styl	Thu Jul 03 22:00:12 2014 +0200
@@ -2,8 +2,99 @@
     dd
         width 80%
         margin-top 0.1em
-        input
-            width auto
     dt
         width 15%
         margin-top 0.1em
+
+fields = (effort difficulty severity priority)
+remove_display_radio()
+    ids = ()
+    for level in 1..5
+        for field in fields
+            id = '#f_meta_%s_%s' % (field level)
+            push(ids, unquote(id))
+    ids = join(', ', ids)
+
+{unquote(remove_display_radio())}
+    display none
+
+css_for_label()
+    ids = ()
+    for level in 1..5
+        for field in fields
+            id = '#f_meta_%s_%s + label.moin-inline-label' % (field level)
+            push(ids, unquote(id))
+    ids = join(', ', ids)
+
+{unquote(css_for_label())}
+    padding-left 25px
+    padding-right 10px
+    height 20px
+    display inline-block
+    line-height 20px
+    background-repeat no-repeat
+    background-position 0 0
+    font-size 20px
+    vertical-align middle
+    cursor pointer
+
+css_for_checked_label()
+    ids = ()
+    for level in 1..5
+        for field in fields
+            id = '#f_meta_%s_%s:checked + label.moin-inline-label' % (field level)
+            push(ids, unquote(id))
+    ids = join(', ', ids)
+
+{unquote(css_for_checked_label())}
+    background-position 0 -20px
+
+label.moin-inline-label
+    background-image url('../img/radiobutton.png');
+
+#f_meta_tags, #f_meta_summary
+    -webkit-transition all 0.30s ease-in-out
+    -moz-transition all 0.30s ease-in-out
+    -ms-transition all 0.30s ease-in-out
+    -o-transition all 0.30s ease-in-out
+    outline none
+    width 200px
+    margin-top 20px
+    margin-bottom 20px
+    height 25px
+    padding 3px 0px 3px 3px
+    margin 5px 1px 3px 0px
+    border 1px solid #DDDDDD
+
+#f_meta_tags:focus, #f_meta_summary:focus
+    box-shadow 0 0 5px rgba(81, 203, 238, 1)
+    margin-top 20px
+    margin-bottom 20px
+    height 25px
+    padding 3px 0px 3px 3px
+    margin 5px 1px 3px 0px
+    border 1px solid rgba(81, 203, 238, 1)
+
+input[type=submit], #f_submit
+    margin-top 10px
+    margin-bottom 20px
+    width 150px
+    background-color #1b436d
+    height 40px
+    color #ffffff
+    border 2px solid
+
+select
+    display inline-block
+    margin-bottom 0
+    font-weight normal
+    text-align center
+    vertical-align middle
+    cursor pointer
+    border 1px solid
+    white-space nowrap
+    padding 6px 12px
+    font-size 14px
+    line-height 1.42857143
+    border-radius 4px
+    -webkit-user-select none
--- a/MoinMoin/static/css/ticket.css	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/static/css/ticket.css	Thu Jul 03 22:00:12 2014 +0200
@@ -1,3 +1,10 @@
-#moin-ticket-form dd{width:80%;margin-top:.1em;}
-#moin-ticket-form dd input{width:auto}
+#moin-ticket-form dd{width:80%;margin-top:.1em}
 #moin-ticket-form dt{width:15%;margin-top:.1em}
+#f_meta_effort_1, #f_meta_difficulty_1, #f_meta_severity_1, #f_meta_priority_1, #f_meta_effort_2, #f_meta_difficulty_2, #f_meta_severity_2, #f_meta_priority_2, #f_meta_effort_3, #f_meta_difficulty_3, #f_meta_severity_3, #f_meta_priority_3, #f_meta_effort_4, #f_meta_difficulty_4, #f_meta_severity_4, #f_meta_priority_4, #f_meta_effort_5, #f_meta_difficulty_5, #f_meta_severity_5, #f_meta_priority_5{display:none}
+#f_meta_effort_1 + label.moin-inline-label, #f_meta_difficulty_1 + label.moin-inline-label, #f_meta_severity_1 + label.moin-inline-label, #f_meta_priority_1 + label.moin-inline-label, #f_meta_effort_2 + label.moin-inline-label, #f_meta_difficulty_2 + label.moin-inline-label, #f_meta_severity_2 + label.moin-inline-label, #f_meta_priority_2 + label.moin-inline-label, #f_meta_effort_3 + label.moin-inline-label, #f_meta_difficulty_3 + label.moin-inline-label, #f_meta_severity_3 + label.moin-inline-label, #f_meta_priority_3 + label.moin-inline-label, #f_meta_effort_4 + label.moin-inline-label, #f_meta_difficulty_4 + label.moin-inline-label, #f_meta_severity_4 + label.moin-inline-label, #f_meta_priority_4 + label.moin-inline-label, #f_meta_effort_5 + label.moin-inline-label, #f_meta_difficulty_5 + label.moin-inline-label, #f_meta_severity_5 + label.moin-inline-label, #f_meta_priority_5 + label.moin-inline-label{padding-left:25px;padding-right:10px;height:20px;display:inline-block;line-height:20px;background-repeat:no-repeat;background-position:0 0;font-size:20px;vertical-align:middle;cursor:pointer}
+#f_meta_effort_1:checked + label.moin-inline-label, #f_meta_difficulty_1:checked + label.moin-inline-label, #f_meta_severity_1:checked + label.moin-inline-label, #f_meta_priority_1:checked + label.moin-inline-label, #f_meta_effort_2:checked + label.moin-inline-label, #f_meta_difficulty_2:checked + label.moin-inline-label, #f_meta_severity_2:checked + label.moin-inline-label, #f_meta_priority_2:checked + label.moin-inline-label, #f_meta_effort_3:checked + label.moin-inline-label, #f_meta_difficulty_3:checked + label.moin-inline-label, #f_meta_severity_3:checked + label.moin-inline-label, #f_meta_priority_3:checked + label.moin-inline-label, #f_meta_effort_4:checked + label.moin-inline-label, #f_meta_difficulty_4:checked + label.moin-inline-label, #f_meta_severity_4:checked + label.moin-inline-label, #f_meta_priority_4:checked + label.moin-inline-label, #f_meta_effort_5:checked + label.moin-inline-label, #f_meta_difficulty_5:checked + label.moin-inline-label, #f_meta_severity_5:checked + label.moin-inline-label, #f_meta_priority_5:checked + label.moin-inline-label{background-position:0 -20px}
+label.moin-inline-label{background-image:url("../img/radiobutton.png")}
+#f_meta_tags,#f_meta_summary{-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;outline:none;width:200px;margin-top:20px;margin-bottom:20px;height:25px;padding:3px 0 3px 3px;margin:5px 1px 3px 0;border:1px solid #ddd}
+#f_meta_tags:focus,#f_meta_summary:focus{box-shadow:0 0 5px #51cbee;margin-top:20px;margin-bottom:20px;height:25px;padding:3px 0 3px 3px;margin:5px 1px 3px 0;border:1px solid #51cbee}
+input[type=submit],#f_submit{margin-top:10px;margin-bottom:20px;width:150px;background-color:#1b436d;height:40px;color:#fff;border:2px solid}
+select{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;border:1px solid;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none}
Binary file MoinMoin/static/img/radiobutton.png has changed
--- a/MoinMoin/static/js/tickets.js	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/static/js/tickets.js	Thu Jul 03 22:00:12 2014 +0200
@@ -1,41 +1,15 @@
 $(document).ready(function(){
 
-    // adds filter option in /+tickets view
-    // User can click on on any td element in the table to filter according to the value in that
-    function filter(selected_column) {
-        return function () {
-            var table = document.getElementById('ticket-list');
-            var cols = table.rows[0].cells.length;
-            var columns = table.getElementsByTagName("td");
-            var rows = table.getElementsByTagName("tr");
-            var selected_row = parseInt(selected_column/cols) + 1;
-            selected_column = selected_column%cols;
-            var data_to_filter = table.rows[selected_row].cells[selected_column].innerHTML;
-            var len = columns.length;
-
-            for ( var i = len-1; i >= 0; i-- ) {
-                if( i%cols == selected_column ) {
-                    var row = parseInt(i/cols) + 1;
-                    var data = table.rows[row].cells[selected_column].innerHTML;
-                    if( data != data_to_filter ) {
-                        rows[row].remove();
-                        i = i - selected_column;
-                    }
-                }
-            }
-        };
+    $("table").tablesorter({
+    widgets: ["resizable"],
+    widgetOptions : {
+        // css class name applied to the sticky header
+        resizable : false
+    },
+    headers: {
+        // remove sorting option for tags column
+        7: { sorter: false }
     }
-
-    var table = document.getElementById('ticket-list');
-    var cols = table.rows[0].cells.length;
-    var columns = table.getElementsByTagName("td");
-    for (var  i = 0; i < columns.length; i++ ) {
-    // listener not required for Summary and Itemid columns
-        if ( i%cols != 0 && i%cols != 1 ) {
-            columns[i].onclick = filter(i);
-        }
-    }
-
-    $("#ticket-list").tablesorter();
+  });
 
 });
--- a/MoinMoin/templates/ticket/base.html	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/templates/ticket/base.html	Thu Jul 03 22:00:12 2014 +0200
@@ -9,6 +9,13 @@
         'severity',
         'priority',
         'tags',
+        ] %}
+        {{ forms.render(form['meta'][e]) }}
+    {% endfor %}
+{% endmacro %}
+
+{% macro render_selectlists() %}
+    {% for e in [
         'assigned_to',
         'superseded_by',
         'depends_on',
--- a/MoinMoin/templates/ticket/modify.html	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/templates/ticket/modify.html	Thu Jul 03 22:00:12 2014 +0200
@@ -14,6 +14,11 @@
     <dl>
     {{ forms.render_errors(form) }}
     {{ render_meta() }}
+    {{ _("Suggested Tags: ") }}
+    {% for suggested_tag in suggested_tags %}
+        {{ suggested_tag }}
+    {% endfor %}
+    {{ render_selectlists() }}
     </dl>
 
     {{ forms.render(form['submit']) }}
--- a/MoinMoin/templates/ticket/submit.html	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/templates/ticket/submit.html	Thu Jul 03 22:00:12 2014 +0200
@@ -15,6 +15,11 @@
     <dl>
     {{ forms.render_errors(form) }}
     {{ render_meta() }}
+    {{ _("Suggested Tags: ") }}
+    {% for suggested_tag in suggested_tags %}
+        {{ suggested_tag }}
+    {% endfor %}
+    {{ render_selectlists() }}
     </dl>
 
     {{ forms.render_submit(form) }}
--- a/MoinMoin/templates/tickets.html	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/templates/tickets.html	Thu Jul 03 22:00:12 2014 +0200
@@ -13,14 +13,38 @@
 {% set status_values = ['all', 'open', 'closed']  %}
 {{_("Filter:")}}
 {% for status_value in status_values %}
-    <form action="{{ url_for('frontend.tickets') }}" method="post" class='moin-ticketsearch-form'>
+    <form action="{{ url_for('frontend.tickets', selected_tags=list(selected_tags), time_sorting=time_sorting if time_sorting) }}" method="post" class='moin-ticketsearch-form'>
         <input type="hidden" name="q" value="{{ query if query }}" >
         &nbsp;<input type="hidden" name="status" value="{{ status_value }}">
         <input type="submit" value="{{ status_value.capitalize() }}" title="{{ _('Show %(status)s tickets', status=status_value) }}" class="{{ 'ticket-query-button active' if status == status_value  else 'ticket-query-button'}}">
     </form>
 {% endfor %}
 
-<form action="{{ url_for('frontend.tickets') }}" method="post">
+<div class="tickets-sort-button dropdown">
+    <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
+        {% if time_sorting == 'new' %}
+            {% set sortedby = 'Recently updated' %}
+        {% elif time_sorting == 'old' %}
+            {% set sortedby = 'Least recently updated' %}
+        {% endif %}
+        {{_("Sort: %(sortedby)s", sortedby=sortedby if sortedby)}}
+    <span class="caret"></span>
+    </button>
+    <ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
+        <li role="presentation">
+            <a role="menuitem" tabindex="-1" href="{{ url_for('frontend.tickets', selected_tags=list(selected_tags), time_sorting='new') }}">
+                {{_("Recently updated")}}
+            </a>
+        </li>
+        <li role="presentation">
+            <a role="menuitem" tabindex="-1" href="{{ url_for('frontend.tickets', selected_tags=list(selected_tags), time_sorting='old') }}">
+                {{_("Least recently updated")}}
+            </a>
+        </li>
+    </ul>
+</div>
+
+<form action="{{ url_for('frontend.tickets',selected_tags=list(selected_tags), time_sorting=time_sorting if time_sorting) }}" method="post">
     <input type="hidden" name="status" value="{{ status }}">
     <input type="text" name="q" value="{{ query if query }}" id="moin-ticketsearch-query" class="form-control" placeholder="{{_('Find tickets')}}">
 </form>
@@ -49,23 +73,23 @@
                 </a>
             <td>
                 <a href="{{  url_for_item(result['itemid'], field='itemid', namespace=result['namespace'])}}" title="{{ _('ITEMID: %(itemid)s', itemid=result['itemid'])}}">
-                    {{ result['summary'][:50] }}
+                    {{ result['summary'] }}
                 </a>
             </td>
-            <td title="{{ _('Filter by status: %(status)s', status='Closed' if result['closed'] else 'Open') }}">
+            <td>
                 {{ _("Closed") if result['closed'] else _("Open") }}
             </td>
-            <td title="{{ _('Filter by effort: %(effort)d', effort=result['effort']) }}">
-                {{ result['effort'] }}
+            <td>
+                {{ result.get('effort', '') }}
             </td>
-            <td title="{{ _('Filter by difficulty: %(difficulty)d', difficulty=result['difficulty']) }}">
-                {{ result['difficulty'] }}
+            <td>
+                {{ result.get('difficulty', '') }}
             </td>
-            <td title="{{ _('Filter by severity: %(severity)d', severity=result['severity']) }}">
-                {{ result['severity'] }}
+            <td>
+                {{ result.get('severity', '') }}
             </td>
-            <td title="{{ _('Filter by priority: %(priority)d', priority=result['priority']) }}">
-                {{ result['priority'] }}
+            <td>
+                {{ result.get('priority', '') }}
             </td>
             <td>
                 {% for tag in result['tags'] %}
@@ -76,9 +100,26 @@
     {% endfor %}
     </tbody>
     </table>
+
+    <p id="ticket-table-tip">
+        {{_("Tip: Sort multiple columns simultaneously by holding down the Shift key and clicking a second, third or even fourth column header!")}}
+    </p>
 {% else %}
     {{ _("No Ticket found.") }}
 {% endif %}
+
+<h3>{{_("Tags")}}</h3>
+{% for tag in tags %}
+    {# If user clicks on the tag then it gets selected if it is not already selected #}
+    {# If that tag is already selected then it gets deselected #}
+    {% set selected_tags_value = list(selected_tags.difference([tag]) if tag in selected_tags else selected_tags.union([tag])) %}
+    <form action="{{ url_for('frontend.tickets', selected_tags=selected_tags_value, time_sorting=time_sorting if time_sorting) }}" method="post">
+        <input type="hidden" name="q" value="{{ query if query }}" >
+        <input type="hidden" name="status" value="{{ status }}">
+        <input type="submit" value="{{ tag }}" class="{{ 'ticket-tags-button btn btn-primary active' if tag in selected_tags else 'ticket-tags-button btn btn-default' }}">
+    </form>
+{% endfor %}
+
 </p>
 {% endblock %}
 {% block body_scripts %}
--- a/MoinMoin/themes/basic/static/css/basic.css	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/themes/basic/static/css/basic.css	Thu Jul 03 22:00:12 2014 +0200
@@ -6362,6 +6362,7 @@
 #ticket-list tr td,
 #ticket-list thead th {
   cursor: pointer;
+  padding-left: 20px;
 }
 #ticket-list tr td:first-child a {
   font-family: monospace;
@@ -6386,7 +6387,7 @@
 .tablesorter-header {
   background-image: url('../img/bg.png');
   background-repeat: no-repeat;
-  background-position: right center;
+  background-position: left center;
 }
 .tablesorter-headerAsc {
   background-image: url('../img/asc.png');
@@ -6394,3 +6395,24 @@
 .tablesorter-headerDesc {
   background-image: url('../img/desc.png');
 }
+.tablesorter-header.sorter-false {
+  background-image: None;
+}
+#ticket-summary {
+  width: 50%;
+}
+#ticket-list {
+  float: right;
+  width: 90%;
+}
+.ticket-tags-button {
+  padding: 4px;
+  width: 100px;
+}
+#ticket-table-tip {
+  float: right;
+  color: #999999;
+}
+.tickets-sort-button.dropdown {
+  display: inline;
+}
--- a/MoinMoin/themes/basic/static/custom-less/basic.less	Thu Jul 03 21:54:00 2014 +0200
+++ b/MoinMoin/themes/basic/static/custom-less/basic.less	Thu Jul 03 22:00:12 2014 +0200
@@ -335,6 +335,7 @@
 }
 #ticket-list tr td, #ticket-list thead th {
   cursor: pointer;
+  padding-left: 20px;
 }
 #ticket-list tr td:first-child a {
   font-family: monospace;
@@ -358,7 +359,7 @@
 .tablesorter-header {
   background-image: url('../img/bg.png');
   background-repeat: no-repeat;
-  background-position: right center;
+  background-position: left center;
 }
 .tablesorter-headerAsc {
   background-image: url('../img/asc.png');
@@ -366,3 +367,24 @@
 .tablesorter-headerDesc {
   background-image: url('../img/desc.png');
 }
+.tablesorter-header.sorter-false {
+  background-image: None;
+}
+#ticket-summary {
+  width: 50%;
+}
+#ticket-list {
+  float: right;
+  width: 90%;
+}
+.ticket-tags-button {
+  padding: 4px;
+  width: 100px;
+}
+#ticket-table-tip {
+  float: right;
+  color: #999999;
+}
+.tickets-sort-button.dropdown {
+  display: inline;
+}