changeset 1678:51e1e8b0b7f2

Implement Ticket item as 'ticket' itemtype.
author Cheer Xiao <xiaqqaix@gmail.com>
date Tue, 14 Aug 2012 00:40:43 +0800
parents d74b9e4d41e5
children d880379b6a01 d38274bc398a f676a325b974
files MoinMoin/items/ticket.py MoinMoin/storage/middleware/indexing.py MoinMoin/templates/ticket.html
diffstat 3 files changed, 218 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/items/ticket.py	Tue Aug 14 00:40:43 2012 +0800
@@ -0,0 +1,144 @@
+# 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, Submit, SmallNatural, Tags, Reference, BackReference
+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)
+    submit = Submit.using(default=L_("Update ticket"))
+
+    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)
+
+
+# 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
+    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):
+        if request.method == 'GET':
+            form = TicketForm.from_item(self)
+        elif request.method == 'POST':
+            form = TicketForm.from_request(request)
+            if form.validate():
+                meta = form['meta'].value
+                meta.update({
+                    ITEMTYPE: self.itemtype,
+                    # XXX support other markups
+                    CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8',
+                })
+
+                if isinstance(self.content, NonExistentContent):
+                    data = u''
+                else:
+                    data = self.content.data_storage_to_internal(self.content.data)
+                message = form['message'].value
+                if message:
+                    data += message_markup(message)
+
+                try:
+                    self.modify(meta, data)
+                except AccessDenied:
+                    abort(403)
+                else:
+                    return redirect(url_for('.show_item', item_name=self.name))
+        if isinstance(self.content, NonExistentContent):
+            is_new = True
+            # XXX suppress the "foo doesn't exist. Create it?" dummy content
+            data_rendered = None
+            form['submit'] = L_('Submit ticket')
+        else:
+            is_new = False
+            data_rendered = Markup(self.content._render_data())
+
+        return render_template(self.modify_template,
+                               is_new=is_new,
+                               item_name=self.name,
+                               data_rendered=data_rendered,
+                               form=form,
+                              )
--- a/MoinMoin/storage/middleware/indexing.py	Tue Aug 14 00:40:06 2012 +0800
+++ b/MoinMoin/storage/middleware/indexing.py	Tue Aug 14 00:40:43 2012 +0800
@@ -274,6 +274,19 @@
         }
         latest_revs_fields.update(**userprofile_fields)
 
+        # XXX This is a highly adhoc way to support indexing of ticket items.
+        ticket_fields = {
+            'effort': NUMERIC(stored=True),
+            'difficulty': NUMERIC(stored=True),
+            'severity': NUMERIC(stored=True),
+            'priority': NUMERIC(stored=True),
+            'status': ID(stored=True),
+            'assigned_to': ID(stored=True),
+            'superseded_by': ID(stored=True),
+            'depends_on': ID(stored=True),
+        }
+        latest_revs_fields.update(**ticket_fields)
+
         blog_entry_fields = {
             # publish time from metadata (converted to UTC datetime)
             PTIME: DATETIME(stored=True)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/ticket.html	Tue Aug 14 00:40:43 2012 +0800
@@ -0,0 +1,61 @@
+{% import "forms.html" as forms %}
+{% extends theme("layout.html") %}
+
+{% if is_new %}
+    {% set title = _("Creating new ticket: %(item_name)s", item_name=item_name) %}
+{% else %}
+    {% set title = _("Ticket: %(item_name)s", item_name=item_name) %}
+{% endif %}
+
+{% block content %}
+<h1>{{ title }}</h1>
+<div class="moin-form">
+    {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
+
+    {% if data_rendered %}
+        <div id="moin-content-data">
+            {{ data_rendered }}
+        </div>
+    {% endif %}
+
+    {% if is_new %}
+        <h2>Describe the ticket</h2>
+    {% else %}
+        <h2>Add comment</h2>
+    {% endif %}
+    <dl>
+    {{ forms.render(form['message']) }}
+    </dl>
+
+    <h2>Edit metadata</h2>
+    <dl>
+    {{ forms.render_errors(form) }}
+    {% for e in [
+        'summary',
+        'effort',
+        'difficulty',
+        'severity',
+        'priority',
+        'tags',
+        'assigned_to',
+        'superseded_by',
+        'depends_on',
+        ] %}
+        {{ forms.render(form['meta'][e]) }}
+    {% endfor %}
+    </dl>
+    {{ forms.render(form['submit']) }}
+
+    <h2>Back references</h2>
+    <dl>
+    {% for e in [
+        'supersedes',
+        'required_by',
+        'subscribers',
+        ] %}
+        {{ forms.render(form['backrefs'][e]) }}
+    {% endfor %}
+
+    {{ gen.form.close() }}
+</div>
+{% endblock %}