Merged in [18371] from mark@painless-security.com:

Track slide submissions even after acceptance or rejection.  Fixes #2835.
 - Legacy-Id: 18421
Note: SVN reference [18371] has been migrated to Git commit 21ba67fb79
This commit is contained in:
Henrik Levkowetz 2020-08-26 16:47:35 +00:00
commit 2603bf4a23
11 changed files with 208 additions and 18 deletions

View file

@ -0,0 +1,22 @@
# 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', '0018_slidesubmissionstatusname'),
('doc', '0035_populate_docextresources'),
('meeting', '0030_allow_empty_joint_with_sessions'),
]
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'),
),
]

View file

@ -0,0 +1,20 @@
# 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 = [
('meeting', '0031_auto_20200803_1153'),
]
operations = [
migrations.AddField(
model_name='slidesubmission',
name='status',
field=ietf.utils.models.ForeignKey(null=True, default='pending', on_delete=django.db.models.deletion.SET_NULL, to='name.SlideSubmissionStatusName'),
),
]

View file

@ -29,7 +29,7 @@ from ietf.dbtemplate.models import DBTemplate
from ietf.doc.models import Document
from ietf.group.models import Group
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.utils.decorators import memoize
from ietf.utils.storage import NoLocationMigrationFileSystemStorage
@ -1277,6 +1277,8 @@ class SlideSubmission(models.Model):
filename = models.CharField(max_length=255)
apply_to_all = models.BooleanField(default=False)
submitter = ForeignKey(Person)
status = ForeignKey(SlideSubmissionStatusName, null=True, default='pending', on_delete=models.SET_NULL)
doc = ForeignKey(Document, null=True, on_delete=models.SET_NULL)
def staged_filepath(self):
return os.path.join(settings.SLIDE_STAGING_PATH , self.filename)

View file

@ -2788,6 +2788,7 @@ class MaterialsTests(TestCase):
def test_disapprove_proposed_slides(self):
submission = SlideSubmissionFactory()
submission.session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20))
self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(), 1)
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})
login_testing_unauthorized(self, chair.user.username, url)
@ -2795,7 +2796,11 @@ class MaterialsTests(TestCase):
self.assertEqual(r.status_code,200)
r = self.client.post(url,dict(title='some title',disapprove="disapprove"))
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):
submission = SlideSubmissionFactory()
@ -2804,13 +2809,22 @@ class MaterialsTests(TestCase):
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})
login_testing_unauthorized(self, chair.user.username, url)
self.assertEqual(submission.status_id, 'pending')
self.assertIsNone(submission.doc)
r = self.client.get(url)
self.assertEqual(r.status_code,200)
r = self.client.post(url,dict(title='different title',approve='approve'))
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.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):
submission = SlideSubmissionFactory(session__meeting__type_id='ietf')
@ -2881,7 +2895,7 @@ class MaterialsTests(TestCase):
self.assertEqual(r.status_code, 302)
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})
login_testing_unauthorized(self, chair.user.username, approve_url)
@ -2893,7 +2907,8 @@ class MaterialsTests(TestCase):
self.assertEqual(r.status_code,302)
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')
path = os.path.join(submission.session.meeting.get_materials_path(),'slides')
filename = os.path.join(path,session.sessionpresentation_set.first().document.name+'-01.txt')

View file

