Hi CM,

I've updated your patch for trac 1.0 and attached it.


Cheers - Geert

-- 
You received this message because you are subscribed to the Google Groups "Trac 
Users" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/trac-users/-/wC5unEA5CNkJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/trac-users?hl=en.

Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py  2013-01-17 12:40:19.379815349 +1300
+++ trac/ticket/api.py  2013-01-17 12:44:23.679821392 +1300
@@ -373,7 +373,7 @@
                 'label': config.get(name + '.label') or name.capitalize(),
                 'value': config.get(name + '.value', '')
             }
-            if field['type'] == 'select' or field['type'] == 'radio':
+            if field['type'] in ('select', 'radio', 'multi'):
                 field['options'] = config.getlist(name + '.options', sep='|')
                 if '' in field['options']:
                     field['optional'] = True
Index: trac/ticket/web_ui.py
===================================================================
--- trac/ticket/web_ui.py	2013-01-17 12:40:19.379815349 +1300
+++ trac/ticket/web_ui.py	2013-01-17 13:59:29.795910105 +1300
@@ -1240,10 +1240,17 @@
             if name in ticket.values and name in ticket._old:
                 value = ticket[name]
                 if value:
-                    if value not in field['options']:
-                        add_warning(req, '"%s" is not a valid value for '
-                                    'the %s field.' % (value, name))
-                        valid = False
+                    if field['type'] == 'multi':
+                        values = value.split('|')
+                        if len(values) > 1:
+                            values.remove('')   #   get rid of trailing ''
+                    else:
+                        values = (value,)
+                    for val in values:
+                        if val not in field['options']:
+                            add_warning(req, '"%s" is not a valid value for '
+                                        'the %s field.' % (value, name))
+                            valid = False
                 elif not field.get('optional', False):
                     add_warning(req, _("field %(name)s must be set",
                                        name=name))
@@ -1503,19 +1510,32 @@
                         field_changes['cc']['new'] = ','.join(new_cc_list)
 
             # per type settings
-            if type_ in ('radio', 'select'):
+            if type_ in ('radio', 'select', 'multi'):
                 if ticket.exists:
                     value = ticket.values.get(name)
                     options = field['options']
                     optgroups = []
                     for x in field.get('optgroups', []):
                         optgroups.extend(x['options'])
-                    if value and \
-                        (not value in options and \
-                         not value in optgroups):
-                        # Current ticket value must be visible,
-                        # even if it's not among the possible values
-                        options.append(value)
+                    if value:
+                        if type_ == 'multi':
+                            values = value.split('|')
+                            if len(values) > 1:
+                                values.remove('')
+                        else:
+                            values = (value,)
+                        for val in values:
+                          if not val in options and not val in optgroups:
+                                # Current ticket value must be visible,
+                                # even if it's not among the possible values
+                                options.append(value)
+                        #   Rendered output should be pretty for multi-values
+                        if len(values) > 1:
+                            from genshi.builder import Element
+                            choices = Element('ul', class_='multi-value')
+                            for val in values:
+                                choices.append(Element('li')(val))
+                            field['rendered'] = choices
             elif type_ == 'checkbox':
                 value = ticket.values.get(name)
                 if value in ('1', '0'):
@@ -1744,6 +1764,13 @@
         elif field == 'keywords':
             old_list, new_list = old.split(), new.split()
             sep = ' '
+        elif type_ == 'multi':
+            old_list = (old or '').split('|')
+            if old_list.count(''):
+                old_list.remove('')
+            new_list = new.split('|')
+            if new_list.count(''):
+                new_list.remove('')
         if (old_list, new_list) != (None, None):
             added = [tag.em(render_elt(x)) for x in new_list 
                      if x not in old_list]
Index: trac/ticket/model.py
===================================================================
--- trac/ticket/model.py	2013-01-17 12:40:19.379815349 +1300
+++ trac/ticket/model.py	2013-01-17 12:51:12.903828192 +1300
@@ -144,6 +144,8 @@
     def __setitem__(self, name, value):
         """Log ticket modifications so the table ticket_change can be updated
         """
+        if isinstance(value, list): #   account for multi-selects
+            value = '|'.join(value) + '|'
         if name in self.values and self.values[name] == value:
             return
         if name not in self._old: # Changed field
@@ -152,7 +154,7 @@
             del self._old[name]
         if value:
             if isinstance(value, list):
-                raise TracError(_("Multi-values fields not supported yet"))
+                value = '|'.join(value) + '|'
             field = [field for field in self.fields if field['name'] == name]
             if field and field[0].get('type') != 'textarea':
                 value = value.strip()
@@ -184,6 +186,10 @@
                      and name.startswith('checkbox_')]:
             if name[9:] not in values:
                 self[name[9:]] = '0'
