Skip to content

Commit 188c084

Browse files
committed
Add approval/pre-approval page for Secretariat and WG Chairs, with
subpages for adding and canceling pre-approvals and a set of migrations for replacing the old IdApprovedDetail with a slightly less confusing Preapproval model. - Legacy-Id: 4411
1 parent 37172f3 commit 188c084

15 files changed

Lines changed: 1645 additions & 52 deletions

ietf/submit/admin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ def status_link(self, instance):
2121

2222
admin.site.register(IdSubmissionDetail, IdSubmissionDetailAdmin)
2323

24-
class IdApprovedDetailAdmin(admin.ModelAdmin):
24+
class PreapprovalAdmin(admin.ModelAdmin):
2525
pass
26-
admin.site.register(IdApprovedDetail, IdApprovedDetailAdmin)
26+
admin.site.register(Preapproval, PreapprovalAdmin)
2727

2828
class TempIdAuthorsAdmin(admin.ModelAdmin):
2929
ordering = ["-id"]

ietf/submit/forms.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
from ietf.idtracker.models import InternetDraft, IETFWG
1616
from ietf.proceedings.models import Meeting
17-
from ietf.submit.models import IdSubmissionDetail, TempIdAuthors
18-
from ietf.submit.utils import MANUAL_POST_REQUESTED, NONE_WG, UPLOADED, WAITING_AUTHENTICATION
17+
from ietf.submit.models import IdSubmissionDetail, TempIdAuthors, Preapproval
18+
from ietf.submit.utils import MANUAL_POST_REQUESTED, NONE_WG, UPLOADED, WAITING_AUTHENTICATION, POSTED, POSTED_BY_SECRETARIAT
1919
from ietf.submit.parsers.pdf_parser import PDFParser
2020
from ietf.submit.parsers.plain_parser import PlainParser
2121
from ietf.submit.parsers.ps_parser import PSParser
@@ -547,3 +547,33 @@ def send_mail_to_secretariat(self, request):
547547
'submitter': submitter
548548
},
549549
cc=cc)
550+
551+
552+
class PreapprovalForm(forms.Form):
553+
name = forms.CharField(max_length=255, required=True, label="Pre-approved name", initial="draft-ietf-")
554+
555+
def clean_name(self):
556+
n = self.cleaned_data['name'].strip().lower()
557+
558+
if not n.startswith("draft-"):
559+
raise forms.ValidationError("Name doesn't start with \"draft-\".")
560+
if len(n.split(".")) > 1 and len(n.split(".")[-1]) == 3:
561+
raise forms.ValidationError("Name appears to end with a file extension .%s - do not include an extension." % n.split(".")[-1])
562+
563+
components = n.split("-")
564+
if components[-1] == "00":
565+
raise forms.ValidationError("Name appears to end with a revision number -00 - do not include the revision.")
566+
if len(components) < 4:
567+
raise forms.ValidationError("Name has less than four dash-delimited components - can't form a valid WG draft name.")
568+
if not components[-1]:
569+
raise forms.ValidationError("Name ends with a dash.")
570+
acronym = components[2]
571+
if acronym not in self.groups.values_list('acronym', flat=True):
572+
raise forms.ValidationError("WG acronym not recognized as one you can approve drafts for.")
573+
574+
if Preapproval.objects.filter(name=n):
575+
raise forms.ValidationError("Pre-approval for this name already exists.")
576+
if IdSubmissionDetail.objects.filter(status__in=[POSTED, POSTED_BY_SECRETARIAT ], filename=n):
577+
raise forms.ValidationError("A draft with this name has already been submitted and accepted. A pre-approval would not make any difference.")
578+
579+
return n

ietf/submit/migrations/0001_initial.py

Lines changed: 361 additions & 0 deletions
Large diffs are not rendered by default.

ietf/submit/migrations/0002_auto__add_preapproval.py

Lines changed: 299 additions & 0 deletions
Large diffs are not rendered by default.

ietf/submit/migrations/0003_copy_idapproveddetail_to_preapproval.py

Lines changed: 311 additions & 0 deletions
Large diffs are not rendered by default.

ietf/submit/migrations/0004_auto__del_idapproveddetail.py

Lines changed: 292 additions & 0 deletions
Large diffs are not rendered by default.

ietf/submit/models.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
1-
import re
1+
import re, datetime
22

33
from django.conf import settings
44
from django.db import models
55
from django.utils.hashcompat import md5_constructor
66

77
from ietf.idtracker.models import IETFWG
8+
from ietf.person.models import Person
89

910

1011
class IdSubmissionStatus(models.Model):
1112
status_id = models.IntegerField(primary_key=True)
1213
status_value = models.CharField(blank=True, max_length=255)
1314

