datatracker/ietf/submit/views.py
Ole Laursen ecf68dbb05 Revamp and clean up submit models:
- Rename IdSubmissionDetail to Submission
- Rename various submission fields to correspond to the conventions in
  the new schema
- Use a name model for the states instead of IdSubmissionStatus
- Drop the TempIdAuthor model which is based on splitting up author
  names
- Add a simple textual SubmissionEvent for tracking events in the
  lifetime of a submission
- Delete a bunch of obsolete fields
- Make sure all submission have an access key so we can depend on it
- Add state for when approval is needed from previous authors

A couple of migrations take care of transforming the
IdSubmissionDetail and moving data over/cleaning it up.

Also revamp the submit view code:

- Make form code do validation/cleaning only so there's a clear
  separation of concerns
- Reduce uses of inheritance that made the code hard to follow -
  forms now don't inherit from each other, views don't call each other
  but instead reuse common utilities, templates share CSS/utilities
  instead of relying on inheritance
- Move email rendering/sending to separate file
- Drop the in-grown terminology use (auto post vs. manual posts)
- Make the status page explain who is emailed for what purpose
- Add history table with recorded events
- Make the status page handle its post actions by itself instead of
  duplicating most of the setup logic in a number of simple views
- Fix a couple of minor bugs and handle some edge cases better
- Expand tests with a couple of more cases

Possibly the submit tool could still use more help text added to
explain the process, ideally what's explained in the tool instructions
page should be inlined or self-evident.
 - Legacy-Id: 6714
2013-11-15 13:30:32 +00:00

437 lines
20 KiB
Python

