Detailed stream info view. Fixes #573

- Legacy-Id: 2758
This commit is contained in:
Emilio A. Sánchez López 2011-01-23 10:23:45 +00:00
parent d516f43271
commit 256dd73dd4
15 changed files with 402 additions and 13 deletions

View file

@ -17,12 +17,16 @@ class ObjectHistoryEntry(models.Model):
comment = models.TextField(_('Comment'))
person = models.ForeignKey(PersonOrOrgInfo)
class Meta:
ordering = ('-date', )
def get_real_instance(self):
if hasattr(self, '_real_instance'):
return self._real_instance
for i in ('objectworkflowhistoryentry', 'objectannotationtaghistoryentry', 'objectstreamhistoryentry'):
try:
real_instance = getattr(self, 'objectworkflowhistoryentry', None)
real_instance = getattr(self, i, None)
if real_instance:
self._real_instance = real_instance
return real_instance
@ -36,16 +40,42 @@ class ObjectWorkflowHistoryEntry(ObjectHistoryEntry):
from_state = models.CharField(_('From state'), max_length=100)
to_state = models.CharField(_('To state'), max_length=100)
def describe_change(self):
html = '<p class="describe_state_change">'
html += 'Changed state <i>%s</i> to <b>%s</b>' % (self.from_state, self.to_state)
html += '</p>'
return html
class ObjectAnnotationTagHistoryEntry(ObjectHistoryEntry):
setted = models.TextField(_('Setted tags'), blank=True, null=True)
unsetted = models.TextField(_('Unsetted tags'), blank=True, null=True)
def describe_change(self):
html = ''
if self.setted:
html += '<p class="describe_tags_set">'
html += 'Annotation tags set: '
html += self.setted
html += '</p>'
if self.unsetted:
html += '<p class="describe_tags_reset">'
html += 'Annotation tags reset: '
html += self.unsetted
html += '</p>'
return html
class ObjectStreamHistoryEntry(ObjectHistoryEntry):
from_stream = models.TextField(_('From stream'), blank=True, null=True)
to_stream = models.TextField(_('To stream'), blank=True, null=True)
def describe_change(self):
html = '<p class="describe_stream_change">'
html += 'Changed doc from stream <i>%s</i> to <b>%s</b>' % (self.from_stream, self.to_stream)
html += '</p>'
return html
class AnnotationTag(models.Model):
name = models.CharField(_(u"Name"), max_length=100)

View file

@ -0,0 +1,84 @@
from django.db import models
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.models import StreamedID, Stream
def get_streamed_draft(draft):
if not draft:
return None
try:
return draft.streamedid
except StreamedID.DoesNotExist:
return None
def get_stream_from_draft(draft):
streamedid = get_streamed_draft(draft)
if streamedid:
return streamedid.stream
return False
def get_stream_from_id(stream_id):
try:
return Stream.objects.get(id=stream_id)
except Stream.DoesNotExist:
return None
def _get_group_from_acronym(group_model_str, acronym):
try:
app, model = group_model_str.split('.', 1)
except ValueError:
return None
group_model = models.get_model(app, model)
if not group_model:
return
if 'acronym' in group_model._meta.get_all_field_names():
try:
return group_model._default_manager.get(acronym=acronym)
except group_model.DoesNotExist:
return None
elif 'group_acronym' in group_model._meta.get_all_field_names():
try:
return group_model._default_manager.get(group_acronym__acronym=acronym)
except group_model.DoesNotExist:
return None
else:
return None
def _set_stream_automatically(draft, stream):
streamed = StreamedID.objects.create(stream=stream, draft=draft)
if not stream or not stream.with_groups:
return
try:
draft_literal, stream_name, group_name, extra = draft.filename.split('-', 3)
if stream_name.lower() == stream.name.lower():
group = _get_group_from_acronym(stream.group_model, group_name)
if group:
streamed.group = group
streamed.save()
except ValueError:
return
def get_stream_from_wrapper(idrfc_wrapper):
idwrapper = None
if isinstance(idrfc_wrapper, IdRfcWrapper):
idwrapper = idrfc_wrapper.id
elif isinstance(idrfc_wrapper, IdWrapper):
idwrapper = idrfc_wrapper
if not idwrapper:
return None
draft = idwrapper._draft
stream = get_stream_from_draft(draft)
if stream == False:
stream_id = idwrapper.stream_id()
stream = get_stream_from_id(stream_id)
_set_stream_automatically(draft, stream)
return stream
else:
return stream
return None