14-
class Meta:
15-
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
16-
db_table = 'id_submission_status'
17-
1815
def __unicode__(self):
1916
return self.status_value
2017

@@ -50,9 +47,8 @@ class IdSubmissionDetail(models.Model):
5047
idnits_failed = models.IntegerField(null=True, blank=True)
5148
submission_hash = models.CharField(null=True, blank=True, max_length=255)
5249

53-
class Meta:
54-
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
55-
db_table = 'id_submission_detail'
50+
def __unicode__(self):
51+
return u"%s-%s" % (self.filename, self.revision)
5652

5753
def create_hash(self):
5854
self.submission_hash = md5_constructor(settings.SECRET_KEY + self.filename).hexdigest()
@@ -78,20 +74,14 @@ def create_submission_hash(sender, instance, **kwargs):
7874

7975
models.signals.pre_save.connect(create_submission_hash, sender=IdSubmissionDetail)
8076

81-
class IdApprovedDetail(models.Model):
82-
filename = models.CharField(null=True, blank=True, max_length=255, db_index=True)
83-
approved_status = models.IntegerField(null=True, blank=True)
84-
approved_person_tag = models.IntegerField(null=True, blank=True)
85-
approved_date = models.DateField(null=True, blank=True)
86-
recorded_by = models.IntegerField(null=True, blank=True)
87-
88-
class Meta:
89-
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
90-
db_table = 'id_approved_detail'
77+
class Preapproval(models.Model):
78+
"""Pre-approved draft submission name."""
79+
name = models.CharField(max_length=255, db_index=True)
80+
by = models.ForeignKey(Person)
81+
time = models.DateTimeField(default=datetime.datetime.now)
9182

9283
def __unicode__(self):
93-
return "%s (%s)" % (self.filename, self.approved_status)
94-
84+
return self.name
9585

9686
class TempIdAuthors(models.Model):
9787
id_document_tag = models.IntegerField()

ietf/submit/tests.py

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from ietf.person.models import Person, Email
1818
from ietf.group.models import Group, Role
1919
from ietf.doc.models import Document, BallotPositionDocEvent
20-
from ietf.submit.models import IdSubmissionDetail
20+
from ietf.submit.models import IdSubmissionDetail, Preapproval
2121

2222
class SubmitTestCase(django.test.TestCase):
2323
fixtures = ['names', 'idsubmissionstatus']
@@ -357,7 +357,79 @@ def test_request_full_url(self):
357357
self.assertTrue("Full URL for managing submission" in outbox[-1]["Subject"])
358358
self.assertTrue(name in outbox[-1]["Subject"])
359359

360+
class ApprovalsTestCase(django.test.TestCase):
361+
fixtures = ['names', 'idsubmissionstatus']
362+
363+
def test_approvals(self):
364+
make_test_data()
365+
366+
url = urlreverse('submit_approvals')
367+
self.client.login(remote_user="marschairman")
368+
369+
from ietf.submit.views import POSTED, INITIAL_VERSION_APPROVAL_REQUESTED
370+
371+
Preapproval.objects.create(name="draft-ietf-mars-foo", by=Person.objects.get(user__username="marschairman"))
372+
Preapproval.objects.create(name="draft-ietf-mars-baz", by=Person.objects.get(user__username="marschairman"))
373+
374+
IdSubmissionDetail.objects.create(filename="draft-ietf-mars-foo",
375+
group_acronym_id=Group.objects.get(acronym="mars").pk,
376+
revision="00",
377+
status_id=POSTED)
378+
IdSubmissionDetail.objects.create(filename="draft-ietf-mars-bar",
379+
group_acronym_id=Group.objects.get(acronym="mars").pk,
380+
revision="00",
381+
status_id=INITIAL_VERSION_APPROVAL_REQUESTED)
382+
383+
# get
384+
r = self.client.get(url)
385+
self.assertEquals(r.status_code, 200)
386+
q = PyQuery(r.content)
387+
388+
self.assertEquals(len(q('.approvals a:contains("draft-ietf-mars-foo")')), 0)
389+
self.assertEquals(len(q('.approvals a:contains("draft-ietf-mars-bar")')), 1)
390+
self.assertEquals(len(q('.preapprovals td:contains("draft-ietf-mars-foo")')), 0)
391+
self.assertEquals(len(q('.preapprovals td:contains("draft-ietf-mars-baz")')), 1)
392+
393+
def test_add_preapproval(self):
394+
make_test_data()
395+
396+
url = urlreverse('submit_add_preapproval')
397+
login_testing_unauthorized(self, "marschairman", url)
398+
399+
# get
400+
r = self.client.get(url)
401+
self.assertEquals(r.status_code, 200)
402+
q = PyQuery(r.content)
403+
self.assertEquals(len(q('input[type=submit]')), 1)
404+
405+
# faulty post
406+
r = self.client.post(url, dict(name="draft-test-nonexistingwg-something"))
407+
self.assertEquals(r.status_code, 200)
408+
self.assertTrue("errorlist" in r.content)
409+
410+
# add
411+
name = "draft-ietf-mars-foo"
412+
r = self.client.post(url, dict(name=name))
413+
self.assertEquals(r.status_code, 302)
414+
415+
self.assertEquals(len(Preapproval.objects.filter(name=name)), 1)
416+
417+
def test_cancel_preapproval(self):
418+
make_test_data()
419+
420+
preapproval = Preapproval.objects.create(name="draft-ietf-mars-foo", by=Person.objects.get(user__username="marschairman"))
421+
422+
url = urlreverse('submit_cancel_preapproval', kwargs=dict(preapproval_id=preapproval.pk))
423+
login_testing_unauthorized(self, "marschairman", url)
424+
425+
# get
426+
r = self.client.get(url)
427+
self.assertEquals(r.status_code, 200)
428+
q = PyQuery(r.content)
429+
self.assertEquals(len(q('input[type=submit]')), 1)
430+
431+
# cancel
432+
r = self.client.post(url, dict(action="cancel"))
433+
self.assertEquals(r.status_code, 302)
360434

