Source

moin-2.0 / MoinMoin / items / ticket.py

# Copyright: 2012 MoinMoin:CheerXiao
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.

"""
MoinMoin - Ticket itemtype
"""


from __future__ import absolute_import, division

import time

from flask import request, abort, redirect, url_for
from flask import g as flaskg

from jinja2 import Markup

from whoosh.query import Term

from MoinMoin.i18n import L_
from MoinMoin.themes import render_template
from MoinMoin.forms import (Form, OptionalText, OptionalMultilineText, SmallNatural, Tags,
                            Reference, BackReference, SelectSubmit)
from MoinMoin.storage.middleware.protecting import AccessDenied
from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, ITEMID, CURRENT
from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
from MoinMoin.items import Item, Contentful, register, BaseModifyForm
from MoinMoin.items.content import NonExistentContent


ITEMTYPE_TICKET = u'ticket'

USER_QUERY = Term(CONTENTTYPE, CONTENTTYPE_USER)
TICKET_QUERY = Term(ITEMTYPE, ITEMTYPE_TICKET)

Rating = SmallNatural.using(optional=True).with_properties(lower=1, upper=5)
OptionalTicketReference = Reference.to(TICKET_QUERY).using(optional=True)
OptionalUserReference = Reference.to(USER_QUERY).using(optional=True).with_properties(empty_label='(Nobody)')


class TicketMetaForm(Form):
    summary = OptionalText.using(label=L_("Summary")).with_properties(placeholder=L_("One-line summary of the item"))
    effort = Rating.using(label=L_("Effort"))
    difficulty = Rating.using(label=L_("Difficulty"))
    severity = Rating.using(label=L_("Severity"))
    priority = Rating.using(label=L_("Priority"))
    tags = Tags.using(optional=True)
    assigned_to = OptionalUserReference.using(label=L_("Assigned To"))
    superseded_by = OptionalTicketReference.using(label=L_("Superseded By"))
    depends_on = OptionalTicketReference.using(label=L_("Depends On"))


class TicketBackRefForm(Form):
    supersedes = BackReference.using(label=L_("Supersedes"))
    required_by = BackReference.using(label=L_("Required By"))
    subscribers = BackReference.using(label=L_("Subscribers"))

    def _load(self, item):
        id_ = item.meta[ITEMID]
        self['supersedes'].set(Term('superseded_by', id_))
        self['required_by'].set(Term('depends_on', id_))
        self['subscribers'].set(Term('subscribed_items', id_))


class TicketForm(BaseModifyForm):
    meta = TicketMetaForm
    backrefs = TicketBackRefForm
    message = OptionalMultilineText.using(label=L_("Message")).with_properties(rows=8, cols=80)

    def _load(self, item):
        meta = item.prepare_meta_for_modify(item.meta)
        self['meta'].set(meta, 'duck')
        # XXX need a more explicit way to test for item creation/modification
        if ITEMID in item.meta:
            self['backrefs']._load(item)


class TicketSubmitForm(TicketForm):
    submit_label = L_("Submit ticket")

    def _dump(self, item):
        # initial metadata for Ticket-itemtyped item
        meta = {
            ITEMTYPE: item.itemtype,
            # XXX support other markups
            CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8',
            'closed': False,
        }
        return meta, message_markup(self['message'].value)


class TicketUpdateForm(TicketForm):
    submit = SelectSubmit.valued('update', 'update_negate_status')

    def _load(self, item):
        super(TicketUpdateForm, self)._load(item)
        self['submit'].properties['labels'] = {
            'update': L_('Update ticket'),
            'update_negate_status':
                L_('Update & reopen ticket') if item.meta.get('closed') else
                L_('Update & close ticket')
        }

    def _dump(self, item):
        # Since the metadata form for tickets is an incomplete one, we load the
        # original meta and update it with those from the metadata editor
        meta = item.meta_filter(item.prepare_meta_for_modify(item.meta))
        meta.update(self['meta'].value)
        if self['submit'].value == 'update_negate_status':
            meta['closed'] = not meta.get('closed')

        data = item.content.data_storage_to_internal(item.content.data)
        message = self['message'].value
        if message:
            data += message_markup(message)

        return meta, data


# XXX Ideally we should generate DOM instead of moin wiki source. But
# currently this is not very useful, since
# * DOM cannot be stored directly, it has to be converted to some markup first
# * DOM -> markup conversion is only available for moinwiki

# XXX How to do i18n on this?

def message_markup(message):
    return u'''{{{{{{#!wiki tip
%(author)s wrote on <<DateTime(%(timestamp)d)>>:

%(message)s
}}}}}}
''' % dict(author=flaskg.user.name, timestamp=time.time(), message=message)


@register
class Ticket(Contentful):
    itemtype = ITEMTYPE_TICKET
    display_name = L_('Ticket')
    description = L_('Ticket item')
    modify_template = 'ticket.html'

    def do_show(self, revid):
        if revid != CURRENT:
            # TODO When requesting a historical version, show a readonly view
            abort(403)
        else:
            return self.do_modify()

    def do_modify(self):
        is_new = isinstance(self.content, NonExistentContent)
        closed = self.meta.get('closed')

        Form = TicketSubmitForm if is_new else TicketUpdateForm

        if request.method in ['GET', 'HEAD']:
            form = Form.from_item(self)
        elif request.method == 'POST':
            form = Form.from_request(request)
            if form.validate():
                meta, data = form._dump(self)
                try:
                    self.modify(meta, data)
                except AccessDenied:
                    abort(403)
                else:
                    return redirect(url_for('.show_item', item_name=self.name))

        # 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())

        return render_template(self.modify_template,
                               is_new=is_new,
                               closed=closed,
                               item_name=self.name,
                               data_rendered=data_rendered,
                               form=form,
                              )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.