View file

@ -0,0 +1,48 @@
from django import template
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.utils import (get_workflow_for_draft,
get_state_for_draft)
from ietf.ietfworkflows.streams import get_stream_from_wrapper
register = template.Library()
@register.inclusion_tag('ietfworkflows/stream_state.html', takes_context=True)
def stream_state(context, doc):
request = context.get('request', None)
user = request and request.user
data = {}
stream = get_stream_from_wrapper(doc)
data.update({'stream': stream})
if not stream:
return data
idwrapper = None
if isinstance(doc, IdRfcWrapper):
idwrapper = doc.id
elif isinstance(doc, IdWrapper):
idwrapper = doc
if not idwrapper:
return data
draft = getattr(idwrapper, '_draft', None)
if not draft:
return data
workflow = get_workflow_for_draft(draft)
state = get_state_for_draft(draft)
data.update({'workflow': workflow,
'draft': draft,
'state': state})
return data
@register.inclusion_tag('ietfworkflows/workflow_history_entry.html', takes_context=True)
def workflow_history_entry(context, entry):
real_entry = entry.get_real_instance()
return {'entry': real_entry,
'entry_class': real_entry.__class__.__name__.lower()}

View file

@ -0,0 +1,7 @@
# Copyright The IETF Trust 2008, All Rights Reserved
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('ietf.ietfworkflows.views',
url(r'^(?P<name>[^/]+)/history/$', 'stream_history', name='stream_history'),
)

View file