361-
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
362-
# the above tests only work with the new schema
363-
del SubmitTestCase
435+
self.assertEquals(len(Preapproval.objects.filter(name=preapproval.name)), 0)

ietf/submit/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
url(r'^status/(?P<submission_id>\d+)/(?P<submission_hash>[a-f\d]+)/$', 'draft_status', name='draft_status_by_hash'),
1515
url(r'^status/(?P<submission_id>\d+)/(?P<submission_hash>[a-f\d]+)/cancel/$', 'draft_cancel', name='draft_cancel_by_hash'),
1616
url(r'^status/(?P<submission_id>\d+)/(?P<submission_hash>[a-f\d]+)/edit/$', 'draft_edit', name='draft_edit_by_hash'),
17+
url(r'^approvals/$', 'approvals', name='submit_approvals'),
18+
url(r'^approvals/addpreapproval/$', 'add_preapproval', name='submit_add_preapproval'),
19+
url(r'^approvals/cancelpreapproval/(?P<preapproval_id>[a-f\d]+)/$', 'cancel_preapproval', name='submit_cancel_preapproval'),
1720
)
1821

1922
urlpatterns += patterns('django.views.generic.simple',

ietf/submit/utils.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99

1010
from ietf.idtracker.models import (InternetDraft, PersonOrOrgInfo, IETFWG,
1111
IDAuthor, EmailAddress, IESGLogin, BallotInfo)
12-
from ietf.submit.models import TempIdAuthors
12+
from ietf.submit.models import TempIdAuthors, IdSubmissionDetail, Preapproval
1313
from ietf.utils.mail import send_mail, send_mail_message
1414
from ietf.utils import unaccent
15+
from ietf.ietfauth.decorators import has_role
1516

1617
from ietf.doc.models import *
1718
from ietf.person.models import Person, Alias, Email
@@ -457,6 +458,33 @@ def remove_docs(submission):
457458
if os.path.exists(source):
458459
os.unlink(source)
459460

461+
def get_approvable_submissions(user):
462+
if not user.is_authenticated():
463+
return []
464+
465+
res = IdSubmissionDetail.objects.filter(status=INITIAL_VERSION_APPROVAL_REQUESTED).order_by('-submission_date')
466+
if has_role(user, "Secretariat"):
467+
return res
468+
469+
# those we can reach as chairs
470+
return res.filter(group_acronym__role__name="chair", group_acronym__role__person__user=user)
471+
472+
def get_preapprovals(user):
473+
if not user.is_authenticated():
474+
return []
475+
476+
posted = IdSubmissionDetail.objects.distinct().filter(status__in=[POSTED, POSTED_BY_SECRETARIAT]).values_list('filename', flat=True)
477+
res = Preapproval.objects.exclude(name__in=posted).order_by("-time").select_related('by')
478+
if has_role(user, "Secretariat"):
479+
return res
480+
481+
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type="wg")]
482+
483+
res = res.filter(name__regex="draft-[^-]+-(%s)-.*" % "|".join(acronyms))
484+
485+
return res
486+
487+
460488

461489
class DraftValidation(object):
462490

0 commit comments

Comments
 (0)