Merged [6629] from tterriberry@mozilla.com:

Allow wgchairs to edit replaces relationships.

This actually allows anyone with can_edit_stream_info permission
to edit the list. This does draft name completion, but does not
currently filter those names for likely replacements. Styling is
also basically non-existent.

Fixes #1002
 - Legacy-Id: 6639
Note: SVN reference [6629] has been migrated to Git commit 9ef29a323f
This commit is contained in:
Henrik Levkowetz 2013-11-02 23:41:51 +00:00
commit f029c88347
4 changed files with 240 additions and 2 deletions

View file

@ -75,8 +75,10 @@ urlpatterns += patterns('',
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/submit-to-iesg/$', views_draft.to_iesg, name='doc_to_iesg'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/resurrect/$', views_draft.resurrect, name='doc_resurrect'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/addcomment/$', views_doc.add_comment, name='doc_add_comment'),
url(r'^ajax/internet_draft/?$', views_draft.doc_ajax_internet_draft, name="doc_ajax_internet_draft"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/stream/$', views_draft.change_stream, name='doc_change_stream'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/replaces/$', views_draft.replaces, name='doc_change_replaces'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/notify/$', views_draft.edit_notices, name='doc_change_notify'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/status/$', views_draft.change_intention, name='doc_change_intended_status'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/telechat/$', views_draft.telechat_date, name='doc_change_telechat_date'),

View file

@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse as urlreverse
from django.template.loader import render_to_string
from django.template import RequestContext
from django import forms
from django.utils import simplejson
from django.utils.html import strip_tags
from django.db.models import Max
from django.conf import settings
@ -23,6 +24,7 @@ from ietf.doc.lastcall import request_last_call
from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.person.forms import EmailsField
from ietf.group.models import Group
from ietf.secr.lib import jsonapi
from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.utils import update_stream
@ -268,6 +270,143 @@ def change_stream(request, name):
),
context_instance=RequestContext(request))
@jsonapi
def doc_ajax_internet_draft(request):
if request.method != 'GET' or not request.GET.has_key('term'):
return { 'success' : False, 'error' : 'No term submitted or not GET' }
q = request.GET.get('term')
results = DocAlias.objects.filter(name__icontains=q)
if (results.count() > 20):
results = results[:20]
elif results.count() == 0:
return { 'success' : False, 'error' : "No results" }
response = [dict(id=r.id, label=r.name) for r in results]
return response
def collect_email_addresses(emails, doc):
for author in doc.authors.all():
if author.address not in emails:
emails[author.address] = '"%s"' % (author.person.name)
if doc.group.acronym != 'none':
for role in doc.group.role_set.filter(name='chair'):
if role.email.address not in emails:
emails[role.email.address] = '"%s"' % (role.person.name)
if doc.group.type.slug == 'wg':
address = '%s-ads@tools.ietf.org' % doc.group.acronym
if address not in emails:
emails[address] = '"%s-ads"' % (doc.group.acronym)
elif doc.group.type.slug == 'rg':
email = doc.group.parent.role_set.filter(name='char')[0].email
if email.address not in emails:
emails[email.address] = '"%s"' % (email.person.name)
if doc.shepherd:
address = doc.shepherd.email_address();
if address not in emails:
emails[address] = '"%s"' % (doc.shepherd.name)
return emails
class ReplacesForm(forms.Form):
replaces = forms.CharField(max_length=512,widget=forms.HiddenInput)
comment = forms.CharField(widget=forms.Textarea, required=False)
def __init__(self, *args, **kwargs):
self.doc = kwargs.pop('doc')
super(ReplacesForm, self).__init__(*args, **kwargs)
drafts = {}
for d in self.doc.related_that_doc("replaces"):
drafts[d.id] = d.document.name
self.initial['replaces'] = simplejson.dumps(drafts)
def clean_replaces(self):
data = self.cleaned_data['replaces'].strip()
if data:
ids = [int(x) for x in simplejson.loads(data)]
else:
return []
objects = []
for id in ids:
try:
d = DocAlias.objects.get(pk=id)
except DocAlias.DoesNotExist, e:
raise forms.ValidationError("ERROR: %s not found for id %d" % DocAlias._meta.verbos_name, id)
if d.document == self.doc:
raise forms.ValidationError("ERROR: A draft can't replace itself")
if d.document.type_id == "draft" and d.document.get_state_slug() == "rfc":
raise forms.ValidationError("ERROR: A draft can't replace an RFC")
objects.append(d)
return objects
def replaces(request, name):
"""Change 'replaces' set of a Document of type 'draft' , notifying parties
as necessary and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.type_id != 'draft':
raise Http404
if not (has_role(request.user, ("Secretariat", "Area Director"))
or is_authorized_in_doc_stream(request.user, doc)):
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
login = request.user.get_profile()
if request.method == 'POST':
form = ReplacesForm(request.POST, doc=doc)
if form.is_valid():
new_replaces = set(form.cleaned_data['replaces'])
comment = form.cleaned_data['comment'].strip()
old_replaces = set(doc.related_that_doc("replaces"))
if new_replaces != old_replaces:
save_document_in_history(doc)
emails = {}
emails = collect_email_addresses(emails, doc)
relationship = DocRelationshipName.objects.get(slug='replaces')
for d in old_replaces:
if d not in new_replaces:
emails = collect_email_addresses(emails, d.document)
RelatedDocument.objects.filter(source=doc, target=d,
relationship=relationship).delete()
for d in new_replaces:
if d not in old_replaces:
emails = collect_email_addresses(emails, d.document)
RelatedDocument.objects.create(source=doc, target=d,
relationship=relationship)
e = DocEvent(doc=doc,by=login,type='changed_document')
new_replaces_names = ", ".join([d.name for d in new_replaces])
if not new_replaces_names:
new_replaces_names = "None"
old_replaces_names = ", ".join([d.name for d in old_replaces])
if not old_replaces_names:
old_replaces_names = "None"
e.desc = u"Set of documents this document replaces changed to <b>%s</b> from %s"% (new_replaces_names, old_replaces_names)
e.save()
email_desc = e.desc
if comment:
c = DocEvent(doc=doc,by=login,type="added_comment")
c.desc = comment
c.save()
email_desc += "\n"+c.desc
doc.time = e.time
doc.save()
email_list = []
for key in sorted(emails):
if emails[key]:
email_list.append('%s <%s>' % (emails[key], key))
else:
email_list.append('<%s>' % key)
email_string = ", ".join(email_list)
send_mail(request, email_string,
"DraftTracker Mail System <iesg-secretary@ietf.org>",
"%s updated by %s" % (doc.file_tag, login),
"doc/mail/change_notice.txt",
dict(text=html_to_text(email_desc),
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
return HttpResponseRedirect(doc.get_absolute_url())
else:
form = ReplacesForm(doc=doc)
return render_to_response('idrfc/change_replaces.html',
dict(form=form,
doc=doc,
),
context_instance=RequestContext(request))
class ChangeIntentionForm(forms.Form):
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
comment = forms.CharField(widget=forms.Textarea, required=False)

View file

@ -35,8 +35,19 @@
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
{% endif %}
{% if replaces %}<div>Replaces: {{ replaces|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if can_edit_stream_info %}
<div>
{% if replaces %}
<a class="editlink" href="{% url doc_change_replaces name=doc.name %}">Replaces</a>:
{{ replaces|join:", "|urlize_ietf_docs }}
{% else %}
<a class="editlink" href="{% url doc_change_replaces name=doc.name %}">Replaces: None</a>
{% endif %}
</a>
</div>
{% else %}
{% if replaces %}<div>Replaces: {{ replaces|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% endif %}
{% if replaced_by %}<div>Replaced by: {{ replaced_by|join:", "|urlize_ietf_docs }}</div>{% endif %}
</td>
</tr>

View file

@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block title %}Change which documents {{ doc }} replaces{% endblock %}
{% block pagehead %}{{ block.super }}
<!--TODO: The template will later load another version of jQuery.
We should eliminate the duplication.-->
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery-1.5.1.min.js"></script>
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery-ui-1.8.9.min.js"></script>
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery.json-2.2.min.js"></script>
<link rel="stylesheet" href="{{ SECR_STATIC_URL }}css/redmond/jquery-ui-1.8.9.custom.css" type="text/css" media="screen" charset="utf-8"/>
<script type="text/javascript">
// Free up the $ and jQuery names.
j=$.noConflict(true);
j(document).ready(function($) {
function add_to_list(list, id, label) {
list.append('<li><a href="' + id
+ '"><img src="{{ SECR_STATIC_URL }}img/delete.png" alt="delete"></a> '
+ label + '</li>');
}
function setup_ajax(field, list, searchfield, url) {
var datastore = {};
window.field = field;
if (field.val() != '') {
datastore = $.parseJSON(field.val());
}
$.each(datastore, function(k, v) {
add_to_list(list, k, v);
});
searchfield.autocomplete({
source: url,
minLength: 1,
select: function(event, ui) {
datastore[ui.item.id] = ui.item.label;
field.val($.toJSON(datastore));
searchfield.val('');
add_to_list(list, ui.item.id, ui.item.label);
return false;
}
});
// Automatically select the first element in the autocomplete list, so
// that hitting Enter adds it.
$(".ui-autocomplete-input").live("autocompleteopen", function() {
var autocomplete = $(this).data("autocomplete");
var menu = autocomplete.menu;
menu.activate($.Event({ type: "mouseenter" }),
menu.element.children().first());
});
list.delegate("a", "click", function() {
delete datastore[$(this).attr("href")];
field.val($.toJSON(datastore));
$(this).closest("li").remove();
return false;
});
}
setup_ajax($("#id_replaces"), $("#replaces_list"),
$("#id_replaces_search"), "{% url doc_ajax_internet_draft %}");
});
</script>
{% endblock %}
{% block content %}
<h1>Change which documents {{ doc }} replaces</h1>
<form class="change-replaces" action="" method="post">
{{ form.non_field_errors }}
{{ form.replaces.label_tag }}
<input type="text" id="id_replaces_search">
{{ form.replaces }}
<ul id="replaces_list"></ul>
{{ form.replaces.errors }}
<table>
<tr>
<td>{{ form.comment.label_tag }}</td>
<td>{{ form.comment.errors }} {{ form.comment }}</td>
</tr>
<tr>
<td colspan="2" class="actions">
<a href="{{ doc.get_absolute_url }}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}