@ -10,8 +10,10 @@ from workflows.models import State
from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
get_state)
from ietf.ietfworkflows.streams import get_streamed_draft
from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
AnnotationTag, ObjectAnnotationTagHistoryEntry)
AnnotationTag, ObjectAnnotationTagHistoryEntry,
ObjectHistoryEntry)
WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
@ -24,7 +26,8 @@ def get_default_workflow_for_wg():
return workflow
except WGWorkflow.DoesNotExist:
return None
def clone_transition(transition):
new = copy.copy(transition)
new.pk = None
@ -35,6 +38,7 @@ def clone_transition(transition):
new.states.add(state)
return new
def clone_workflow(workflow, name):
new = WGWorkflow.objects.create(name=name, initial_state=workflow.initial_state)
@ -51,20 +55,25 @@ def clone_workflow(workflow, name):
new.transitions.add(clone_transition(transition))
return new
def get_workflow_for_wg(wg):
def get_workflow_for_wg(wg, default=None):
workflow = get_workflow_for_object(wg)
try:
workflow = workflow and workflow.wgworkflow
except WGWorkflow.DoesNotExist:
workflow = None
if not workflow:
workflow = get_default_workflow_for_wg()
if default:
workflow = default
else:
workflow = get_default_workflow_for_wg()
if not workflow:
return None
workflow = clone_workflow(workflow, name='%s workflow' % wg)
set_workflow_for_object(wg, workflow)
return workflow
def get_workflow_for_draft(draft):
workflow = get_workflow_for_object(draft)
try:
@ -72,11 +81,29 @@ def get_workflow_for_draft(draft):
except WGWorkflow.DoesNotExist:
workflow = None
if not workflow:
workflow = get_workflow_for_wg(draft.group.ietfwg)
streamed_draft = get_streamed_draft(draft)
if not streamed_draft or not streamed_draft.stream:
return None
stream = streamed_draft.stream
if stream.with_groups:
if not streamed_draft.group:
return None
else:
workflow = get_workflow_for_wg(streamed_draft.group, streamed_draft.stream.workflow)
else:
workflow = stream.workflow
set_workflow_for_object(draft, workflow)
return workflow
def get_workflow_history_for_draft(draft):
ctype = ContentType.objects.get_for_model(draft)
history = ObjectHistoryEntry.objects.filter(content_type=ctype, content_id=draft.pk).\
select_related('objectworkflowhistoryentry', 'objectannotationtaghistoryentry',
'objectstreamhistoryentry')
return history
def get_annotation_tags_for_draft(draft):
ctype = ContentType.objects.get_for_model(draft)
tags = AnnotationTagObjectRelation.objects.filter(content_type=ctype, content_id=draft.pk)
@ -100,6 +127,7 @@ def get_annotation_tag_by_name(tag_name):
except AnnotationTag.DoesNotExist:
return None
def set_tag_by_name(obj, tag_name):
ctype = ContentType.objects.get_for_model(obj)
try:
@ -159,7 +187,7 @@ def notify_tag_entry(entry, extra_notify=[]):
def notify_state_entry(entry, extra_notify=[]):
return notify_entry(entry, 'ietfworkflows/state_updated_mail.txt', extra_notify)
def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
ctype = ContentType.objects.get_for_model(obj)
setted = []
@ -173,9 +201,9 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[
entry = ObjectAnnotationTagHistoryEntry.objects.create(
content_type=ctype,
content_id=obj.pk,
setted = ','.join(setted),
unsetted = ','.join(resetted),
change_date = datetime.datetime.now(),
comment = comment,
setted=','.join(setted),
unsetted=','.join(resetted),
date=datetime.datetime.now(),
comment=comment,
person=person)
notify_tag_entry(entry, extra_notify)

View file

@ -0,0 +1,60 @@
from ietf.idtracker.models import IETFWG, InternetDraft, IESGLogin
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.http import HttpResponseForbidden, Http404
from ietf.idrfc.views_search import SearchForm, search_query
from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
workflow_form_factory, TransitionFormSet,
WriteUpEditForm)
from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
can_manage_shepherds_in_group,
can_manage_workflow_in_group,
can_manage_shepherd_of_a_document,
can_manage_writeup_of_a_document,
can_manage_writeup_of_a_document_no_state,
)
from ietf.ietfworkflows.streams import (get_stream_from_draft,
get_streamed_draft)
from ietf.ietfworkflows.utils import (get_workflow_for_wg,
get_default_workflow_for_wg,
get_workflow_history_for_draft,
get_workflow_for_draft,
get_state_by_name,
get_annotation_tags_for_draft,
get_state_for_draft, WAITING_WRITEUP,
FOLLOWUP_TAG)
REDUCED_HISTORY_LEN = 20
def stream_history(request, name):
user = request.user
person = get_person_for_user(user)
draft = get_object_or_404(InternetDraft, filename=name)
streamed = get_streamed_draft(draft)
stream = get_stream_from_draft(draft)
workflow = get_workflow_for_draft(draft)
tags = []
if workflow:
tags_setted = [i.annotation_tag.pk for i in get_annotation_tags_for_draft(draft)]
for tag in workflow.get_tags():
tag.setted = tag.pk in tags_setted
tags.append(tag)
state = get_state_for_draft(draft)
history = get_workflow_history_for_draft(draft)
show_more = False
if history.count > REDUCED_HISTORY_LEN:
show_more = True
return render_to_response('ietfworkflows/stream_history.html',
{'stream': stream,
'streamed': streamed,
'draft': draft,
'tags': tags,
'state': state,
'workflow': workflow,
'show_more': show_more,
'history': history[:REDUCED_HISTORY_LEN],
},
context_instance=RequestContext(request))

