| # 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 |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| from pylons import tmpl_context as c |
| from formencode import validators as fev |
| from webhelpers.html.builder import literal |
| |
| import ew as ew_core |
| import ew.jinja2_ew as ew |
| |
| from allura import model as M |
| from allura.lib.widgets import form_fields as ffw |
| from allura.lib import helpers as h |
| from allura.lib import validators as v |
| |
| |
| class TicketCustomFields(ew.CompoundField): |
| template = 'jinja:forgetracker:templates/tracker_widgets/ticket_custom_fields.html' |
| |
| def __init__(self, *args, **kwargs): |
| super(TicketCustomFields, self).__init__(*args, **kwargs) |
| self._fields = None |
| self._custom_fields_values = {} |
| |
| def context_for(self, field): |
| response = super(TicketCustomFields, self).context_for(field) |
| response['value'] = self._custom_fields_values.get(field.name) |
| return response |
| |
| @property |
| def fields(self): |
| # milestone is kind of special because of the layout |
| # add it to the main form rather than handle with the other customs |
| if self._fields is None: |
| self._fields = [] |
| for cf in c.app.globals.custom_fields: |
| if cf.name != '_milestone': |
| self._fields.append(TicketCustomField.make(cf)) |
| return self._fields |
| |
| |
| class GenericTicketForm(ew.SimpleForm): |
| defaults = dict( |
| ew.SimpleForm.defaults, |
| name="ticket_form", |
| submit_text='Save', |
| ticket=None, |
| show_comment=False) |
| |
| def display_field_by_name(self, idx, ignore_errors=False): |
| field = self.fields[idx] |
| ctx = self.context_for(field) |
| if idx == 'assigned_to': |
| self._add_current_value_to_user_field(field, ctx.get('value')) |
| elif idx == 'custom_fields': |
| field._custom_fields_values = ctx.get('value') or {} |
| for cf in c.app.globals.custom_fields: |
| if cf and cf.type == 'user': |
| val = ctx.get('value') |
| user = val.get(cf.name) if val else None |
| for f in field.fields: |
| if f.name == cf.name: |
| self._add_current_value_to_user_field(f, user) |
| |
| display = field.display(**ctx) |
| if ctx['errors'] and field.show_errors and not ignore_errors: |
| display += literal("<div class='error'>{0}</div>".format(ctx['errors'])) |
| return display |
| |
| def _add_current_value_to_user_field(self, field, user): |
| """Adds current field's value to `ProjectUserCombo` options. |
| |
| This is done to be able to select default value when widget loads, |
| since normally `ProjectUserCombo` shows without any options, and loads |
| them asynchronously (via ajax). |
| """ |
| if isinstance(user, basestring): |
| user = M.User.by_username(user) |
| if user and user != M.User.anonymous(): |
| field.options = [ |
| ew.Option( |
| py_value=user.username, |
| label='%s (%s)' % (user.display_name, user.username)) |
| ] |
| |
| @property |
| def fields(self): |
| fields = [ |
| ew.TextField(name='summary', label='Title', |
| attrs={'style': 'width: 425px', |
| 'placeholder': 'Title'}, |
| validator=fev.UnicodeString( |
| not_empty=True, messages={ |
| 'empty': "You must provide a Title"})), |
| ffw.MarkdownEdit(label='Description', name='description', |
| attrs={'style': 'width: 95%'}), |
| ew.SingleSelectField(name='status', label='Status', |
| options=lambda: c.app.globals.all_status_names.split( |
| )), |
| ffw.ProjectUserCombo(name='assigned_to', label='Owner'), |
| ffw.LabelEdit(label='Labels', name='labels', |
| className='ticket_form_tags'), |
| ew.Checkbox(name='private', label='Mark as Private', |
| validator=v.AnonymousValidator(), |
| attrs={'class': 'unlabeled'}), |
| ew.InputField(name='attachment', label='Attachment', field_type='file', attrs={ |
| 'multiple': 'True'}, validator=fev.FieldStorageUploadConverter(if_missing=None)), |
| ffw.MarkdownEdit(name='comment', label='Comment', |
| attrs={'style': 'min-height:7em; width:97%'}), |
| ew.SubmitButton(label=self.submit_text, name='submit', |
| attrs={ |
| 'class': "ui-button ui-widget ui-state-default ui-button-text-only"}), |
| ew.HiddenField(name='ticket_num', |
| validator=fev.Int(if_missing=None)), |
| ] |
| # milestone is kind of special because of the layout |
| # add it to the main form rather than handle with the other customs |
| if c.app.globals.custom_fields: |
| for cf in c.app.globals.custom_fields: |
| if cf.name == '_milestone': |
| fields.append(TicketCustomField.make(cf)) |
| break |
| return ew_core.NameList(fields) |
| |
| |
| class TicketForm(GenericTicketForm): |
| template = 'jinja:forgetracker:templates/tracker_widgets/ticket_form.html' |
| |
| @property |
| def fields(self): |
| fields = ew_core.NameList(super(TicketForm, self).fields) |
| if c.app.globals.custom_fields: |
| fields.append(TicketCustomFields(name="custom_fields")) |
| return fields |
| |
| def resources(self): |
| for r in super(TicketForm, self).resources(): |
| yield r |
| yield ew.JSScript(''' |
| // Sometimes IE11 is not firing jQuery's ready callbacks like |
| // "$(function(){...})" or "$(document).ready(function(){...});" |
| $(window).load(function(){ |
| $('#show_attach').click(function(evt) { |
| $('#view_attach').show(); |
| $('#show_attach').hide(); |
| evt.preventDefault(); |
| }); |
| $('form').submit(function() { |
| $('input[type=submit]', this).attr('disabled', 'disabled'); |
| }); |
| $('div.reply.discussion-post a.markdown_preview').click(function(){ |
| var arrow = $(this).closest('.discussion-post').find('span.arw'); |
| arrow.hide(); |
| }); |
| $('div.reply.discussion-post a.markdown_edit').click(function(){ |
| var arrow = $(this).closest('.discussion-post').find('span.arw'); |
| arrow.show(); |
| }); |
| });''') |
| |
| |
| class TicketCustomField(object): |
| |
| def _select(field): |
| options = [] |
| field_options = h.split_select_field_options( |
| h.really_unicode(field.options)) |
| |
| for opt in field_options: |
| selected = False |
| if opt.startswith('*'): |
| opt = opt[1:] |
| selected = True |
| options.append( |
| ew.Option(label=opt, html_value=opt, py_value=opt, selected=selected)) |
| return ew.SingleSelectField(label=field.label, name=str(field.name), options=options) |
| |
| def _milestone(field): |
| options = [] |
| for m in field.milestones: |
| options.append(ew.Option( |
| label=m.name, |
| py_value=m.name, |
| selected=getattr(m, 'default', False), |
| complete=bool(m.complete))) |
| |
| ssf = MilestoneField( |
| label=field.label, name=str(field.name), |
| options=options) |
| return ssf |
| |
| def _boolean(field): |
| return ew.Checkbox(label=field.label, name=str(field.name), suppress_label=True) |
| |
| def _number(field): |
| return ew.NumberField(label=field.label, name=str(field.name)) |
| |
| def _user(field): |
| return ffw.ProjectUserCombo(label=field.label, name=str(field.name)) |
| |
| @staticmethod |
| def _default(field): |
| return ew.TextField(label=field.label, name=str(field.name)) |
| |
| SELECTOR = dict( |
| select=_select, |
| milestone=_milestone, |
| boolean=_boolean, |
| number=_number, |
| user=_user) |
| |
| @classmethod |
| def make(cls, field): |
| factory = cls.SELECTOR.get(field.get('type'), cls._default) |
| return factory(field) |
| |
| |
| class MilestoneField(ew.SingleSelectField): |
| template = ew.Snippet('''<select {{widget.j2_attrs({ |
| 'id':id, |
| 'name':rendered_name, |
| 'multiple':multiple, |
| 'class':css_class}, |
| attrs)}}> |
| {% for o in open_milestones %} |
| <option{% if o.selected%} selected{% endif %} value="{{o.html_value}}">{{o.label|e}}</option> |
| {% endfor %} |
| {% if closed_milestones %} |
| <optgroup label="Closed"> |
| {% for o in closed_milestones %} |
| <option{% if o.selected%} selected{% endif %} value="{{o.html_value}}">{{o.label|e}}</option> |
| {% endfor %} |
| </optgroup> |
| {% endif %} |
| </select>''', 'jinja2') |
| |
| def prepare_context(self, context): |
| context = super(MilestoneField, self).prepare_context(context) |
| |
| # group open / closed milestones |
| context['open_milestones'] = [ |
| opt for opt in self.options if not opt.complete] |
| context['closed_milestones'] = [ |
| opt for opt in self.options if opt.complete] |
| |
| # filter closed milestones entirely |
| #value = context['value'] |
| #context['options'] = [opt for opt in self.options if not opt.complete or value == opt.py_value] |
| |
| return context |