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;