View file

@ -31,7 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endcomment %}
{% load ietf_filters %}{% load ballot_icon %}
{% load ietf_filters ietf_streams %}{% load ballot_icon %}
<td class="status">
{{ doc.friendly_state|safe }} {% if not doc.rfc %}{{ doc.id|state_age_colored|safe }}{% endif %}
{% if not hide_telechat_date %}{% if doc.telechat_date %}<br/>IESG Telechat: {{ doc.telechat_date }}{% endif %}{% endif %}
@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% if doc.rfc.has_errata %}<br /><a href="http://www.rfc-editor.org/errata_search.php?rfc={{doc.rfc.rfc_number}}" rel="nofollow">Errata</a>{% endif %}
{% else %}{# not rfc #}
{% if doc.id.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.id.draft_name}}">{{ doc.id.rfc_editor_state|escape }}</a>{% endif %}
{% stream_state doc %}
{% endif %}
</td>
<td class="ballot">

View file

@ -0,0 +1,44 @@
{% load ietf_streams %}
<table class="ietf-ballot ietf-stream">
<tr>
<td class="left">
{% if workflow %}
<ul class="ietf-stream-tag-list">
{% for tag in tags %}
<li{% if tag.setted %} class="tag_set"{% endif %}>{{ tag }}</li>
{% endfor %}
</ul>
{% endif %}
</td>
<td class="right">
<div class="ietf-stream-head">
{% if stream %}
<h2>
{{ stream|default:"Without stream" }}{% if stream.with_groups and streamed.group %} :: {{ streamed.group|default:"" }}{% endif %}
</h2>
<h3>Current state: {{ state|default:"None" }}</h3>
{% else %}
<h2>Without stream</h2>
{% endif %}
</div>
{% if history %}
<div class="ietf-stream-history">
{% if show_more %}
<p> Viewing the last 20 entries. <a href="">Show full log</a>.</p>
{% endif %}
{% for entry in history %}
{% workflow_history_entry entry %}
{% endfor %}
{% if show_more %}
<p> Viewing the last 20 entries. <a href="">Show full log</a>.</p>
{% endif %}
</div>
{% else %}
<p>
There is no stream history for this document.
</p>
{% endif %}
</td>
</tr>
</table>

View file

@ -0,0 +1,10 @@
{% if draft %}
<div class="stream_state" style="border: 1px solid black; padding: 0px 5px; margin: 5px 0px;">
<div class="stream_state_more" style="float: right; margin-left: 2em;"><a href="{% url stream_history draft.filename %}" class="show_stream_info" title="Stream information for {{ draft.filename }}" style="text-decoration: none; color: black;">+</a></div>
{% if stream %}
{{ stream }} {% if state %}:: {{ state }}{% endif %}
{% else %}
No stream asigned
{% endif %}
</div>
{% endif %}

View file

@ -0,0 +1,12 @@
<div class="workflow-history-entry workflow-history-entry-{{ entry_class }}">
<div class="entry-title">
<span class="entry-date">{{ entry.date }}</span>
{{ entry.person }}
</div>
<div class="entry-action">
{{ entry.describe_change|safe }}
</div>
<div class="entry-comment">
{{ entry.comment }}
</div>
</pre>

View file

