Track slide submissions even after acceptance or rejection. Fixes #2835. Commit ready for merge.

- Legacy-Id: 18371
This commit is contained in:
Mark J. Donnelly 2020-08-13 13:48:01 +00:00
parent b6c3a8f1f8
commit 21ba67fb79
9 changed files with 176 additions and 20 deletions

View file

@ -0,0 +1,28 @@
# Generated by Django 2.2.14 on 2020-08-03 11:53
from django.db import migrations
import django.db.models.deletion
import ietf.utils.models
class Migration(migrations.Migration):
dependencies = [
('name', '0016_slidesubmissionstatusname'),
('doc', '0035_populate_docextresources'),
('meeting', '0030_allow_empty_joint_with_sessions'),
('name', '0016_slidesubmissionstatusname'),
]
operations = [
migrations.AddField(
model_name='slidesubmission',
name='doc',
field=ietf.utils.models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='doc.Document'),
),
migrations.AddField(
model_name='slidesubmission',
name='status',
field=ietf.utils.models.ForeignKey(default='pending', on_delete=django.db.models.deletion.PROTECT, to='name.SlideSubmissionStatusName'),
),
]

View file

@ -28,7 +28,7 @@ from ietf.dbtemplate.models import DBTemplate
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.group.utils import can_manage_materials from ietf.group.utils import can_manage_materials
from ietf.name.models import MeetingTypeName, TimeSlotTypeName, SessionStatusName, ConstraintName, RoomResourceName, ImportantDateName, TimerangeName from ietf.name.models import MeetingTypeName, TimeSlotTypeName, SessionStatusName, ConstraintName, RoomResourceName, ImportantDateName, TimerangeName, SlideSubmissionStatusName
from ietf.person.models import Person from ietf.person.models import Person
from ietf.utils.decorators import memoize from ietf.utils.decorators import memoize
from ietf.utils.storage import NoLocationMigrationFileSystemStorage from ietf.utils.storage import NoLocationMigrationFileSystemStorage
@ -1269,6 +1269,9 @@ class SlideSubmission(models.Model):
apply_to_all = models.BooleanField(default=False) apply_to_all = models.BooleanField(default=False)
submitter = ForeignKey(Person) submitter = ForeignKey(Person)
status = ForeignKey(SlideSubmissionStatusName, default='pending', on_delete=models.PROTECT)
doc = ForeignKey(Document, null=True, on_delete=models.SET_NULL)
def staged_filepath(self): def staged_filepath(self):
return os.path.join(settings.SLIDE_STAGING_PATH , self.filename) return os.path.join(settings.SLIDE_STAGING_PATH , self.filename)

View file

@ -2760,7 +2760,11 @@ class MaterialsTests(TestCase):
self.assertEqual(r.status_code,200) self.assertEqual(r.status_code,200)
r = self.client.post(url,dict(title='some title',disapprove="disapprove")) r = self.client.post(url,dict(title='some title',disapprove="disapprove"))
self.assertEqual(r.status_code,302) self.assertEqual(r.status_code,302)
self.assertEqual(SlideSubmission.objects.count(), 0) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'rejected').count(), 1)
self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(), 0)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, "These slides have already been rejected")
def test_approve_proposed_slides(self): def test_approve_proposed_slides(self):
submission = SlideSubmissionFactory() submission = SlideSubmissionFactory()
@ -2769,13 +2773,22 @@ class MaterialsTests(TestCase):
chair = RoleFactory(group=submission.session.group,name_id='chair').person chair = RoleFactory(group=submission.session.group,name_id='chair').person
url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number}) url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number})
login_testing_unauthorized(self, chair.user.username, url) login_testing_unauthorized(self, chair.user.username, url)
self.assertEqual(submission.status_id, 'pending')
self.assertIsNone(submission.doc)
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code,200) self.assertEqual(r.status_code,200)
r = self.client.post(url,dict(title='different title',approve='approve')) r = self.client.post(url,dict(title='different title',approve='approve'))
self.assertEqual(r.status_code,302) self.assertEqual(r.status_code,302)
self.assertEqual(SlideSubmission.objects.count(), 0) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(), 0)
self.assertEqual(SlideSubmission.objects.filter(status__slug = 'approved').count(), 1)
submission = SlideSubmission.objects.get(id = submission.id)
self.assertEqual(submission.status_id, 'approved')
self.assertIsNotNone(submission.doc)
self.assertEqual(session.sessionpresentation_set.count(),1) self.assertEqual(session.sessionpresentation_set.count(),1)
self.assertEqual(session.sessionpresentation_set.first().document.title,'different title') self.assertEqual(session.sessionpresentation_set.first().document.title,'different title')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, "These slides have already been approved")
def test_approve_proposed_slides_multisession_apply_one(self): def test_approve_proposed_slides_multisession_apply_one(self):
submission = SlideSubmissionFactory(session__meeting__type_id='ietf') submission = SlideSubmissionFactory(session__meeting__type_id='ietf')
@ -2846,7 +2859,7 @@ class MaterialsTests(TestCase):
self.assertEqual(r.status_code, 302) self.assertEqual(r.status_code, 302)
self.client.logout() self.client.logout()
(first_submission, second_submission) = SlideSubmission.objects.filter(session=session).order_by('id') (first_submission, second_submission) = SlideSubmission.objects.filter(session=session, status__slug = 'pending').order_by('id')
approve_url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':second_submission.pk,'num':second_submission.session.meeting.number}) approve_url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':second_submission.pk,'num':second_submission.session.meeting.number})
login_testing_unauthorized(self, chair.user.username, approve_url) login_testing_unauthorized(self, chair.user.username, approve_url)
@ -2858,7 +2871,8 @@ class MaterialsTests(TestCase):
self.assertEqual(r.status_code,302) self.assertEqual(r.status_code,302)
self.client.logout() self.client.logout()
self.assertEqual(SlideSubmission.objects.count(),0) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(),0)
self.assertEqual(SlideSubmission.objects.filter(status__slug = 'rejected').count(),1)
self.assertEqual(session.sessionpresentation_set.first().document.rev,'01') self.assertEqual(session.sessionpresentation_set.first().document.rev,'01')
path = os.path.join(submission.session.meeting.get_materials_path(),'slides') path = os.path.join(submission.session.meeting.get_materials_path(),'slides')
filename = os.path.join(path,session.sessionpresentation_set.first().document.name+'-01.txt') filename = os.path.join(path,session.sessionpresentation_set.first().document.name+'-01.txt')