+        #   We do something similar for empty multi-selects
+        for f in self.fields:
+            if f['type'] == 'multi' and not f['name'] in values:
+                self[f['name']] = ''
 
     def insert(self, when=None, db=None):
         """Add ticket to database.
Index: trac/ticket/query.py
===================================================================
--- trac/ticket/query.py	2013-01-17 12:40:19.379815349 +1300
+++ trac/ticket/query.py	2013-01-17 13:17:47.271857022 +1300
@@ -169,7 +169,7 @@
             field, values = filter_
             # from last chars of `field`, get the mode of comparison
             mode = ''
-            if field and field[-1] in ('~', '^', '$') \
+            if field and field[-1] in ('~', '^', '$', '|') \
                                 and not field in cls.substitutions:
                 mode = field[-1]
                 field = field[:-1]
@@ -340,6 +340,14 @@
                             val = bool(int(val))
                         except (TypeError, ValueError):
                             val = False
+                    elif field and field['type'] == 'multi':
+                        val = (val or '').split('|')
+                        if val.count(''):
+                            val.remove('')
+                        if len(val):
+                            val = ', '.join(val)
+                        else:
+                            val = 'None'
                     elif val is None:
                         val = ''
                     result[name] = val
@@ -542,6 +550,8 @@
                 value = value + '%'
             elif mode == '$':
                 value = '%' + value
+            elif mode == '|':
+                value = '%' + value + '|%'
             return ("COALESCE(%s,'') %s%s" % (col, 'NOT ' if neg else '',
                                               db.like()),
                     (value, ))
@@ -556,7 +566,7 @@
                 # starts-with, negation, etc.)
                 neg = v[0].startswith('!')
                 mode = ''
-                if len(v[0]) > neg and v[0][neg] in ('~', '^', '$'):
+                if len(v[0]) > neg and v[0][neg] in ('~', '^', '$', '|'):
                     mode = v[0][neg]
 
                 # Special case id ranges
@@ -681,6 +691,10 @@
             {'name': _("is"), 'value': ""},
             {'name': _("is not"), 'value': "!"},
         ]
+        modes['multi'] = [
+            {'name': _("contains"), 'value': "|"},
+            {'name': _("does not contain"), 'value': "!"}
+        ]
         modes['id'] = [
             {'name': _("is"), 'value': ""},
             {'name': _("is not"), 'value': "!"},
@@ -699,7 +713,7 @@
                     if neg:
                         val = val[1:]
                     mode = ''
-                    if val[:1] in ('~', '^', '$') \
+                    if val[:1] in ('~', '^', '$', '|') \
                                         and not val in self.substitutions:
                         mode, val = val[:1], val[1:]
                     if req:
Index: trac/ticket/templates/ticket.html
===================================================================
--- trac/ticket/templates/ticket.html	2013-01-17 12:40:19.379815349 +1300
+++ trac/ticket/templates/ticket.html	2013-01-17 13:24:27.263864843 +1300
@@ -269,6 +269,19 @@
                                     value="$option" py:content="option"></option>
                           </optgroup>
                         </select>
+                        <select py:when="'multi'" id="field-${field.name}" name="field_${field.name}" size="5" 
+                          multiple="multiple">
+                          <option py:for="option in field.options"
+                                  selected="${option in value.split('|') or None}"
+                                  py:content="option"></option>
+                          <optgroup py:for="optgroup in field.optgroups"
+                                    py:if="optgroup.options"
+                                    label="${optgroup.label}">
+                            <option py:for="option in optgroup.options"
+                                    selected="${option in value.split('|') or None}"
+                                    py:content="option"></option>
+                          </optgroup>
+                        </select>
                         <textarea py:when="'textarea'" id="field-${field.name}" name="field_${field.name}"
                                   cols="${field.width}" rows="${field.height}"
                                   class="${'wikitext ' if field.format == 'wiki' else None}trac-resizable">
Index: trac/ticket/templates/query.html
===================================================================
--- trac/ticket/templates/query.html	2013-01-17 12:40:19.379815349 +1300
+++ trac/ticket/templates/query.html	2013-01-17 12:56:59.195831236 +1300
@@ -57,7 +57,7 @@
                   <tbody py:for="field_name in field_names" py:if="field_name in constraints"
                          py:with="field = fields[field_name]; n_field_name = clause_pre + field_name;
                                   constraint = constraints[field_name];
-                                  multiline = field.type in ('select', 'text', 'textarea', 'time')">
+                                  multiline = field.type in ('select', 'multi', 'text', 'textarea', 'time')">
                     <tr py:for="constraint_idx, constraint_value in enumerate(constraint['values'])"
                         class="${field_name}" py:if="multiline or constraint_idx == 0">
                       <td>
@@ -83,7 +83,7 @@
                       <td class="filter" colspan="${2 if field.type in ('radio', 'checkbox', 'time') else None}"
                           py:choose="">
 
-                        <py:when test="field.type == 'select'">
+                        <py:when test="field.type in ('select', 'multi')">
                           <select name="${n_field_name}">
                             <option></option>
                             <option py:for="option in field.options"
Index: trac/htdocs/js/query.js
===================================================================
--- trac/htdocs/js/query.js	2013-01-17 12:40:19.371815602 +1300
+++ trac/htdocs/js/query.js	2013-01-17 13:09:53.583851393 +1300
@@ -201,7 +201,7 @@
 
         // Add the selector or text input for the actual filter value
         td = $("<td>").addClass("filter");
-        if (property.type == "select") {
+        if (property.type == "select" || property.type == "multi") {
           focusElement = createSelect(propertyName, property.options, true,
                                       property.optgroups);
         } else if ((property.type == "text") || (property.type == "id")
Index: trac/htdocs/css/ticket.css
===================================================================
--- trac/htdocs/css/ticket.css	(2013-01-17 12:40:19.371815602 +1300)
+++ trac/htdocs/css/ticket.css	(2013-01-17 13:21:01.671859764 +1300)
@@ -100,6 +100,12 @@
 #ticket table.properties td p:last-child { margin-bottom: 0 }
 #ticket table.properties .description { border-top: 1px solid #dd9 }
 
+#ticket table.properties ul.multi-value {
+ padding: 0; 
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
 #ticket .description h3 {
  border-bottom: 1px solid #dd9;
  color: #663;

Reply via email to