Merged in Ole's submit-time draft replacement information work, as tested and updated by Robert and Adam.
- Legacy-Id: 9829
This commit is contained in:
commit
953d639ae3
|
@ -4,6 +4,8 @@ from django.utils.html import escape
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.urlresolvers import reverse as urlreverse
|
from django.core.urlresolvers import reverse as urlreverse
|
||||||
|
|
||||||
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
from ietf.doc.models import Document, DocAlias
|
from ietf.doc.models import Document, DocAlias
|
||||||
from ietf.doc.utils import uppercase_std_abbreviated_name
|
from ietf.doc.utils import uppercase_std_abbreviated_name
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ from ietf.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDoc
|
||||||
from ietf.doc.utils import needed_ballot_positions
|
from ietf.doc.utils import needed_ballot_positions
|
||||||
from ietf.person.models import Person
|
from ietf.person.models import Person
|
||||||
from ietf.group.models import Group, Role
|
from ietf.group.models import Group, Role
|
||||||
|
from ietf.doc.models import Document
|
||||||
|
|
||||||
def email_state_changed(request, doc, text):
|
def email_state_changed(request, doc, text):
|
||||||
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
|
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
|
||||||
|
@ -488,3 +489,30 @@ def email_stream_tags_changed(request, doc, added_tags, removed_tags, by, commen
|
||||||
by=by,
|
by=by,
|
||||||
comment=comment))
|
comment=comment))
|
||||||
|
|
||||||
|
def send_review_possibly_replaces_request(request, doc):
|
||||||
|
to_email = []
|
||||||
|
|
||||||
|
if doc.stream_id == "ietf":
|
||||||
|
to_email.extend(r.formatted_email() for r in Role.objects.filter(group=doc.group, name="chair").select_related("email", "person"))
|
||||||
|
elif doc.stream_id == "iab":
|
||||||
|
to_email.append("IAB Stream <iab-stream@iab.org>")
|
||||||
|
elif doc.stream_id == "ise":
|
||||||
|
to_email.append("Independent Submission Editor <rfc-ise@rfc-editor.org>")
|
||||||
|
elif doc.stream_id == "irtf":
|
||||||
|
to_email.append("IRSG <irsg@irtf.org>")
|
||||||
|
|
||||||
|
possibly_replaces = Document.objects.filter(name__in=[alias.name for alias in doc.related_that_doc("possibly-replaces")])
|
||||||
|
other_chairs = Role.objects.filter(group__in=[other.group for other in possibly_replaces], name="chair").select_related("email", "person")
|
||||||
|
to_email.extend(r.formatted_email() for r in other_chairs)
|
||||||
|
|
||||||
|
if not to_email:
|
||||||
|
to_email.append("internet-drafts@ietf.org")
|
||||||
|
|
||||||
|
if to_email:
|
||||||
|
send_mail(request, list(set(to_email)), settings.DEFAULT_FROM_EMAIL,
|
||||||
|
'Review of suggested possible replacements for %s-%s needed' % (doc.name, doc.rev),
|
||||||
|
'doc/mail/review_possibly_replaces_request.txt', {
|
||||||
|
'doc': doc,
|
||||||
|
'possibly_replaces': doc.related_that_doc("possibly-replaces"),
|
||||||
|
'review_url': settings.IDTRACKER_BASE_URL + urlreverse("doc_review_possibly_replaces", kwargs={ "name": doc.name }),
|
||||||
|
})
|
||||||
|
|
|
@ -654,6 +654,8 @@ EVENT_TYPES = [
|
||||||
("completed_resurrect", "Completed resurrect"),
|
("completed_resurrect", "Completed resurrect"),
|
||||||
("changed_consensus", "Changed consensus"),
|
("changed_consensus", "Changed consensus"),
|
||||||
("published_rfc", "Published RFC"),
|
("published_rfc", "Published RFC"),
|
||||||
|
("added_suggested_replaces", "Added suggested replacement relationships"),
|
||||||
|
("reviewed_suggested_replaces", "Reviewed suggested replacement relationships"),
|
||||||
|
|
||||||
# WG events
|
# WG events
|
||||||
("changed_group", "Changed group"),
|
("changed_group", "Changed group"),
|
||||||
|
|
|
@ -11,7 +11,7 @@ import debug # pyflakes:ignore
|
||||||
|
|
||||||
from ietf.doc.models import ( Document, DocAlias, DocReminder, DocumentAuthor, DocEvent,
|
from ietf.doc.models import ( Document, DocAlias, DocReminder, DocumentAuthor, DocEvent,
|
||||||
ConsensusDocEvent, LastCallDocEvent, RelatedDocument, State, TelechatDocEvent,
|
ConsensusDocEvent, LastCallDocEvent, RelatedDocument, State, TelechatDocEvent,
|
||||||
WriteupDocEvent, BallotDocEvent)
|
WriteupDocEvent, BallotDocEvent, DocRelationshipName)
|
||||||
from ietf.doc.utils import get_tags_for_stream_id
|
from ietf.doc.utils import get_tags_for_stream_id
|
||||||
from ietf.name.models import StreamName, IntendedStdLevelName, DocTagName
|
from ietf.name.models import StreamName, IntendedStdLevelName, DocTagName
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
|
@ -1255,11 +1255,14 @@ class ChangeReplacesTests(TestCase):
|
||||||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||||
|
|
||||||
# Post that says replacea replaces base a
|
# Post that says replacea replaces base a
|
||||||
|
RelatedDocument.objects.create(source=self.replacea, target=self.basea.docalias_set.first(),
|
||||||
|
relationship=DocRelationshipName.objects.get(slug="possibly-replaces"))
|
||||||
self.assertEqual(self.basea.get_state().slug,'active')
|
self.assertEqual(self.basea.get_state().slug,'active')
|
||||||
r = self.client.post(url, dict(replaces=str(DocAlias.objects.get(name=self.basea.name).id)))
|
r = self.client.post(url, dict(replaces=str(DocAlias.objects.get(name=self.basea.name).id)))
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1)
|
self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1)
|
||||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||||
|
self.assertTrue(not RelatedDocument.objects.filter(relationship='possibly-replaces', source=self.replacea))
|
||||||
|
|
||||||
# Post that says replaceboth replaces both base a and base b
|
# Post that says replaceboth replaces both base a and base b
|
||||||
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name))
|
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name))
|
||||||
|
@ -1281,3 +1284,21 @@ class ChangeReplacesTests(TestCase):
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
|
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
|
||||||
|
|
||||||
|
def test_review_possibly_replaces(self):
|
||||||
|
replaced = self.basea.docalias_set.first()
|
||||||
|
RelatedDocument.objects.create(source=self.replacea, target=replaced,
|
||||||
|
relationship=DocRelationshipName.objects.get(slug="possibly-replaces"))
|
||||||
|
|
||||||
|
url = urlreverse('doc_review_possibly_replaces', kwargs=dict(name=self.replacea.name))
|
||||||
|
login_testing_unauthorized(self, "secretary", url)
|
||||||
|
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEquals(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertEquals(len(q('form[name=review-suggested-replaces]')), 1)
|
||||||
|
|
||||||
|
r = self.client.post(url, dict(replaces=[replaced.pk]))
|
||||||
|
self.assertEquals(r.status_code, 302)
|
||||||
|
self.assertTrue(not self.replacea.related_that_doc("possibly-replaces"))
|
||||||
|
self.assertEqual(len(self.replacea.related_that_doc("replaces")), 1)
|
||||||
|
self.assertEquals(Document.objects.get(pk=self.basea.pk).get_state().slug, 'repl')
|
||||||
|
|
|
@ -79,6 +79,7 @@ urlpatterns = patterns('',
|
||||||
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/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/replaces/$', views_draft.replaces, name='doc_change_replaces'),
|
||||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/notify/$', views_doc.edit_notify, name='doc_change_notify'),
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/notify/$', views_doc.edit_notify, name='doc_change_notify'),
|
||||||
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/suggested-replaces/$', views_draft.review_possibly_replaces, name='doc_review_possibly_replaces'),
|
||||||
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/status/$', views_draft.change_intention, name='doc_change_intended_status'),
|
||||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/telechat/$', views_doc.telechat_date, name='doc_change_telechat_date'),
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/telechat/$', views_doc.telechat_date, name='doc_change_telechat_date'),
|
||||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/iesgnote/$', views_draft.edit_iesg_note, name='doc_change_iesg_note'),
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/iesgnote/$', views_draft.edit_iesg_note, name='doc_change_iesg_note'),
|
||||||
|
|
|
@ -10,8 +10,7 @@ from django.db.models.query import EmptyQuerySet
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils.html import strip_tags, escape
|
from django.utils.html import strip_tags, escape
|
||||||
|
|
||||||
from ietf.utils import markup_txt
|
from ietf.doc.models import Document, DocHistory, State
|
||||||
from ietf.doc.models import Document, DocHistory
|
|
||||||
from ietf.doc.models import DocAlias, RelatedDocument, BallotType, DocReminder
|
from ietf.doc.models import DocAlias, RelatedDocument, BallotType, DocReminder
|
||||||
from ietf.doc.models import DocEvent, BallotDocEvent, NewRevisionDocEvent, StateDocEvent
|
from ietf.doc.models import DocEvent, BallotDocEvent, NewRevisionDocEvent, StateDocEvent
|
||||||
from ietf.doc.models import save_document_in_history, STATUSCHANGE_RELATIONS
|
from ietf.doc.models import save_document_in_history, STATUSCHANGE_RELATIONS
|
||||||
|
@ -19,7 +18,7 @@ from ietf.name.models import DocReminderTypeName, DocRelationshipName
|
||||||
from ietf.group.models import Role
|
from ietf.group.models import Role
|
||||||
from ietf.person.models import Email
|
from ietf.person.models import Email
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role
|
||||||
from ietf.utils import draft
|
from ietf.utils import draft, markup_txt
|
||||||
from ietf.utils.mail import send_mail
|
from ietf.utils.mail import send_mail
|
||||||
|
|
||||||
#FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
|
#FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
|
||||||
|
@ -314,9 +313,9 @@ def update_reminder(doc, reminder_type_slug, event, due_date):
|
||||||
reminder.active = False
|
reminder.active = False
|
||||||
reminder.save()
|
reminder.save()
|
||||||
|
|
||||||
def prettify_std_name(n):
|
def prettify_std_name(n, spacing=" "):
|
||||||
if re.match(r"(rfc|bcp|fyi|std)[0-9]+", n):
|
if re.match(r"(rfc|bcp|fyi|std)[0-9]+", n):
|
||||||
return n[:3].upper() + " " + n[3:]
|
return n[:3].upper() + spacing + n[3:]
|
||||||
else:
|
else:
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
@ -459,6 +458,75 @@ def rebuild_reference_relations(doc,filename=None):
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
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 and 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':
|
||||||
|
for role in doc.group.parent.role_set.filter(name='chair'):
|
||||||
|
if role.email.address not in emails:
|
||||||
|
emails[role.email.address] = '"%s"' % (role.person.name)
|
||||||
|
if doc.shepherd and doc.shepherd.address not in emails:
|
||||||
|
emails[doc.shepherd.address] = u'"%s"' % (doc.shepherd.person.name or "")
|
||||||
|
|
||||||
|
def set_replaces_for_document(request, doc, new_replaces, by, email_subject, email_comment=""):
|
||||||
|
emails = {}
|
||||||
|
collect_email_addresses(emails, doc)
|
||||||
|
|
||||||
|
relationship = DocRelationshipName.objects.get(slug='replaces')
|
||||||
|
old_replaces = doc.related_that_doc("replaces")
|
||||||
|
|
||||||
|
for d in old_replaces:
|
||||||
|
if d not in new_replaces:
|
||||||
|
collect_email_addresses(emails, d.document)
|
||||||
|
RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete()
|
||||||
|
if not RelatedDocument.objects.filter(target=d, relationship=relationship):
|
||||||
|
s = 'active' if d.document.expires > datetime.datetime.now() else 'expired'
|
||||||
|
d.document.set_state(State.objects.get(type='draft', slug=s))
|
||||||
|
|
||||||
|
for d in new_replaces:
|
||||||
|
if d not in old_replaces:
|
||||||
|
collect_email_addresses(emails, d.document)
|
||||||
|
RelatedDocument.objects.create(source=doc, target=d, relationship=relationship)
|
||||||
|
d.document.set_state(State.objects.get(type='draft', slug='repl'))
|
||||||
|
|
||||||
|
e = DocEvent(doc=doc, by=by, type='changed_document')
|
||||||
|
new_replaces_names = u", ".join(d.name for d in new_replaces) or u"None"
|
||||||
|
old_replaces_names = u", ".join(d.name for d in old_replaces) or u"None"
|
||||||
|
e.desc = u"This document now replaces <b>%s</b> instead of %s" % (new_replaces_names, old_replaces_names)
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
# make sure there are no lingering suggestions duplicating new replacements
|
||||||
|
RelatedDocument.objects.filter(source=doc, target__in=new_replaces, relationship="possibly-replaces").delete()
|
||||||
|
|
||||||
|
email_desc = e.desc.replace(", ", "\n ")
|
||||||
|
|
||||||
|
if email_comment:
|
||||||
|
email_desc += "\n" + email_comment
|
||||||
|
|
||||||
|
to = [
|
||||||
|
u'%s <%s>' % (emails[email], email) if emails[email] else u'<%s>' % email
|
||||||
|
for email in sorted(emails)
|
||||||
|
]
|
||||||
|
|
||||||
|
from ietf.doc.mails import html_to_text
|
||||||
|
|
||||||
|
send_mail(request, to,
|
||||||
|
"DraftTracker Mail System <iesg-secretary@ietf.org>",
|
||||||
|
email_subject,
|
||||||
|
"doc/mail/change_notice.txt",
|
||||||
|
dict(text=html_to_text(email_desc),
|
||||||
|
doc=doc,
|
||||||
|
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
|
||||||
|
|
||||||
def check_common_doc_name_rules(name):
|
def check_common_doc_name_rules(name):
|
||||||
"""Check common rules for document names for use in forms, throws
|
"""Check common rules for document names for use in forms, throws
|
||||||
ValidationError in case there's a problem."""
|
ValidationError in case there's a problem."""
|
||||||
|
|
|
@ -156,6 +156,11 @@ def document_main(request, name, rev=None):
|
||||||
person__user=request.user)))
|
person__user=request.user)))
|
||||||
can_edit_iana_state = has_role(request.user, ("Secretariat", "IANA"))
|
can_edit_iana_state = has_role(request.user, ("Secretariat", "IANA"))
|
||||||
|
|
||||||
|
can_edit_replaces = has_role(request.user, ("Area Director", "Secretariat", "WG Chair", "RG Chair", "WG Secretary", "RG Secretary"))
|
||||||
|
|
||||||
|
is_author = unicode(request.user) in set([email.address for email in doc.authors.all()])
|
||||||
|
can_view_possibly_replaces = can_edit_replaces or is_author
|
||||||
|
|
||||||
rfc_number = name[3:] if name.startswith("") else None
|
rfc_number = name[3:] if name.startswith("") else None
|
||||||
draft_name = None
|
draft_name = None
|
||||||
for a in aliases:
|
for a in aliases:
|
||||||
|
@ -345,6 +350,8 @@ def document_main(request, name, rev=None):
|
||||||
|
|
||||||
replaces = [d.name for d in doc.related_that_doc("replaces")]
|
replaces = [d.name for d in doc.related_that_doc("replaces")]
|
||||||
replaced_by = [d.name for d in doc.related_that("replaces")]
|
replaced_by = [d.name for d in doc.related_that("replaces")]
|
||||||
|
possibly_replaces = [d.name for d in doc.related_that_doc("possibly-replaces")]
|
||||||
|
possibly_replaced_by = [d.name for d in doc.related_that("possibly-replaces")]
|
||||||
published = doc.latest_event(type="published_rfc")
|
published = doc.latest_event(type="published_rfc")
|
||||||
started_iesg_process = doc.latest_event(type="started_iesg_process")
|
started_iesg_process = doc.latest_event(type="started_iesg_process")
|
||||||
|
|
||||||
|
@ -367,6 +374,8 @@ def document_main(request, name, rev=None):
|
||||||
can_edit_notify=can_edit_notify,
|
can_edit_notify=can_edit_notify,
|
||||||
can_edit_iana_state=can_edit_iana_state,
|
can_edit_iana_state=can_edit_iana_state,
|
||||||
can_edit_consensus=can_edit_consensus,
|
can_edit_consensus=can_edit_consensus,
|
||||||
|
can_edit_replaces=can_edit_replaces,
|
||||||
|
can_view_possibly_replaces=can_view_possibly_replaces,
|
||||||
|
|
||||||
rfc_number=rfc_number,
|
rfc_number=rfc_number,
|
||||||
draft_name=draft_name,
|
draft_name=draft_name,
|
||||||
|
@ -377,6 +386,8 @@ def document_main(request, name, rev=None):
|
||||||
|
|
||||||
replaces=replaces,
|
replaces=replaces,
|
||||||
replaced_by=replaced_by,
|
replaced_by=replaced_by,
|
||||||
|
possibly_replaces=possibly_replaces,
|
||||||
|
possibly_replaced_by=possibly_replaced_by,
|
||||||
updates=[prettify_std_name(d.name) for d in doc.related_that_doc("updates")],
|
updates=[prettify_std_name(d.name) for d in doc.related_that_doc("updates")],
|
||||||
updated_by=[prettify_std_name(d.document.canonical_name()) for d in doc.related_that("updates")],
|
updated_by=[prettify_std_name(d.document.canonical_name()) for d in doc.related_that("updates")],
|
||||||
obsoletes=[prettify_std_name(d.name) for d in doc.related_that_doc("obs")],
|
obsoletes=[prettify_std_name(d.name) for d in doc.related_that_doc("obs")],
|
||||||
|
|
|
@ -13,16 +13,17 @@ from django.contrib.auth.decorators import login_required
|
||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
from ietf.doc.models import ( Document, DocAlias, DocRelationshipName, RelatedDocument, State,
|
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
|
||||||
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS,
|
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS,
|
||||||
save_document_in_history )
|
save_document_in_history )
|
||||||
from ietf.doc.mails import ( email_ad, email_pulled_from_rfc_queue, email_resurrect_requested,
|
from ietf.doc.mails import ( email_ad, email_pulled_from_rfc_queue, email_resurrect_requested,
|
||||||
email_resurrection_completed, email_state_changed, email_stream_changed,
|
email_resurrection_completed, email_state_changed, email_stream_changed,
|
||||||
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
|
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
|
||||||
generate_publication_request, html_to_text )
|
generate_publication_request )
|
||||||
from ietf.doc.utils import ( add_state_change_event, can_adopt_draft,
|
from ietf.doc.utils import ( add_state_change_event, can_adopt_draft,
|
||||||
get_tags_for_stream_id, nice_consensus,
|
get_tags_for_stream_id, nice_consensus,
|
||||||
update_reminder, update_telechat, make_notify_changed_event, get_initial_notify )
|
update_reminder, update_telechat, make_notify_changed_event, get_initial_notify,
|
||||||
|
set_replaces_for_document )
|
||||||
from ietf.doc.lastcall import request_last_call
|
from ietf.doc.lastcall import request_last_call
|
||||||
from ietf.doc.fields import SearchableDocAliasesField
|
from ietf.doc.fields import SearchableDocAliasesField
|
||||||
from ietf.group.models import Group, Role
|
from ietf.group.models import Group, Role
|
||||||
|
@ -287,26 +288,6 @@ def doc_ajax_internet_draft(request):
|
||||||
response = [dict(id=r.id, label=r.name) for r in results]
|
response = [dict(id=r.id, label=r.name) for r in results]
|
||||||
return response
|
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':
|
|
||||||
for role in doc.group.parent.role_set.filter(name='chair'):
|
|
||||||
if role.email.address not in emails:
|
|
||||||
emails[role.email.address] = '"%s"' % (role.person.name)
|
|
||||||
if doc.shepherd and doc.shepherd.address not in emails:
|
|
||||||
emails[doc.shepherd.address] = u'"%s"' % (doc.shepherd.person.name or "")
|
|
||||||
return emails
|
|
||||||
|
|
||||||
class ReplacesForm(forms.Form):
|
class ReplacesForm(forms.Form):
|
||||||
replaces = SearchableDocAliasesField(required=False)
|
replaces = SearchableDocAliasesField(required=False)
|
||||||
comment = forms.CharField(widget=forms.Textarea, required=False)
|
comment = forms.CharField(widget=forms.Textarea, required=False)
|
||||||
|
@ -333,68 +314,96 @@ def replaces(request, name):
|
||||||
if not (has_role(request.user, ("Secretariat", "Area Director"))
|
if not (has_role(request.user, ("Secretariat", "Area Director"))
|
||||||
or is_authorized_in_doc_stream(request.user, doc)):
|
or is_authorized_in_doc_stream(request.user, doc)):
|
||||||
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
|
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
|
||||||
login = request.user.person
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = ReplacesForm(request.POST, doc=doc)
|
form = ReplacesForm(request.POST, doc=doc)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_replaces = set(form.cleaned_data['replaces'])
|
new_replaces = set(form.cleaned_data['replaces'])
|
||||||
comment = form.cleaned_data['comment'].strip()
|
comment = form.cleaned_data['comment'].strip()
|
||||||
old_replaces = set(doc.related_that_doc("replaces"))
|
old_replaces = set(doc.related_that_doc("replaces"))
|
||||||
|
by = request.user.person
|
||||||
|
|
||||||
if new_replaces != old_replaces:
|
if new_replaces != old_replaces:
|
||||||
save_document_in_history(doc)
|
save_document_in_history(doc)
|
||||||
emails = {}
|
doc.time = datetime.datetime.now()
|
||||||
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()
|
|
||||||
if not RelatedDocument.objects.filter(target=d, relationship=relationship):
|
|
||||||
d.document.set_state(State.objects.get(type='draft',slug='active' if d.document.expires>datetime.datetime.now() else 'expired'))
|
|
||||||
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)
|
|
||||||
d.document.set_state(State.objects.get(type='draft',slug='repl'))
|
|
||||||
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"This document now replaces <b>%s</b> instead of %s"% (new_replaces_names, old_replaces_names)
|
|
||||||
e.save()
|
|
||||||
email_desc = e.desc.replace(", ", "\n ")
|
|
||||||
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()
|
doc.save()
|
||||||
email_list = []
|
|
||||||
for key in sorted(emails):
|
set_replaces_for_document(request, doc, new_replaces, by=by,
|
||||||
if emails[key]:
|
email_subject="%s replacement status updated by %s" % (doc.name, by),
|
||||||
email_list.append('%s <%s>' % (emails[key], key))
|
email_comment=comment)
|
||||||
else:
|
|
||||||
email_list.append('<%s>' % key)
|
if comment:
|
||||||
email_string = ", ".join(email_list)
|
DocEvent.objects.create(doc=doc, by=by, type="added_comment", desc=comment)
|
||||||
send_mail(request, email_string,
|
|
||||||
"DraftTracker Mail System <iesg-secretary@ietf.org>",
|
|
||||||
"%s updated by %s" % (doc.name, 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())
|
return HttpResponseRedirect(doc.get_absolute_url())
|
||||||
else:
|
else:
|
||||||
form = ReplacesForm(doc=doc)
|
form = ReplacesForm(doc=doc)
|
||||||
return render_to_response('doc/draft/change_replaces.html',
|
return render(request, 'doc/draft/change_replaces.html',
|
||||||
dict(form=form,
|
dict(form=form,
|
||||||
doc=doc,
|
doc=doc,
|
||||||
),
|
))
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
class SuggestedReplacesForm(forms.Form):
|
||||||
|
replaces = forms.ModelMultipleChoiceField(queryset=DocAlias.objects.all(),
|
||||||
|
label="Suggestions", required=False, widget=forms.CheckboxSelectMultiple,
|
||||||
|
help_text="Select only the documents that are replaced by this document")
|
||||||
|
comment = forms.CharField(label="Optional comment", widget=forms.Textarea, required=False)
|
||||||
|
|
||||||
|
def __init__(self, suggested, *args, **kwargs):
|
||||||
|
super(SuggestedReplacesForm, self).__init__(*args, **kwargs)
|
||||||
|
pks = [d.pk for d in suggested]
|
||||||
|
self.fields["replaces"].initial = pks
|
||||||
|
self.fields["replaces"].queryset = self.fields["replaces"].queryset.filter(pk__in=pks)
|
||||||
|
self.fields["replaces"].choices = [(d.pk, d.name) for d in suggested]
|
||||||
|
|
||||||
|
def review_possibly_replaces(request, name):
|
||||||
|
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")
|
||||||
|
|
||||||
|
suggested = list(doc.related_that_doc("possibly-replaces"))
|
||||||
|
if not suggested:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = SuggestedReplacesForm(suggested, request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
replaces = set(form.cleaned_data['replaces'])
|
||||||
|
old_replaces = set(doc.related_that_doc("replaces"))
|
||||||
|
new_replaces = old_replaces.union(replaces)
|
||||||
|
|
||||||
|
comment = form.cleaned_data['comment'].strip()
|
||||||
|
by = request.user.person
|
||||||
|
|
||||||
|
save_document_in_history(doc)
|
||||||
|
doc.time = datetime.datetime.now()
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
# all suggestions reviewed, so get rid of them
|
||||||
|
DocEvent.objects.create(doc=doc, by=by, type="reviewed_suggested_replaces",
|
||||||
|
desc="Reviewed suggested replacement relationships: %s" % ", ".join(d.name for d in suggested))
|
||||||
|
RelatedDocument.objects.filter(source=doc, target__in=suggested,relationship__slug='possibly-replaces').delete()
|
||||||
|
|
||||||
|
if new_replaces != old_replaces:
|
||||||
|
set_replaces_for_document(request, doc, new_replaces, by=by,
|
||||||
|
email_subject="%s replacement status updated by %s" % (doc.name, by),
|
||||||
|
email_comment=comment)
|
||||||
|
|
||||||
|
if comment:
|
||||||
|
DocEvent.objects.create(doc=doc, by=by, type="added_comment", desc=comment)
|
||||||
|
|
||||||
|
return HttpResponseRedirect(doc.get_absolute_url())
|
||||||
|
else:
|
||||||
|
form = SuggestedReplacesForm(suggested)
|
||||||
|
|
||||||
|
return render(request, 'doc/draft/review_possibly_replaces.html',
|
||||||
|
dict(form=form,
|
||||||
|
doc=doc,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
class ChangeIntentionForm(forms.Form):
|
class ChangeIntentionForm(forms.Form):
|
||||||
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
|
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
|
||||||
|
|
|
@ -293,6 +293,17 @@
|
||||||
"model": "name.docrelationshipname",
|
"model": "name.docrelationshipname",
|
||||||
"pk": "toexp"
|
"pk": "toexp"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 0,
|
||||||
|
"revname": "Possibly Replaced By",
|
||||||
|
"used": true,
|
||||||
|
"name": "Possibly Replaces",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.docrelationshipname",
|
||||||
|
"pk": "possibly-replaces"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"order": 3,
|
"order": 3,
|
||||||
|
|
19
ietf/name/migrations/0005_add_sug_replaces.py
Normal file
19
ietf/name/migrations/0005_add_sug_replaces.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def add_possibly_replaces(apps, schema_editor):
|
||||||
|
|
||||||
|
DocRelationshipName = apps.get_model("name","DocRelationshipName")
|
||||||
|
DocRelationshipName.objects.create(slug='possibly-replaces',name='Possibly Replaces',revname='Possibly Replaced By')
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('name', '0004_auto_20150318_1140'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(add_possibly_replaces)
|
||||||
|
]
|
|
@ -12,6 +12,7 @@ import debug # pyflakes:ignore
|
||||||
from ietf.doc.models import Document
|
from ietf.doc.models import Document
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role
|
||||||
|
from ietf.doc.fields import SearchableDocAliasesField
|
||||||
from ietf.meeting.models import Meeting
|
from ietf.meeting.models import Meeting
|
||||||
from ietf.submit.models import Submission, Preapproval
|
from ietf.submit.models import Submission, Preapproval
|
||||||
from ietf.submit.utils import validate_submission_rev, validate_submission_document_date
|
from ietf.submit.utils import validate_submission_rev, validate_submission_document_date
|
||||||
|
@ -250,6 +251,9 @@ class NameEmailForm(forms.Form):
|
||||||
line += u" <%s>" % email
|
line += u" <%s>" % email
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
class ReplacesForm(forms.Form):
|
||||||
|
replaces = SearchableDocAliasesField(required=False, help_text="Any drafts that this document replaces (approval required for replacing a draft you are not the author of)")
|
||||||
|
|
||||||
class EditSubmissionForm(forms.ModelForm):
|
class EditSubmissionForm(forms.ModelForm):
|
||||||
title = forms.CharField(required=True, max_length=255)
|
title = forms.CharField(required=True, max_length=255)
|
||||||
rev = forms.CharField(label=u'Revision', max_length=2, required=True)
|
rev = forms.CharField(label=u'Revision', max_length=2, required=True)
|
||||||
|
|
20
ietf/submit/migrations/0003_auto_20150713_1104.py
Normal file
20
ietf/submit/migrations/0003_auto_20150713_1104.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('submit', '0002_auto_20150430_0847'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='submission',
|
||||||
|
name='replaces',
|
||||||
|
field=models.CharField(max_length=1000, blank=True),
|
||||||
|
preserve_default=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -33,7 +33,7 @@ class Submission(models.Model):
|
||||||
pages = models.IntegerField(null=True, blank=True)
|
pages = models.IntegerField(null=True, blank=True)
|
||||||
authors = models.TextField(blank=True, help_text="List of author names and emails, one author per line, e.g. \"John Doe <john@example.org>\".")
|
authors = models.TextField(blank=True, help_text="List of author names and emails, one author per line, e.g. \"John Doe <john@example.org>\".")
|
||||||
note = models.TextField(blank=True)
|
note = models.TextField(blank=True)
|
||||||
replaces = models.CharField(max_length=255, blank=True)
|
replaces = models.CharField(max_length=1000, blank=True)
|
||||||
|
|
||||||
first_two_pages = models.TextField(blank=True)
|
first_two_pages = models.TextField(blank=True)
|
||||||
file_types = models.CharField(max_length=50, blank=True)
|
file_types = models.CharField(max_length=50, blank=True)
|
||||||
|
|
|
@ -17,7 +17,7 @@ from ietf.meeting.models import Meeting
|
||||||
from ietf.submit.utils import expirable_submissions, expire_submission, ensure_person_email_info_exists
|
from ietf.submit.utils import expirable_submissions, expire_submission, ensure_person_email_info_exists
|
||||||
from ietf.person.models import Person
|
from ietf.person.models import Person
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.doc.models import Document, DocEvent, State, BallotDocEvent, BallotPositionDocEvent, DocumentAuthor
|
from ietf.doc.models import Document, DocAlias, DocEvent, State, BallotDocEvent, BallotPositionDocEvent, DocumentAuthor
|
||||||
from ietf.submit.models import Submission, Preapproval
|
from ietf.submit.models import Submission, Preapproval
|
||||||
|
|
||||||
class SubmitTests(TestCase):
|
class SubmitTests(TestCase):
|
||||||
|
@ -86,7 +86,7 @@ class SubmitTests(TestCase):
|
||||||
|
|
||||||
return status_url
|
return status_url
|
||||||
|
|
||||||
def supply_submitter(self, name, status_url, submitter_name, submitter_email):
|
def supply_extra_metadata(self, name, status_url, submitter_name, submitter_email, replaces):
|
||||||
# check the page
|
# check the page
|
||||||
r = self.client.get(status_url)
|
r = self.client.get(status_url)
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
|
@ -99,10 +99,12 @@ class SubmitTests(TestCase):
|
||||||
"action": action,
|
"action": action,
|
||||||
"submitter-name": submitter_name,
|
"submitter-name": submitter_name,
|
||||||
"submitter-email": submitter_email,
|
"submitter-email": submitter_email,
|
||||||
|
"replaces": replaces,
|
||||||
})
|
})
|
||||||
|
|
||||||
submission = Submission.objects.get(name=name)
|
submission = Submission.objects.get(name=name)
|
||||||
self.assertEqual(submission.submitter, u"%s <%s>" % (submitter_name, submitter_email))
|
self.assertEqual(submission.submitter, u"%s <%s>" % (submitter_name, submitter_email))
|
||||||
|
self.assertEqual(submission.replaces, ",".join(d.name for d in DocAlias.objects.filter(pk__in=replaces.split(",") if replaces else [])))
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
@ -122,6 +124,27 @@ class SubmitTests(TestCase):
|
||||||
# submit new -> supply submitter info -> approve
|
# submit new -> supply submitter info -> approve
|
||||||
draft = make_test_data()
|
draft = make_test_data()
|
||||||
|
|
||||||
|
# prepare draft to suggest replace
|
||||||
|
sug_replaced_draft = Document.objects.create(
|
||||||
|
name="draft-ietf-ames-sug-replaced",
|
||||||
|
time=datetime.datetime.now(),
|
||||||
|
type_id="draft",
|
||||||
|
title="Draft to be suggested to be replaced",
|
||||||
|
stream_id="ietf",
|
||||||
|
group=Group.objects.get(acronym="ames"),
|
||||||
|
abstract="Blahblahblah.",
|
||||||
|
rev="01",
|
||||||
|
pages=2,
|
||||||
|
intended_std_level_id="ps",
|
||||||
|
ad=draft.ad,
|
||||||
|
expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE),
|
||||||
|
notify="aliens@example.mars",
|
||||||
|
note="",
|
||||||
|
)
|
||||||
|
sug_replaced_draft.set_state(State.objects.get(used=True, type="draft", slug="active"))
|
||||||
|
sug_replaced_alias = DocAlias.objects.create(document=sug_replaced_draft, name=sug_replaced_draft.name)
|
||||||
|
|
||||||
|
|
||||||
name = "draft-ietf-mars-testing-tests"
|
name = "draft-ietf-mars-testing-tests"
|
||||||
rev = "00"
|
rev = "00"
|
||||||
|
|
||||||
|
@ -129,7 +152,9 @@ class SubmitTests(TestCase):
|
||||||
|
|
||||||
# supply submitter info, then draft should be in and ready for approval
|
# supply submitter info, then draft should be in and ready for approval
|
||||||
mailbox_before = len(outbox)
|
mailbox_before = len(outbox)
|
||||||
r = self.supply_submitter(name, status_url, "Author Name", "author@example.com")
|
replaced_alias = draft.docalias_set.first()
|
||||||
|
r = self.supply_extra_metadata(name, status_url, "Author Name", "author@example.com",
|
||||||
|
replaces=str(replaced_alias.pk) + "," + str(sug_replaced_alias.pk))
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
status_url = r["Location"]
|
status_url = r["Location"]
|
||||||
|
@ -155,10 +180,11 @@ class SubmitTests(TestCase):
|
||||||
|
|
||||||
draft = Document.objects.get(docalias__name=name)
|
draft = Document.objects.get(docalias__name=name)
|
||||||
self.assertEqual(draft.rev, rev)
|
self.assertEqual(draft.rev, rev)
|
||||||
new_revision = draft.latest_event()
|
new_revision = draft.latest_event(type="new_revision")
|
||||||
self.assertEqual(draft.group.acronym, "mars")
|
self.assertEqual(draft.group.acronym, "mars")
|
||||||
self.assertEqual(new_revision.type, "new_revision")
|
self.assertEqual(new_revision.type, "new_revision")
|
||||||
self.assertEqual(new_revision.by.name, "Author Name")
|
self.assertEqual(new_revision.by.name, "Author Name")
|
||||||
|
self.assertTrue(draft.latest_event(type="added_suggested_replaces"))
|
||||||
self.assertTrue(not os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
|
self.assertTrue(not os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
|
||||||
self.assertTrue(os.path.exists(os.path.join(self.repository_dir, u"%s-%s.txt" % (name, rev))))
|
self.assertTrue(os.path.exists(os.path.join(self.repository_dir, u"%s-%s.txt" % (name, rev))))
|
||||||
self.assertEqual(draft.type_id, "draft")
|
self.assertEqual(draft.type_id, "draft")
|
||||||
|
@ -168,12 +194,22 @@ class SubmitTests(TestCase):
|
||||||
self.assertEqual(draft.authors.count(), 1)
|
self.assertEqual(draft.authors.count(), 1)
|
||||||
self.assertEqual(draft.authors.all()[0].get_name(), "Author Name")
|
self.assertEqual(draft.authors.all()[0].get_name(), "Author Name")
|
||||||
self.assertEqual(draft.authors.all()[0].address, "author@example.com")
|
self.assertEqual(draft.authors.all()[0].address, "author@example.com")
|
||||||
self.assertEqual(len(outbox), mailbox_before + 2)
|
self.assertEqual(draft.relations_that_doc("replaces").count(), 1)
|
||||||
self.assertTrue((u"I-D Action: %s" % name) in outbox[-2]["Subject"])
|
self.assertTrue(draft.relations_that_doc("replaces").first().target, replaced_alias)
|
||||||
self.assertTrue("Author Name" in unicode(outbox[-2]))
|
self.assertEqual(draft.relations_that_doc("possibly-replaces").count(), 1)
|
||||||
self.assertTrue("New Version Notification" in outbox[-1]["Subject"])
|
self.assertTrue(draft.relations_that_doc("possibly-replaces").first().target, sug_replaced_alias)
|
||||||
|
self.assertEqual(len(outbox), mailbox_before + 4)
|
||||||
|
self.assertTrue((u"I-D Action: %s" % name) in outbox[-3]["Subject"])
|
||||||
|
self.assertTrue("Author Name" in unicode(outbox[-3]))
|
||||||
|
self.assertTrue("New Version Notification" in outbox[-2]["Subject"])
|
||||||
|
self.assertTrue(name in unicode(outbox[-2]))
|
||||||
|
self.assertTrue("mars" in unicode(outbox[-2]))
|
||||||
|
# Check "Review of suggested possible replacements for..." mail
|
||||||
|
self.assertTrue("review" in outbox[-1]["Subject"].lower())
|
||||||
self.assertTrue(name in unicode(outbox[-1]))
|
self.assertTrue(name in unicode(outbox[-1]))
|
||||||
self.assertTrue("mars" in unicode(outbox[-1]))
|
self.assertTrue(sug_replaced_alias.name in unicode(outbox[-1]))
|
||||||
|
self.assertTrue("ameschairman" in outbox[-1]["To"].lower())
|
||||||
|
self.assertTrue("marschairman" in outbox[-1]["To"].lower())
|
||||||
|
|
||||||
def test_submit_existing(self):
|
def test_submit_existing(self):
|
||||||
# submit new revision of existing -> supply submitter info -> prev authors confirm
|
# submit new revision of existing -> supply submitter info -> prev authors confirm
|
||||||
|
@ -215,7 +251,7 @@ class SubmitTests(TestCase):
|
||||||
|
|
||||||
# supply submitter info, then previous authors get a confirmation email
|
# supply submitter info, then previous authors get a confirmation email
|
||||||
mailbox_before = len(outbox)
|
mailbox_before = len(outbox)
|
||||||
r = self.supply_submitter(name, status_url, "Submitter Name", "submitter@example.com")
|
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "submitter@example.com", replaces="")
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
status_url = r["Location"]
|
status_url = r["Location"]
|
||||||
r = self.client.get(status_url)
|
r = self.client.get(status_url)
|
||||||
|
@ -285,7 +321,7 @@ class SubmitTests(TestCase):
|
||||||
|
|
||||||
# supply submitter info, then draft should be be ready for email auth
|
# supply submitter info, then draft should be be ready for email auth
|
||||||
mailbox_before = len(outbox)
|
mailbox_before = len(outbox)
|
||||||
r = self.supply_submitter(name, status_url, "Submitter Name", "submitter@example.com")
|
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "submitter@example.com", replaces="")
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
status_url = r["Location"]
|
status_url = r["Location"]
|
||||||
|
@ -375,7 +411,7 @@ class SubmitTests(TestCase):
|
||||||
|
|
||||||
def test_edit_submission_and_force_post(self):
|
def test_edit_submission_and_force_post(self):
|
||||||
# submit -> edit
|
# submit -> edit
|
||||||
make_test_data()
|
draft = make_test_data()
|
||||||
|
|
||||||
name = "draft-ietf-mars-testing-tests"
|
name = "draft-ietf-mars-testing-tests"
|
||||||
rev = "00"
|
rev = "00"
|
||||||
|
@ -413,6 +449,7 @@ class SubmitTests(TestCase):
|
||||||
"edit-pages": "123",
|
"edit-pages": "123",
|
||||||
"submitter-name": "Some Random Test Person",
|
"submitter-name": "Some Random Test Person",
|
||||||
"submitter-email": "random@example.com",
|
"submitter-email": "random@example.com",
|
||||||
|
"replaces": str(draft.docalias_set.all().first().pk),
|
||||||
"edit-note": "no comments",
|
"edit-note": "no comments",
|
||||||
"authors-0-name": "Person 1",
|
"authors-0-name": "Person 1",
|
||||||
"authors-0-email": "person1@example.com",
|
"authors-0-email": "person1@example.com",
|
||||||
|
@ -429,6 +466,7 @@ class SubmitTests(TestCase):
|
||||||
self.assertEqual(submission.pages, 123)
|
self.assertEqual(submission.pages, 123)
|
||||||
self.assertEqual(submission.note, "no comments")
|
self.assertEqual(submission.note, "no comments")
|
||||||
self.assertEqual(submission.submitter, "Some Random Test Person <random@example.com>")
|
self.assertEqual(submission.submitter, "Some Random Test Person <random@example.com>")
|
||||||
|
self.assertEqual(submission.replaces, draft.docalias_set.all().first().name)
|
||||||
self.assertEqual(submission.state_id, "manual")
|
self.assertEqual(submission.state_id, "manual")
|
||||||
|
|
||||||
authors = submission.authors_parsed()
|
authors = submission.authors_parsed()
|
||||||
|
|
|
@ -4,8 +4,12 @@ import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from ietf.doc.models import Document, State, DocAlias, DocEvent, DocumentAuthor, NewRevisionDocEvent, save_document_in_history
|
from ietf.doc.models import Document, State, DocAlias, DocEvent, DocumentAuthor
|
||||||
|
from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
|
||||||
|
from ietf.doc.models import RelatedDocument, DocRelationshipName
|
||||||
from ietf.doc.utils import add_state_change_event, rebuild_reference_relations
|
from ietf.doc.utils import add_state_change_event, rebuild_reference_relations
|
||||||
|
from ietf.doc.utils import set_replaces_for_document
|
||||||
|
from ietf.doc.mails import send_review_possibly_replaces_request
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role
|
||||||
from ietf.name.models import StreamName
|
from ietf.name.models import StreamName
|
||||||
|
@ -215,12 +219,62 @@ def post_submission(request, submission):
|
||||||
move_files_to_repository(submission)
|
move_files_to_repository(submission)
|
||||||
submission.state = DraftSubmissionStateName.objects.get(slug="posted")
|
submission.state = DraftSubmissionStateName.objects.get(slug="posted")
|
||||||
|
|
||||||
|
new_replaces, new_possibly_replaces = update_replaces_from_submission(request, submission, draft)
|
||||||
|
|
||||||
announce_to_lists(request, submission)
|
announce_to_lists(request, submission)
|
||||||
announce_new_version(request, submission, draft, state_change_msg)
|
announce_new_version(request, submission, draft, state_change_msg)
|
||||||
announce_to_authors(request, submission)
|
announce_to_authors(request, submission)
|
||||||
|
|
||||||
|
if new_possibly_replaces:
|
||||||
|
send_review_possibly_replaces_request(request, draft)
|
||||||
|
|
||||||
submission.save()
|
submission.save()
|
||||||
|
|
||||||
|
def update_replaces_from_submission(request, submission, draft):
|
||||||
|
if not submission.replaces:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
is_secretariat = has_role(request.user, "Secretariat")
|
||||||
|
is_chair_of = []
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
is_chair_of = list(Group.objects.filter(role__person__user=request.user, role__name="chair"))
|
||||||
|
|
||||||
|
replaces = DocAlias.objects.filter(name__in=submission.replaces.split(",")).select_related("document", "document__group")
|
||||||
|
existing_replaces = list(draft.related_that_doc("replaces"))
|
||||||
|
existing_suggested = set(draft.related_that_doc("possibly-replaces"))
|
||||||
|
|
||||||
|
submitter_email = submission.submitter_parsed()["email"]
|
||||||
|
|
||||||
|
approved = []
|
||||||
|
suggested = []
|
||||||
|
for r in replaces:
|
||||||
|
if r in existing_replaces:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rdoc = r.document
|
||||||
|
|
||||||
|
if (is_secretariat
|
||||||
|
or (draft.group in is_chair_of and (rdoc.group.type_id == "individ" or rdoc.group in is_chair_of))
|
||||||
|
or (submitter_email and rdoc.authors.filter(address__iexact=submitter_email)).exists()):
|
||||||
|
approved.append(r)
|
||||||
|
else:
|
||||||
|
if r not in existing_suggested:
|
||||||
|
suggested.append(r)
|
||||||
|
|
||||||
|
by = request.user.person if request.user.is_authenticated() else Person.objects.get(name="(System)")
|
||||||
|
set_replaces_for_document(request, draft, existing_replaces + approved, by,
|
||||||
|
email_subject="%s replacement status set during submit by %s" % (draft.name, submission.submitter_parsed()["name"]))
|
||||||
|
|
||||||
|
|
||||||
|
if suggested:
|
||||||
|
possibly_replaces = DocRelationshipName.objects.get(slug="possibly-replaces")
|
||||||
|
for r in suggested:
|
||||||
|
RelatedDocument.objects.create(source=draft, target=r, relationship=possibly_replaces)
|
||||||
|
|
||||||
|
DocEvent.objects.create(doc=draft, by=by, type="added_suggested_replaces",
|
||||||
|
desc="Added suggested replacement relationships: %s" % ", ".join(d.name for d in suggested))
|
||||||
|
|
||||||
|
return approved, suggested
|
||||||
|
|
||||||
def get_person_from_name_email(name, email):
|
def get_person_from_name_email(name, email):
|
||||||
# try email
|
# try email
|
||||||
|
|
|
@ -6,14 +6,13 @@ from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse as urlreverse
|
from django.core.urlresolvers import reverse as urlreverse
|
||||||
from django.core.validators import validate_email, ValidationError
|
from django.core.validators import validate_email, ValidationError
|
||||||
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden
|
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.shortcuts import render_to_response
|
|
||||||
from django.template import RequestContext
|
|
||||||
|
|
||||||
from ietf.doc.models import Document
|
from ietf.doc.models import Document, DocAlias
|
||||||
|
from ietf.doc.utils import prettify_std_name
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.ietfauth.utils import has_role, role_required
|
from ietf.ietfauth.utils import has_role, role_required
|
||||||
from ietf.submit.forms import UploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm
|
from ietf.submit.forms import UploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm, ReplacesForm
|
||||||
from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, submission_confirmation_email_list, send_manual_post_request
|
from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, submission_confirmation_email_list, send_manual_post_request
|
||||||
from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName
|
from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName
|
||||||
from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user
|
from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user
|
||||||
|
@ -98,18 +97,15 @@ def upload_submission(request):
|
||||||
else:
|
else:
|
||||||
form = UploadForm(request=request)
|
form = UploadForm(request=request)
|
||||||
|
|
||||||
return render_to_response('submit/upload_submission.html',
|
return render(request, 'submit/upload_submission.html',
|
||||||
{'selected': 'index',
|
{'selected': 'index',
|
||||||
'form': form},
|
'form': form})
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
def note_well(request):
|
def note_well(request):
|
||||||
return render_to_response('submit/note_well.html', {'selected': 'notewell'},
|
return render(request, 'submit/note_well.html', {'selected': 'notewell'})
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
def tool_instructions(request):
|
def tool_instructions(request):
|
||||||
return render_to_response('submit/tool_instructions.html', {'selected': 'instructions'},
|
return render(request, 'submit/tool_instructions.html', {'selected': 'instructions'})
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
def search_submission(request):
|
def search_submission(request):
|
||||||
error = None
|
error = None
|
||||||
|
@ -120,11 +116,10 @@ def search_submission(request):
|
||||||
if submission:
|
if submission:
|
||||||
return redirect(submission_status, submission_id=submission.pk)
|
return redirect(submission_status, submission_id=submission.pk)
|
||||||
error = 'No valid submission found for %s' % name
|
error = 'No valid submission found for %s' % name
|
||||||
return render_to_response('submit/search_submission.html',
|
return render(request, 'submit/search_submission.html',
|
||||||
{'selected': 'status',
|
{'selected': 'status',
|
||||||
'error': error,
|
'error': error,
|
||||||
'name': name},
|
'name': name})
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
def can_edit_submission(user, submission, access_token):
|
def can_edit_submission(user, submission, access_token):
|
||||||
key_matched = access_token and submission.access_token() == access_token
|
key_matched = access_token and submission.access_token() == access_token
|
||||||
|
@ -153,12 +148,7 @@ def submission_status(request, submission_id, access_token=None):
|
||||||
|
|
||||||
confirmation_list = submission_confirmation_email_list(submission)
|
confirmation_list = submission_confirmation_email_list(submission)
|
||||||
|
|
||||||
try:
|
requires_group_approval = (submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not Preapproval.objects.filter(name=submission.name).exists())
|
||||||
preapproval = Preapproval.objects.get(name=submission.name)
|
|
||||||
except Preapproval.DoesNotExist:
|
|
||||||
preapproval = None
|
|
||||||
|
|
||||||
requires_group_approval = submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not preapproval
|
|
||||||
|
|
||||||
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
|
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
|
||||||
|
|
||||||
|
@ -175,6 +165,7 @@ def submission_status(request, submission_id, access_token=None):
|
||||||
|
|
||||||
|
|
||||||
submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter")
|
submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter")
|
||||||
|
replaces_form = ReplacesForm(initial=DocAlias.objects.filter(name__in=submission.replaces.split(",")))
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
action = request.POST.get('action')
|
action = request.POST.get('action')
|
||||||
|
@ -183,8 +174,12 @@ def submission_status(request, submission_id, access_token=None):
|
||||||
return HttpResponseForbidden("You do not have permission to perfom this action")
|
return HttpResponseForbidden("You do not have permission to perfom this action")
|
||||||
|
|
||||||
submitter_form = NameEmailForm(request.POST, prefix="submitter")
|
submitter_form = NameEmailForm(request.POST, prefix="submitter")
|
||||||
if submitter_form.is_valid():
|
replaces_form = ReplacesForm(request.POST)
|
||||||
|
validations = [submitter_form.is_valid(), replaces_form.is_valid()]
|
||||||
|
if all(validations):
|
||||||
submission.submitter = submitter_form.cleaned_line()
|
submission.submitter = submitter_form.cleaned_line()
|
||||||
|
replaces = replaces_form.cleaned_data.get("replaces", [])
|
||||||
|
submission.replaces = ",".join(o.name for o in replaces)
|
||||||
|
|
||||||
if requires_group_approval:
|
if requires_group_approval:
|
||||||
submission.state = DraftSubmissionStateName.objects.get(slug="grp-appr")
|
submission.state = DraftSubmissionStateName.objects.get(slug="grp-appr")
|
||||||
|
@ -209,7 +204,11 @@ def submission_status(request, submission_id, access_token=None):
|
||||||
else:
|
else:
|
||||||
desc = u"sent confirmation email to submitter and authors: %s" % u", ".join(sent_to)
|
desc = u"sent confirmation email to submitter and authors: %s" % u", ".join(sent_to)
|
||||||
|
|
||||||
create_submission_event(request, submission, u"Set submitter to \"%s\" and %s" % (submission.submitter, desc))
|
msg = u"Set submitter to \"%s\", replaces to %s and %s" % (
|
||||||
|
submission.submitter,
|
||||||
|
", ".join(prettify_std_name(r.name) for r in replaces) if replaces else "(none)",
|
||||||
|
desc)
|
||||||
|
create_submission_event(request, submission, msg)
|
||||||
|
|
||||||
if access_token:
|
if access_token:
|
||||||
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_token=access_token)
|
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_token=access_token)
|
||||||
|
@ -271,23 +270,23 @@ def submission_status(request, submission_id, access_token=None):
|
||||||
# something went wrong, turn this into a GET and let the user deal with it
|
# something went wrong, turn this into a GET and let the user deal with it
|
||||||
return HttpResponseRedirect("")
|
return HttpResponseRedirect("")
|
||||||
|
|
||||||
return render_to_response('submit/submission_status.html',
|
return render(request, 'submit/submission_status.html', {
|
||||||
{'selected': 'status',
|
'selected': 'status',
|
||||||
'submission': submission,
|
'submission': submission,
|
||||||
'errors': errors,
|
'errors': errors,
|
||||||
'passes_idnits': passes_idnits,
|
'passes_idnits': passes_idnits,
|
||||||
'submitter_form': submitter_form,
|
'submitter_form': submitter_form,
|
||||||
'message': message,
|
'replaces_form': replaces_form,
|
||||||
'can_edit': can_edit,
|
'message': message,
|
||||||
'can_force_post': can_force_post,
|
'can_edit': can_edit,
|
||||||
'can_group_approve': can_group_approve,
|
'can_force_post': can_force_post,
|
||||||
'can_cancel': can_cancel,
|
'can_group_approve': can_group_approve,
|
||||||
'show_send_full_url': show_send_full_url,
|
'can_cancel': can_cancel,
|
||||||
'requires_group_approval': requires_group_approval,
|
'show_send_full_url': show_send_full_url,
|
||||||
'requires_prev_authors_approval': requires_prev_authors_approval,
|
'requires_group_approval': requires_group_approval,
|
||||||
'confirmation_list': confirmation_list,
|
'requires_prev_authors_approval': requires_prev_authors_approval,
|
||||||
},
|
'confirmation_list': confirmation_list,
|
||||||
context_instance=RequestContext(request))
|
})
|
||||||
|
|
||||||
|
|
||||||
def edit_submission(request, submission_id, access_token=None):
|
def edit_submission(request, submission_id, access_token=None):
|
||||||
|
@ -312,14 +311,17 @@ def edit_submission(request, submission_id, access_token=None):
|
||||||
|
|
||||||
edit_form = EditSubmissionForm(request.POST, instance=submission, prefix="edit")
|
edit_form = EditSubmissionForm(request.POST, instance=submission, prefix="edit")
|
||||||
submitter_form = NameEmailForm(request.POST, prefix="submitter")
|
submitter_form = NameEmailForm(request.POST, prefix="submitter")
|
||||||
|
replaces_form = ReplacesForm(request.POST)
|
||||||
author_forms = [ NameEmailForm(request.POST, email_required=False, prefix=prefix)
|
author_forms = [ NameEmailForm(request.POST, email_required=False, prefix=prefix)
|
||||||
for prefix in request.POST.getlist("authors-prefix")
|
for prefix in request.POST.getlist("authors-prefix")
|
||||||
if prefix != "authors-" ]
|
if prefix != "authors-" ]
|
||||||
|
|
||||||
# trigger validation of all forms
|
# trigger validation of all forms
|
||||||
validations = [edit_form.is_valid(), submitter_form.is_valid()] + [ f.is_valid() for f in author_forms ]
|
validations = [edit_form.is_valid(), submitter_form.is_valid(), replaces_form.is_valid()] + [ f.is_valid() for f in author_forms ]
|
||||||
if all(validations):
|
if all(validations):
|
||||||
submission.submitter = submitter_form.cleaned_line()
|
submission.submitter = submitter_form.cleaned_line()
|
||||||
|
replaces = replaces_form.cleaned_data.get("replaces", [])
|
||||||
|
submission.replaces = ",".join(o.name for o in replaces)
|
||||||
submission.authors = "\n".join(f.cleaned_line() for f in author_forms)
|
submission.authors = "\n".join(f.cleaned_line() for f in author_forms)
|
||||||
edit_form.save(commit=False) # transfer changes
|
edit_form.save(commit=False) # transfer changes
|
||||||
|
|
||||||
|
@ -350,20 +352,21 @@ def edit_submission(request, submission_id, access_token=None):
|
||||||
else:
|
else:
|
||||||
edit_form = EditSubmissionForm(instance=submission, prefix="edit")
|
edit_form = EditSubmissionForm(instance=submission, prefix="edit")
|
||||||
submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter")
|
submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter")
|
||||||
|
replaces_form = ReplacesForm(initial=DocAlias.objects.filter(name__in=submission.replaces.split(",")))
|
||||||
author_forms = [ NameEmailForm(initial=author, email_required=False, prefix="authors-%s" % i)
|
author_forms = [ NameEmailForm(initial=author, email_required=False, prefix="authors-%s" % i)
|
||||||
for i, author in enumerate(submission.authors_parsed()) ]
|
for i, author in enumerate(submission.authors_parsed()) ]
|
||||||
|
|
||||||
return render_to_response('submit/edit_submission.html',
|
return render(request, 'submit/edit_submission.html',
|
||||||
{'selected': 'status',
|
{'selected': 'status',
|
||||||
'submission': submission,
|
'submission': submission,
|
||||||
'edit_form': edit_form,
|
'edit_form': edit_form,
|
||||||
'submitter_form': submitter_form,
|
'submitter_form': submitter_form,
|
||||||
|
'replaces_form': replaces_form,
|
||||||
'author_forms': author_forms,
|
'author_forms': author_forms,
|
||||||
'empty_author_form': empty_author_form,
|
'empty_author_form': empty_author_form,
|
||||||
'errors': errors,
|
'errors': errors,
|
||||||
'form_errors': form_errors,
|
'form_errors': form_errors,
|
||||||
},
|
})
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
|
|
||||||
def confirm_submission(request, submission_id, auth_token):
|
def confirm_submission(request, submission_id, auth_token):
|
||||||
|
@ -379,10 +382,10 @@ def confirm_submission(request, submission_id, auth_token):
|
||||||
|
|
||||||
return redirect("doc_view", name=submission.name)
|
return redirect("doc_view", name=submission.name)
|
||||||
|
|
||||||
return render_to_response('submit/confirm_submission.html', {
|
return render(request, 'submit/confirm_submission.html', {
|
||||||
'submission': submission,
|
'submission': submission,
|
||||||
'key_matched': key_matched,
|
'key_matched': key_matched,
|
||||||
}, context_instance=RequestContext(request))
|
})
|
||||||
|
|
||||||
|
|
||||||
def approvals(request):
|
def approvals(request):
|
||||||
|
@ -392,13 +395,12 @@ def approvals(request):
|
||||||
days = 30
|
days = 30
|
||||||
recently_approved = recently_approved_by_user(request.user, datetime.date.today() - datetime.timedelta(days=days))
|
recently_approved = recently_approved_by_user(request.user, datetime.date.today() - datetime.timedelta(days=days))
|
||||||
|
|
||||||
return render_to_response('submit/approvals.html',
|
return render(request, 'submit/approvals.html',
|
||||||
{'selected': 'approvals',
|
{'selected': 'approvals',
|
||||||
'approvals': approvals,
|
'approvals': approvals,
|
||||||
'preapprovals': preapprovals,
|
'preapprovals': preapprovals,
|
||||||
'recently_approved': recently_approved,
|
'recently_approved': recently_approved,
|
||||||
'days': days },
|
'days': days })
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
|
|
||||||
@role_required("Secretariat", "WG Chair", "RG Chair")
|
@role_required("Secretariat", "WG Chair", "RG Chair")
|
||||||
|
@ -421,11 +423,10 @@ def add_preapproval(request):
|
||||||
else:
|
else:
|
||||||
form = PreapprovalForm()
|
form = PreapprovalForm()
|
||||||
|
|
||||||
return render_to_response('submit/add_preapproval.html',
|
return render(request, 'submit/add_preapproval.html',
|
||||||
{'selected': 'approvals',
|
{'selected': 'approvals',
|
||||||
'groups': groups,
|
'groups': groups,
|
||||||
'form': form },
|
'form': form })
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
||||||
@role_required("Secretariat", "WG Chair", "RG Chair")
|
@role_required("Secretariat", "WG Chair", "RG Chair")
|
||||||
def cancel_preapproval(request, preapproval_id):
|
def cancel_preapproval(request, preapproval_id):
|
||||||
|
@ -439,7 +440,6 @@ def cancel_preapproval(request, preapproval_id):
|
||||||
|
|
||||||
return HttpResponseRedirect(urlreverse("submit_approvals") + "#preapprovals")
|
return HttpResponseRedirect(urlreverse("submit_approvals") + "#preapprovals")
|
||||||
|
|
||||||
return render_to_response('submit/cancel_preapproval.html',
|
return render(request, 'submit/cancel_preapproval.html',
|
||||||
{'selected': 'approvals',
|
{'selected': 'approvals',
|
||||||
'preapproval': preapproval },
|
'preapproval': preapproval })
|
||||||
context_instance=RequestContext(request))
|
|
||||||
|
|
|
@ -100,7 +100,37 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if can_view_possibly_replaces %}
|
||||||
|
{% if possibly_replaces %}
|
||||||
|
<tr>
|
||||||
|
<th>Possibly Replaces</th>
|
||||||
|
<td class="edit">
|
||||||
|
{% if can_edit_replaces %}
|
||||||
|
<a class="btn btn-default btn-xs" href="{% url "doc_review_possibly_replaces" name=doc.name %}">Edit</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ possibly_replaces|join:", "|urlize_ietf_docs }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if possibly_replaced_by %}
|
||||||
|
<tr>
|
||||||
|
<th>Possibly Replaced By</th>
|
||||||
|
<td class="edit">
|
||||||
|
{% if can_edit_replaces %}
|
||||||
|
{% comment %}<a class="btn btn-default btn-xs" href="{% url "doc_review_possibly_replaces" name=doc.name %}">Edit</a>{% endcomment %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ possibly_replaced_by|join:", "|urlize_ietf_docs }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<tr class="plain">
|
<tr class="plain">
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Stream</th>
|
<th>Stream</th>
|
||||||
|
|
23
ietf/templates/doc/draft/review_possibly_replaces.html
Normal file
23
ietf/templates/doc/draft/review_possibly_replaces.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||||
|
{% load origin %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block title %}Review suggestions for documents that {{ doc }} replaces{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
<h1>Review suggestions for documents that {{ doc }} replaces</h1>
|
||||||
|
|
||||||
|
<form name="review-suggested-replaces" role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
|
||||||
|
{% buttons %}
|
||||||
|
<a class="btn btn-default pull-right" href="{{ doc.get_absolute_url }}">Cancel</a>
|
||||||
|
<button type="submit" value="Save" class="btn btn-primary">Save</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
12
ietf/templates/doc/mail/review_possibly_replaces_request.txt
Normal file
12
ietf/templates/doc/mail/review_possibly_replaces_request.txt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% autoescape off %}
|
||||||
|
{{ doc }} is suggested to replace:
|
||||||
|
|
||||||
|
{% for d in possibly_replaces %} {{ d.name }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
Please visit
|
||||||
|
|
||||||
|
{{ review_url }}
|
||||||
|
|
||||||
|
and either accept or decline the suggestion{{ suggested_replaces|pluralize:"s" }}.
|
||||||
|
{% endautoescape %}
|
|
@ -69,6 +69,7 @@
|
||||||
|
|
||||||
<h3>Submitter</h3>
|
<h3>Submitter</h3>
|
||||||
{% include "submit/submitter_form.html" %}
|
{% include "submit/submitter_form.html" %}
|
||||||
|
{% include "submit/replaces_form.html" %}
|
||||||
|
|
||||||
{% for form in author_forms %}
|
{% for form in author_forms %}
|
||||||
<div {% if forloop.last %}id="cloner"{% endif %}>
|
<div {% if forloop.last %}id="cloner"{% endif %}>
|
||||||
|
|
12
ietf/templates/submit/replaces_form.html
Normal file
12
ietf/templates/submit/replaces_form.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% for field in replaces_form %}
|
||||||
|
<tr{% if field.errors %} class="error"{% endif %}>
|
||||||
|
<th>{{ field.label_tag }}</th>
|
||||||
|
<td>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<div class="helptext">{{ field.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ field.errors }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
|
@ -6,6 +6,12 @@
|
||||||
|
|
||||||
{% block title %}Submission status of {{ submission.name }}-{{ submission.rev }}{% endblock %}
|
{% block title %}Submission status of {{ submission.name }}-{{ submission.rev }}{% endblock %}
|
||||||
|
|
||||||
|
{% block pagehead %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet" href="/css/lib/select2.css">
|
||||||
|
<link rel="stylesheet" href="/css/lib/select2-bootstrap.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block submit_content %}
|
{% block submit_content %}
|
||||||
{% origin %}
|
{% origin %}
|
||||||
{% if submission.state_id != "uploaded" %}
|
{% if submission.state_id != "uploaded" %}
|
||||||
|
@ -26,7 +32,7 @@
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if submitter_form.errors %}
|
{% if submitter_form.errors or replaces_form.errors %}
|
||||||
<p class="alert alert-danger">Please fix errors in the form below.</p>
|
<p class="alert alert-danger">Please fix errors in the form below.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -217,6 +223,7 @@
|
||||||
<form class="idsubmit" method="post">
|
<form class="idsubmit" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include "submit/submitter_form.html" %}
|
{% include "submit/submitter_form.html" %}
|
||||||
|
{% include "submit/replaces_form.html" %}
|
||||||
<input type="hidden" name="action" value="autopost">
|
<input type="hidden" name="action" value="autopost">
|
||||||
<button class="btn btn-primary" type="submit">Post submission</button>
|
<button class="btn btn-primary" type="submit">Post submission</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -242,6 +249,12 @@
|
||||||
<tr><th>Email address</th><td>{{ submission.submitter_parsed.email }}</td></tr>
|
<tr><th>Email address</th><td>{{ submission.submitter_parsed.email }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if submission.replaces %}
|
||||||
|
<h3>Replaced documents</h3>
|
||||||
|
<table class="table table-condensed table-striped">
|
||||||
|
<tr><th>Replaces</th><td>{{ submission.replaces|split:","|join:", "|urlize_ietf_docs }}</td></tr>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if can_cancel %}
|
{% if can_cancel %}
|
||||||
|
@ -313,3 +326,8 @@
|
||||||
{% include "submit/problem-reports-footer.html" %}
|
{% include "submit/problem-reports-footer.html" %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="/js/lib/select2-3.5.2.min.js"></script>
|
||||||
|
<script src="/js/select2-field.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -149,6 +149,7 @@ def make_test_data():
|
||||||
parent=area,
|
parent=area,
|
||||||
list_email="ames-wg@ietf.org",
|
list_email="ames-wg@ietf.org",
|
||||||
)
|
)
|
||||||
|
ames_wg = group
|
||||||
charter = Document.objects.create(
|
charter = Document.objects.create(
|
||||||
name="charter-ietf-" + group.acronym,
|
name="charter-ietf-" + group.acronym,
|
||||||
type_id="charter",
|
type_id="charter",
|
||||||
|
@ -174,10 +175,13 @@ def make_test_data():
|
||||||
# group personnel
|
# group personnel
|
||||||
create_person(mars_wg, "chair", name="WG Chair Man", username="marschairman")
|
create_person(mars_wg, "chair", name="WG Chair Man", username="marschairman")
|
||||||
create_person(mars_wg, "delegate", name="WG Delegate", username="marsdelegate")
|
create_person(mars_wg, "delegate", name="WG Delegate", username="marsdelegate")
|
||||||
|
|
||||||
mars_wg.role_set.get_or_create(name_id='ad',person=ad,email=ad.role_email('ad'))
|
mars_wg.role_set.get_or_create(name_id='ad',person=ad,email=ad.role_email('ad'))
|
||||||
mars_wg.save()
|
mars_wg.save()
|
||||||
|
|
||||||
|
create_person(ames_wg, "chair", name="WG Chair Man", username="ameschairman")
|
||||||
|
create_person(ames_wg, "delegate", name="WG Delegate", username="amesdelegate")
|
||||||
|
ames_wg.role_set.get_or_create(name_id='ad',person=ad,email=ad.role_email('ad'))
|
||||||
|
ames_wg.save()
|
||||||
|
|
||||||
# draft
|
# draft
|
||||||
draft = Document.objects.create(
|
draft = Document.objects.create(
|
||||||
|
|
Loading…
Reference in a new issue