View file

@ -27,7 +27,7 @@ import debug # pyflakes:ignore
from django import forms from django import forms
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseNotFound, Http404
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -79,6 +79,7 @@ from ietf.meeting.utils import session_requested_by
from ietf.meeting.utils import current_session_status from ietf.meeting.utils import current_session_status
from ietf.meeting.utils import data_for_meetings_overview from ietf.meeting.utils import data_for_meetings_overview
from ietf.message.utils import infer_message from ietf.message.utils import infer_message
from ietf.name.models import SlideSubmissionStatusName
from ietf.secr.proceedings.utils import handle_upload_file from ietf.secr.proceedings.utils import handle_upload_file
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files, from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
create_recording) create_recording)
@ -1615,9 +1616,9 @@ def session_details(request, num, acronym):
pending_suggestions = None pending_suggestions = None
if request.user.is_authenticated: if request.user.is_authenticated:
if can_manage: if can_manage:
pending_suggestions = session.slidesubmission_set.all() pending_suggestions = session.slidesubmission_set.filter(status__slug='pending')
else: else:
pending_suggestions = session.slidesubmission_set.filter(submitter=request.user.person) pending_suggestions = session.slidesubmission_set.filter(status__slug='pending', submitter=request.user.person)
return render(request, "meeting/session_details.html", return render(request, "meeting/session_details.html",
{ 'scheduled_sessions':scheduled_sessions , { 'scheduled_sessions':scheduled_sessions ,
@ -3213,13 +3214,16 @@ def approve_proposed_slides(request, slidesubmission_id, num):
name, _ = os.path.splitext(submission.filename) name, _ = os.path.splitext(submission.filename)
name = name[:name.rfind('-ss')] name = name[:name.rfind('-ss')]
existing_doc = Document.objects.filter(name=name).first() existing_doc = Document.objects.filter(name=name).first()
if request.method == 'POST': if request.method == 'POST' and submission.status.slug == 'pending':
form = ApproveSlidesForm(show_apply_to_all_checkbox, request.POST) form = ApproveSlidesForm(show_apply_to_all_checkbox, request.POST)
if form.is_valid(): if form.is_valid():
apply_to_all = submission.session.type_id == 'regular' apply_to_all = submission.session.type_id == 'regular'
if show_apply_to_all_checkbox: if show_apply_to_all_checkbox:
apply_to_all = form.cleaned_data['apply_to_all'] apply_to_all = form.cleaned_data['apply_to_all']
if request.POST.get('approve'): if request.POST.get('approve'):
# Ensure that we have a file to approve. The system gets cranky otherwise.
if submission.filename is None or submission.filename == '' or not os.path.isfile(submission.staged_filepath()):
return HttpResponseNotFound("The slides you attempted to approve could not be found. Please disapprove and delete them instead.")
title = form.cleaned_data['title'] title = form.cleaned_data['title']
if existing_doc: if existing_doc:
doc = Document.objects.get(name=name) doc = Document.objects.get(name=name)
@ -3259,15 +3263,29 @@ def approve_proposed_slides(request, slidesubmission_id, num):
os.rename(submission.staged_filepath(), os.path.join(path, target_filename)) os.rename(submission.staged_filepath(), os.path.join(path, target_filename))
post_process(doc) post_process(doc)
acronym = submission.session.group.acronym acronym = submission.session.group.acronym
submission.delete() submission.status = SlideSubmissionStatusName.objects.get(slug='approved')
submission.doc = doc
submission.save()
return redirect('ietf.meeting.views.session_details',num=num,acronym=acronym) return redirect('ietf.meeting.views.session_details',num=num,acronym=acronym)
elif request.POST.get('disapprove'): elif request.POST.get('disapprove'):
os.unlink(submission.staged_filepath()) # Errors in processing a submit request sometimes result
# in a SlideSubmission object without a file. Handle
# this case and keep processing the 'disapprove' even if
# the filename doesn't exist.
try:
if submission.filename != None and submission.filename != '':
os.unlink(submission.staged_filepath())
except (FileNotFoundError, IsADirectoryError):
pass
acronym = submission.session.group.acronym acronym = submission.session.group.acronym
submission.delete() submission.status = SlideSubmissionStatusName.objects.get(slug='rejected')
submission.save()
return redirect('ietf.meeting.views.session_details',num=num,acronym=acronym) return redirect('ietf.meeting.views.session_details',num=num,acronym=acronym)
else: else:
pass pass
elif not submission.status.slug == 'pending':
return render(request, "meeting/previously_approved_slides.html",
{'submission': submission })
else: else:
initial = { initial = {
'title': submission.title, 'title': submission.title,

View file

@ -11,7 +11,7 @@ from ietf.name.models import (
ReviewRequestStateName, ReviewResultName, ReviewTypeName, RoleName, RoomResourceName, ReviewRequestStateName, ReviewResultName, ReviewTypeName, RoleName, RoomResourceName,
SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, TopicAudienceName, SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, TopicAudienceName,
DocUrlTagName, ReviewAssignmentStateName, ReviewerQueuePolicyName, TimerangeName, DocUrlTagName, ReviewAssignmentStateName, ReviewerQueuePolicyName, TimerangeName,
ExtResourceName, ExtResourceTypeName, ) ExtResourceName, ExtResourceTypeName, SlideSubmissionStatusName)
from ietf.stats.models import CountryAlias from ietf.stats.models import CountryAlias
@ -89,3 +89,4 @@ admin.site.register(TimerangeName, NameAdmin)
admin.site.register(TopicAudienceName, NameAdmin) admin.site.register(TopicAudienceName, NameAdmin)
admin.site.register(DocUrlTagName, NameAdmin) admin.site.register(DocUrlTagName, NameAdmin)
admin.site.register(ExtResourceTypeName, NameAdmin) admin.site.register(ExtResourceTypeName, NameAdmin)
admin.site.register(SlideSubmissionStatusName, NameAdmin)

View file

@ -12015,6 +12015,36 @@
"model": "name.sessionstatusname", "model": "name.sessionstatusname",
"pk": "schedw" "pk": "schedw"
}, },
{
"fields": {
"desc": "Approved",
"name": "approved",
"order": 1,
"used": true
},
"model": "name.slidesubmissionstatusname",
"pk": "approved"
},
{
"fields": {
"desc": "Pending",
"name": "pending",
"order": 0,
"used": true
},
"model": "name.slidesubmissionstatusname",
"pk": "pending"
},
{
"fields": {
"desc": "Rejected",
"name": "rejected",
"order": 2,
"used": true
},
"model": "name.slidesubmissionstatusname",
"pk": "rejected"
},
{ {
"fields": { "fields": {
"desc": "", "desc": "",
@ -14843,7 +14873,7 @@
"fields": { "fields": {
"command": "xym", "command": "xym",
"switch": "--version", "switch": "--version",
"time": "2020-07-09T00:12:56.528", "time": "2020-07-23T00:12:27.508",
"used": true, "used": true,
"version": "xym 0.4.8" "version": "xym 0.4.8"
}, },
@ -14854,9 +14884,9 @@
"fields": { "fields": {
"command": "pyang", "command": "pyang",
"switch": "--version", "switch": "--version",
"time": "2020-07-09T00:12:58.135", "time": "2020-07-23T00:12:28.886",
"used": true, "used": true,
"version": "pyang 2.2.1" "version": "pyang 2.3.2"
}, },
"model": "utils.versioninfo", "model": "utils.versioninfo",
"pk": 2 "pk": 2
@ -14865,7 +14895,7 @@
"fields": { "fields": {
"command": "yanglint", "command": "yanglint",
"switch": "--version", "switch": "--version",
"time": "2020-07-09T00:12:58.398", "time": "2020-07-23T00:12:29.140",
"used": true, "used": true,
"version": "yanglint SO 1.6.7" "version": "yanglint SO 1.6.7"
}, },
@ -14876,9 +14906,9 @@
"fields": { "fields": {
"command": "xml2rfc", "command": "xml2rfc",
"switch": "--version", "switch": "--version",
"time": "2020-07-09T00:13:00.193", "time": "2020-07-23T00:12:30.892",
"used": true, "used": true,
"version": "xml2rfc 2.46.0" "version": "xml2rfc 2.47.0"
}, },
"model": "utils.versioninfo", "model": "utils.versioninfo",
"pk": 4 "pk": 4

View file

@ -0,0 +1,42 @@
# Generated by Django 2.2.14 on 2020-08-03 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('name', '0015_populate_extres'),
]
def forward(apps, schema_editor):
SlideSubmissionStatusName = apps.get_model('name', 'SlideSubmissionStatusName')
slide_submission_status_names = [
('pending', 'Pending'),
('approved', 'Approved'),
('rejected', 'Rejected'),
]
for order, (slug, desc) in enumerate(slide_submission_status_names):
SlideSubmissionStatusName.objects.create(slug=slug, name=slug, desc=desc, used=True, order=order)
def reverse(apps, schema_editor):
pass
operations = [
migrations.CreateModel(
name='SlideSubmissionStatusName',
fields=[
('slug', models.CharField(max_length=32, primary_key=True, serialize=False)),
('name', models.CharField(max_length=255)),
('desc', models.TextField(blank=True)),
('used', models.BooleanField(default=True)),
('order', models.IntegerField(default=0)),
],
options={
'ordering': ['order', 'name'],
'abstract': False,
},
),
migrations.RunPython(forward, reverse),
]

View file

@ -130,3 +130,5 @@ class ExtResourceTypeName(NameModel):
class ExtResourceName(NameModel): class ExtResourceName(NameModel):
"""GitHub Repository URL, GitHub Username, ...""" """GitHub Repository URL, GitHub Username, ..."""
type = ForeignKey(ExtResourceTypeName) type = ForeignKey(ExtResourceTypeName)
class SlideSubmissionStatusName(NameModel):
"Pending, Accepted, Rejected"

View file

@ -17,7 +17,8 @@ from ietf.name.models import ( AgendaTypeName, BallotPositionName, ConstraintNam
LiaisonStatementState, LiaisonStatementTagName, MeetingTypeName, NomineePositionStateName, LiaisonStatementState, LiaisonStatementTagName, MeetingTypeName, NomineePositionStateName,
ReviewAssignmentStateName, ReviewRequestStateName, ReviewResultName, ReviewTypeName, ReviewAssignmentStateName, ReviewRequestStateName, ReviewResultName, ReviewTypeName,
RoleName, RoomResourceName, SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, RoleName, RoomResourceName, SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName,
TopicAudienceName, ReviewerQueuePolicyName, TimerangeName, ExtResourceTypeName, ExtResourceName) TopicAudienceName, ReviewerQueuePolicyName, TimerangeName, ExtResourceTypeName, ExtResourceName,
SlideSubmissionStatusName)
class TimeSlotTypeNameResource(ModelResource): class TimeSlotTypeNameResource(ModelResource):
class Meta: class Meta:
@ -650,3 +651,20 @@ class ExtResourceNameResource(ModelResource):
"type": ALL_WITH_RELATIONS, "type": ALL_WITH_RELATIONS,
} }
api.name.register(ExtResourceNameResource()) api.name.register(ExtResourceNameResource())
class SlideSubmissionStatusNameResource(ModelResource):
class Meta:
queryset = SlideSubmissionStatusName.objects.all()
serializer = api.Serializer()
cache = SimpleCache()
resource_name = 'slidesubmissionstatusname'
ordering = ['slug', ]
filtering = {
"slug": ALL,
"name": ALL,
"desc": ALL,
"used": ALL,
"order": ALL,
}
api.name.register(SlideSubmissionStatusNameResource())