# Copyright The IETF Trust 2007, All Rights Reserved
import datetime
import os
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from django.core.validators import validate_email, ValidationError
from django.contrib.sites.models import Site
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden, HttpResponseNotAllowed
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from ietf.doc.models import Document
from ietf.group.models import Group, Role
from ietf.utils.mail import send_mail
from ietf.ietfauth.utils import has_role, role_required
from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName
from ietf.submit.forms import UploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm
from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user
from ietf.submit.utils import check_idnits, found_idnits, validate_submission, create_submission_event
from ietf.submit.utils import post_submission, cancel_submission, rename_submission_files
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.utils.uniquekey import generate_unique_key
def upload_submission(request):
if request.method == 'POST':
try:
form = UploadForm(request, data=request.POST, files=request.FILES)
if form.is_valid():
# save files
file_types = []
for ext in ['txt', 'pdf', 'xml', 'ps']:
f = form.cleaned_data[ext]
if not f:
continue
file_types.append('.%s' % ext)
draft = form.parsed_draft
name = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (draft.filename, draft.revision, ext))
with open(name, 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
# check idnits
text_path = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (draft.filename, draft.revision))
idnits_message = check_idnits(text_path)
# extract author lines
authors = []
for author in draft.get_author_list():
full_name, first_name, middle_initial, last_name, name_suffix, email, company = author
line = full_name.replace("\n", "").replace("\r", "").replace("<", "").replace(">", "").strip()
email = (email or "").strip()
if email:
try:
validate_email(email)
except ValidationError:
email = ""
if email:
line += u" <%s>" % email
authors.append(line)
# save submission
submission = Submission.objects.create(
state=DraftSubmissionStateName.objects.get(slug="uploaded"),
remote_ip=form.remote_ip,
name=draft.filename,
group=form.group,
title=draft.get_title(),
abstract=draft.get_abstract(),
rev=draft.revision,
pages=draft.get_pagecount(),
authors="\n".join(authors),
note="",
first_two_pages=''.join(draft.pages[:2]),
file_size=form.cleaned_data['txt'].size,
file_types=','.join(file_types),
submission_date=datetime.date.today(),
document_date=draft.get_creation_date(),
replaces="",
idnits_message=idnits_message,
)
create_submission_event(request, submission, desc="Uploaded submission")
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_key=submission.access_key)
except IOError as e:
if "read error" in str(e): # The server got an IOError when trying to read POST data
form = UploadForm(request=request)
form._errors = {}
form._errors["__all__"] = form.error_class(["There was a failure receiving the complete form data -- please try again."])
else:
raise
else:
form = UploadForm(request=request)
return render_to_response('submit/upload_submission.html',
{'selected': 'index',
'form': form},
context_instance=RequestContext(request))
def note_well(request):
return render_to_response('submit/note_well.html', {'selected': 'notewell'},
context_instance=RequestContext(request))
def tool_instructions(request):
return render_to_response('submit/tool_instructions.html', {'selected': 'instructions'},
context_instance=RequestContext(request))
def search_submission(request):
error = None
name = None
if request.method == 'POST':
name = request.POST.get('name', '')
submission = Submission.objects.filter(name=name).order_by('-pk')
if submission:
return HttpResponseRedirect(urlreverse(submission_status, None, kwargs={'submission_id': submission[0].pk}))
error = 'No valid submission found for %s' % name
return render_to_response('submit/search_submission.html',
{'selected': 'status',
'error': error,
'name': name},
context_instance=RequestContext(request))
def can_edit_submission(request, submission, access_key):
key_matched = access_key and submission.access_key == access_key
return key_matched or has_role(request.user, "Secretariat")
def submission_status(request, submission_id, access_key=None, message=None):
submission = get_object_or_404(Submission, pk=submission_id)
if access_key and submission.access_key != access_key:
raise Http404
errors = validate_submission(submission)
passes_idnits = found_idnits(submission.idnits_message)
key_matched = access_key and submission.access_key == access_key
is_secretariat = has_role(request.user, "Secretariat")
is_chair = submission.group and submission.group.has_role(request.user, "chair")
can_edit = can_edit_submission(request, submission, access_key) and submission.state_id == "uploaded"
can_cancel = (key_matched or is_secretariat) and submission.state.next_states.filter(slug="cancel")
can_group_approve = (is_secretariat or is_chair) and submission.state_id == "grp-appr"
can_force_post = is_secretariat and submission.state.next_states.filter(slug="posted")
show_send_full_url = not key_matched and not is_secretariat and submission.state_id not in ("cancel", "posted")
confirmation_list = submission_confirmation_email_list(submission)
try:
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") and not preapproval
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
if submission.state_id == "cancel":
message = ('error', 'This submission has been cancelled, modification is no longer possible.')
elif submission.state_id == "auth":
message = ('success', u'The submission is pending email authentication. An email has been sent to: %s' % ",".join(confirmation_list))
elif submission.state_id == "grp-appr":
message = ('success', 'The submission is pending approval by the group chairs.')
elif submission.state_id == "aut-appr":
message = ('success', 'The submission is pending approval by the authors of the previous version. An email has been sent to: %s' % ",".join(confirmation_list))
submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter")
if request.method == 'POST':
action = request.POST.get('action')
if action == "autopost" and submission.state_id == "uploaded":
if not can_edit:
return HttpResponseForbidden("You do not have permission to perfom this action")
submitter_form = NameEmailForm(request.POST, prefix="submitter")
if submitter_form.is_valid():
submission.submitter = submitter_form.cleaned_line()
if requires_group_approval:
submission.state = DraftSubmissionStateName.objects.get(slug="grp-appr")
submission.save()
sent_to = send_approval_request_to_group(request, submission)
desc = "sent approval email to group chairs: %s" % u", ".join(sent_to)
else:
submission.auth_key = generate_unique_key()
if requires_prev_authors_approval:
submission.state = DraftSubmissionStateName.objects.get(slug="aut-appr")
else:
submission.state = DraftSubmissionStateName.objects.get(slug="auth")
submission.save()
sent_to = send_submission_confirmation(request, submission)
if submission.state_id == "aut-appr":
desc = u"sent confirmation email to previous authors: %s" % u", ".join(sent_to)
else:
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))
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_key=access_key)
elif action == "edit" and submission.state_id == "uploaded":
if access_key:
return redirect("submit_edit_submission_by_hash", submission_id=submission.pk, access_key=access_key)
else:
return redirect("submit_edit_submission", submission_id=submission.pk)
elif action == "sendfullurl" and submission.state_id not in ("cancel", "posted"):
sent_to = send_full_url(request, submission)
message = ('success', u'An email has been sent with the full access URL to: %s' % u",".join(confirmation_list))
create_submission_event(request, submission, u"Sent full access URL to: %s" % u", ".join(sent_to))
elif action == "cancel" and submission.state.next_states.filter(slug="cancel"):
if not can_cancel:
return HttpResponseForbidden('You do not have permission to perform this action')
cancel_submission(submission)
create_submission_event(request, submission, "Cancelled submission")
return redirect("submit_submission_status", submission_id=submission_id)
elif action == "approve" and submission.state_id == "grp-appr":
if not can_group_approve:
return HttpResponseForbidden('You do not have permission to perform this action')
post_submission(request, submission)
create_submission_event(request, submission, "Approved and posted submission")
return redirect("doc_view", name=submission.name)
elif action == "forcepost" and submission.state.next_states.filter(slug="posted"):
if not can_force_post:
return HttpResponseForbidden('You do not have permission to perform this action')
post_submission(request, submission)
if submission.state_id == "manual":
desc = "Posted submission manually"
else:
desc = "Forced post of submission"
create_submission_event(request, submission, desc)
return redirect("doc_view", name=submission.name)
else:
# something went wrong, turn this into a GET and let the user deal with it
return HttpResponseRedirect("")
return render_to_response('submit/submission_status.html',
{'selected': 'status',
'submission': submission,
'errors': errors,
'passes_idnits': passes_idnits,
'submitter_form': submitter_form,
'message': message,
'can_edit': can_edit,
'can_force_post': can_force_post,
'can_group_approve': can_group_approve,
'can_cancel': can_cancel,
'show_send_full_url': show_send_full_url,
'requires_group_approval': requires_group_approval,
'requires_prev_authors_approval': requires_prev_authors_approval,
'confirmation_list': confirmation_list,
},
context_instance=RequestContext(request))
def edit_submission(request, submission_id, access_key=None):
submission = get_object_or_404(Submission, pk=submission_id, state="uploaded")
if not can_edit_submission(request.user, submission, access_key):
return HttpResponseForbidden('You do not have permission to access this page')
errors = validate_submission(submission)
form_errors = False
# we split the form handling into multiple forms, one for the
# submission itself, one for the submitter, and a list of forms
# for the authors
empty_author_form = NameEmailForm(email_required=False)
if request.method == 'POST':
# get a backup submission now, the model form may change some
# fields during validation
prev_submission = Submission.objects.get(pk=submission.pk)
edit_form = EditSubmissionForm(request.POST, instance=submission, prefix="edit")
submitter_form = NameEmailForm(request.POST, prefix="submitter")
author_forms = [ NameEmailForm(request.POST, email_required=False, prefix=prefix)
for prefix in request.POST.getlist("authors-prefix")
if prefix != "authors-" ]
# trigger validation of all forms
validations = [edit_form.is_valid(), submitter_form.is_valid()] + [ f.is_valid() for f in author_forms ]
if all(validations):
submission.submitter = submitter_form.cleaned_line()
submission.authors = "\n".join(f.cleaned_line() for f in author_forms)
edit_form.save(commit=False) # transfer changes
if submission.rev != prev_submission.rev:
rename_submission_files(submission, prev_submission.rev, submission.rev)
submission.state = DraftSubmissionStateName.objects.get(slug="manual")
submission.save()
send_manual_post_request(request, submission, errors)
changed_fields = [
submission._meta.get_field(f).verbose_name
for f in list(edit_form.fields.keys()) + ["submitter", "authors"]
if getattr(submission, f) != getattr(prev_submission, f)
]
if changed_fields:
desc = u"Edited %s and sent request for manual post" % u", ".join(changed_fields)
else:
desc = "Sent request for manual post"
create_submission_event(request, submission, desc)
return redirect("submit_submission_status", submission_id=submission.pk)
else:
form_errors = True
else:
edit_form = EditSubmissionForm(instance=submission, prefix="edit")
submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter")
author_forms = [ NameEmailForm(initial=author, email_required=False, prefix="authors-%s" % i)
for i, author in enumerate(submission.authors_parsed()) ]
return render_to_response('submit/edit_submission.html',
{'selected': 'status',
'submission': submission,
'edit_form': edit_form,
'submitter_form': submitter_form,
'author_forms': author_forms,
'empty_author_form': empty_author_form,
'errors': errors,
'form_errors': form_errors,
},
context_instance=RequestContext(request))
def confirm_submission(request, submission_id, auth_key):
submission = get_object_or_404(Submission, pk=submission_id)
if request.method == 'POST' and submission.state_id in ("auth", "aut-appr") and auth_key == submission.auth_key:
post_submission(request, submission)
create_submission_event(request, submission, "Confirmed and posted submission")
return redirect("doc_view", name=submission.name)
return render_to_response('submit/confirm_submission.html', {
'submission': submission,
'auth_key': auth_key,
}, context_instance=RequestContext(request))
def approvals(request):
approvals = approvable_submissions_for_user(request.user)
preapprovals = preapprovals_for_user(request.user)
days = 30
recently_approved = recently_approved_by_user(request.user, datetime.date.today() - datetime.timedelta(days=days))
return render_to_response('submit/approvals.html',
{'selected': 'approvals',
'approvals': approvals,
'preapprovals': preapprovals,
'recently_approved': recently_approved,
'days': days },
context_instance=RequestContext(request))
@role_required("Secretariat", "WG Chair")
def add_preapproval(request):
groups = Group.objects.filter(type="wg").exclude(state="conclude").order_by("acronym").distinct()
if not has_role(request.user, "Secretariat"):
groups = groups.filter(role__person__user=request.user)
if request.method == "POST":
form = PreapprovalForm(request.POST)
form.groups = groups
if form.is_valid():
p = Preapproval()
p.name = form.cleaned_data["name"]
p.by = request.user.get_profile()
p.save()
return HttpResponseRedirect(urlreverse("submit_approvals") + "#preapprovals")
else:
form = PreapprovalForm()
return render_to_response('submit/add_preapproval.html',
{'selected': 'approvals',
'groups': groups,
'form': form },
context_instance=RequestContext(request))
@role_required("Secretariat", "WG Chair")
def cancel_preapproval(request, preapproval_id):
preapproval = get_object_or_404(Preapproval, pk=preapproval_id)
if preapproval not in preapprovals_for_user(request.user):
raise HttpResponseForbidden("You do not have permission to cancel this preapproval.")
if request.method == "POST" and request.POST.get("action", "") == "cancel":
preapproval.delete()
return HttpResponseRedirect(urlreverse("submit_approvals") + "#preapprovals")
return render_to_response('submit/cancel_preapproval.html',
{'selected': 'approvals',
'preapproval': preapproval },
context_instance=RequestContext(request))