@ -55,6 +55,7 @@ urlpatterns = patterns('',
(r'^accounts/', include('ietf.ietfauth.urls')),
(r'^doc/', include('ietf.idrfc.urls')),
(r'^wg/', include('ietf.wginfo.urls')),
(r'^streams/', include('ietf.ietfworkflows.urls')),
(r'^$', 'ietf.idrfc.views.main'),
('^admin/', include(admin.site.urls)),

View file

@ -363,7 +363,10 @@ class WriteUpEditForm(RelatedWGForm):
if self.data.get('modify_tag', False):
followup = self.cleaned_data.get('followup', False)
comment = self.cleaned_data.get('comment', False)
shepherd = self.doc.shepherd
try:
shepherd = self.doc.shepherd
except PersonOrOrgInfo.DoesNotExist:
shepherd = None
if shepherd:
extra_notify = ['%s <%s>' % shepherd.email()]
else:

View file

@ -71,6 +71,17 @@ body { margin: 0; }
.ietf-ballot .square { border:1px solid black; display: block; float:left;width: 10px; height:10px;font-size:1px;margin-right:4px; margin-top:1px;}
.ietf-ballot .was { padding-left: 10px; font-size:85%; }
.ietf-stream tr { vertical-align: top; }
.ietf-stream ul.ietf-stream-tag-list { padding: 10px; margin: 0px; list-style-type: none; font-size: 10px; }
.ietf-stream ul.ietf-stream-tag-list li { margin-bottom: 1em; list-style-type: circle; color: #999999; }
.ietf-stream ul.ietf-stream-tag-list li.tag_set { font-weight: bold; list-style-type: disc; color: black; }
.ietf-stream td.right { padding-top: 1em; }
.ietf-stream .ietf-stream-head h2, .ietf-stream .ietf-stream-head h3 { margin: 0px; }
.ietf-stream .ietf-stream-head { margin-bottom: 2em; }
.ietf-stream .entry-title { background: #2647A0; color:white; padding: 2px 4px; font-size: 108%; margin-top: 0;}
.ietf-stream .entry-title .entry-date { float: right; }
.ietf-stream .entry-comment { background: #eeeeee; margin: 1em 0px; padding: 1em; }
.search_form_box {width: 99.5%; margin-top:8px; padding:4px; margin-bottom:1em; padding-left:8px;}
form#search_form { padding-top: 4px; padding-bottom: 4px; }
#search_form input { padding: 0; padding-left: 2px; border: 1px solid #89d;}

View file

@ -74,3 +74,53 @@ function showBallot(draftName, editPositionUrl) {
function editBallot(editPositionUrl) {
window.open(editPositionUrl);
}
function showStream(dialogTitle, infoStreamUrl) {
var handleClose = function() {
IETF.streamDialog.hide();
};
var el;
if (!IETF.streamDialog) {
el = document.createElement("div");
el.innerHTML = '<div id="stream_dialog" style="visibility:hidden;"><div class="hd"><span id="stream_title">' + dialogTitle + '</span></div><div class="bd"> <div id="stream_dialog_body" style="overflow-y:scroll; height:400px;"></div> </div></div>';
document.getElementById("ietf-extras").appendChild(el);
var buttons = [{text:"Close", handler:handleClose, isDefault:true}];
var kl = [new YAHOO.util.KeyListener(document, {keys:27}, handleClose)]
IETF.streamDialog = new YAHOO.widget.Dialog("stream_dialog", {
visible:false, draggable:false, close:true, modal:true,
width:"860px", fixedcenter:true, constraintoviewport:true,
buttons: buttons, keylisteners:kl});
IETF.streamDialog.render();
}
document.getElementById("stream_title").innerHTML = dialogTitle;
IETF.streamDialog.show();
el = document.getElementById("stream_dialog_body");
el.innerHTML = "Loading...";
YAHOO.util.Connect.asyncRequest('GET',
infoStreamUrl,
{ success: function(o) { el.innerHTML = (o.responseText !== undefined) ? o.responseText : "?"; },
failure: function(o) { el.innerHTML = "Error: "+o.status+" "+o.statusText; },
argument: null
}, null);
}
(function ($) {
$.fn.StreamInfo = function() {
return this.each(function () {
var infoStreamUrl = $(this).attr('href');
var title = $(this).attr('title');
$(this).click(function() {
showStream(title, infoStreamUrl);
return false;
});
});
};
$(document).ready(function () {
$('a.show_stream_info').StreamInfo();
});
})(jQuery);