@ -25,7 +25,7 @@ from wsgiref.handlers import format_date_time
from django import forms
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, Http404
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@ -79,6 +79,7 @@ from ietf.meeting.utils import current_session_status
from ietf.meeting.utils import data_for_meetings_overview
from ietf.meeting.utils import preprocess_constraints_for_meeting_schedule_editor
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.proc_utils import (get_progress_stats, post_process, import_audio_files,
create_recording)
@ -1541,9 +1542,9 @@ def session_details(request, num, acronym):
pending_suggestions = None
if request.user.is_authenticated:
if can_manage:
pending_suggestions = session.slidesubmission_set.all()
pending_suggestions = session.slidesubmission_set.filter(status__slug='pending')
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",
{ 'scheduled_sessions':scheduled_sessions ,
@ -3146,13 +3147,16 @@ def approve_proposed_slides(request, slidesubmission_id, num):
name, _ = os.path.splitext(submission.filename)
name = name[:name.rfind('-ss')]
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)
if form.is_valid():
apply_to_all = submission.session.type_id == 'regular'
if show_apply_to_all_checkbox:
apply_to_all = form.cleaned_data['apply_to_all']
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']
if existing_doc:
doc = Document.objects.get(name=name)
@ -3192,15 +3196,29 @@ def approve_proposed_slides(request, slidesubmission_id, num):
os.rename(submission.staged_filepath(), os.path.join(path, target_filename))
post_process(doc)
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)
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
submission.delete()
submission.status = SlideSubmissionStatusName.objects.get(slug='rejected')
submission.save()
return redirect('ietf.meeting.views.session_details',num=num,acronym=acronym)
else:
pass
elif not submission.status.slug == 'pending':
return render(request, "meeting/previously_approved_slides.html",
{'submission': submission })
else:
initial = {
'title': submission.title,

View file

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

View file

@ -12077,6 +12077,36 @@
"model": "name.sessionstatusname",
"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": {
"desc": "",
@ -14905,7 +14935,7 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2020-08-12T00:12:54.984",
"time": "2020-07-23T00:12:27.508",
"used": true,
"version": "xym 0.4.8"
},
@ -14916,7 +14946,7 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2020-08-12T00:12:56.359",
"time": "2020-07-23T00:12:28.886",
"used": true,
"version": "pyang 2.3.2"
},
@ -14927,7 +14957,7 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2020-08-12T00:12:56.632",
"time": "2020-07-23T00:12:29.140",
"used": true,
"version": "yanglint SO 1.6.7"
},
@ -14938,7 +14968,7 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2020-08-12T00:12:58.366",
"time": "2020-07-23T00:12:30.892",
"used": true,
"version": "xml2rfc 2.47.0"
},

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', '0017_update_constraintname_order_and_label'),
]
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):
"""GitHub Repository URL, GitHub Username, ..."""
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,
ReviewAssignmentStateName, ReviewRequestStateName, ReviewResultName, ReviewTypeName,
RoleName, RoomResourceName, SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName,
TopicAudienceName, ReviewerQueuePolicyName, TimerangeName, ExtResourceTypeName, ExtResourceName)
TopicAudienceName, ReviewerQueuePolicyName, TimerangeName, ExtResourceTypeName, ExtResourceName,
SlideSubmissionStatusName)
class TimeSlotTypeNameResource(ModelResource):
class Meta:
@ -650,3 +651,20 @@ class ExtResourceNameResource(ModelResource):
"type": ALL_WITH_RELATIONS,
}
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())

View file

@ -0,0 +1,20 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2020, All Rights Reserved #}
{% load origin staticfiles bootstrap3 %}
{% block title %}Approved Slides for {{ submission.session.meeting }} : {{ submission.session.group.acronym }}{% endblock %}
{% block content %}
{% origin %}
<h1>These slides have already been {% if submission.status.slug == 'approved' %} approved {% else %} rejected {% endif %}</h1>
<p>The slides from {{ submission.submitter }} have already been {% if submission.status.slug == 'approved' %} approved {% else %} rejected {% endif %}. No further action is needed.</p>
<p>You may wish to
{% if submission.status.slug == 'approved' and submission.doc %}
<a href="{% url 'ietf.doc.views_doc.document_main' name=submission.doc.name %}">view the slides</a> or
{% endif %}
<a href="{% url "ietf.meeting.views.session_details" num=submission.session.meeting.number acronym=submission.session.group.acronym %}">return to this meeting session</a>.</p>
{% endblock %}