1160 lines
38 KiB
Python
1160 lines
38 KiB
Python
import datetime
|
|
import glob
|
|
import os
|
|
import shutil
|
|
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.db.models import Max
|
|
from django.forms.formsets import formset_factory
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
|
|
from ietf.doc.models import Document, DocumentAuthor, DocAlias, DocRelationshipName, RelatedDocument, State
|
|
from ietf.doc.models import DocEvent, NewRevisionDocEvent
|
|
from ietf.doc.utils import add_state_change_event
|
|
from ietf.ietfauth.utils import role_required
|
|
from ietf.meeting.helpers import get_meeting
|
|
from ietf.name.models import StreamName
|
|
from ietf.person.models import Person
|
|
from ietf.secr.drafts.email import announcement_from_form, get_email_initial
|
|
from ietf.secr.drafts.forms import ( AddModelForm, AuthorForm, BaseRevisionModelForm, EditModelForm,
|
|
EmailForm, ExtendForm, ReplaceForm, RevisionModelForm, RfcModelForm,
|
|
RfcObsoletesForm, SearchForm, UploadForm, WithdrawForm )
|
|
from ietf.secr.utils.ams_utils import get_base
|
|
from ietf.secr.utils.decorators import clear_non_auth
|
|
from ietf.secr.utils.document import get_rfc_num, get_start_date
|
|
from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName, SubmissionEvent
|
|
from ietf.utils.draft import Draft
|
|
|
|
|
|
# -------------------------------------------------
|
|
# Helper Functions
|
|
# -------------------------------------------------
|
|
|
|
def archive_draft_files(filename):
|
|
'''
|
|
Takes a string representing the old draft filename, without extensions.
|
|
Moves any matching files to archive directory.
|
|
'''
|
|
files = glob.glob(os.path.join(settings.INTERNET_DRAFT_PATH,filename) + '.*')
|
|
for file in files:
|
|
shutil.move(file,settings.INTERNET_DRAFT_ARCHIVE_DIR)
|
|
return
|
|
|
|
def get_action_details(draft, session):
|
|
'''
|
|
This function takes a draft object and session object and returns a list of dictionaries
|
|
with keys: label, value to be used in displaying information on the confirmation
|
|
page.
|
|
'''
|
|
result = []
|
|
if session['action'] == 'revision':
|
|
m = {'label':'New Revision','value':session['revision']}
|
|
result.append(m)
|
|
|
|
if session['action'] == 'replace':
|
|
m = {'label':'Replaced By:','value':session['data']['replaced_by']}
|
|
result.append(m)
|
|
|
|
return result
|
|
|
|
def handle_uploaded_file(f):
|
|
'''
|
|
Save uploaded draft files to temporary directory
|
|
'''
|
|
destination = open(os.path.join(settings.IDSUBMIT_MANUAL_STAGING_DIR, f.name), 'wb+')
|
|
for chunk in f.chunks():
|
|
destination.write(chunk)
|
|
destination.close()
|
|
|
|
def handle_substate(doc):
|
|
'''
|
|
This function checks to see if the document has a revision needed tag, if so the
|
|
tag gets changed to ad followup, and a DocEvent is created.
|
|
'''
|
|
qs = doc.tags.filter(slug__in=('need-rev','rev-wglc','rev-ad','rev-iesg'))
|
|
if qs:
|
|
for tag in qs:
|
|
doc.tags.remove(tag)
|
|
doc.tags.add('ad-f-up')
|
|
|
|
# add DocEvent
|
|
system = Person.objects.get(name="(system)")
|
|
DocEvent.objects.create(type="changed_document",
|
|
doc=doc,
|
|
desc="Sub state has been changed to <b>AD Followup</b> from <b>Revised ID Needed</b>",
|
|
by=system)
|
|
|
|
def process_files(request,draft):
|
|
'''
|
|
This function takes a request object and draft object.
|
|
It obtains the list of file objects (ie from request.FILES), uploads
|
|
the files by calling handle_file_upload() and returns
|
|
the basename, revision number and a list of file types. Basename and revision
|
|
are assumed to be the same for all because this is part of the validation process.
|
|
|
|
It also creates the Submission record WITHOUT saving, and places it in the
|
|
session for saving in the final action step.
|
|
'''
|
|
files = request.FILES
|
|
file = files[files.keys()[0]]
|
|
filename = os.path.splitext(file.name)[0]
|
|
revision = os.path.splitext(file.name)[0][-2:]
|
|
basename = get_base(filename)
|
|
file_type_list = []
|
|
for file in files.values():
|
|
extension = os.path.splitext(file.name)[1]
|
|
file_type_list.append(extension)
|
|
if extension == '.txt':
|
|
txt_size = file.size
|
|
wrapper = Draft(file.read(),file.name)
|
|
handle_uploaded_file(file)
|
|
|
|
# create Submission record, leaved unsaved
|
|
idsub = Submission(
|
|
name=basename,
|
|
title=draft.title,
|
|
rev=revision,
|
|
pages=draft.pages,
|
|
file_size=txt_size,
|
|
document_date=wrapper.get_creation_date(),
|
|
submission_date=datetime.date.today(),
|
|
group_id=draft.group.id,
|
|
remote_ip=request.META['REMOTE_ADDR'],
|
|
first_two_pages=''.join(wrapper.pages[:2]),
|
|
state=DraftSubmissionStateName.objects.get(slug="posted"),
|
|
abstract=draft.abstract,
|
|
file_types=','.join(file_type_list),
|
|
)
|
|
request.session['idsub'] = idsub
|
|
|
|
return (filename,revision,file_type_list)
|
|
|
|
def post_submission(request):
|
|
submission = request.session['idsub']
|
|
submission.save()
|
|
|
|
SubmissionEvent.objects.create(
|
|
submission=submission,
|
|
by=request.user.person,
|
|
desc="Submitted and posted manually")
|
|
|
|
|
|
def promote_files(draft, types):
|
|
'''
|
|
This function takes one argument, a draft object. It then moves the draft files from
|
|
the temporary upload directory to the production directory.
|
|
'''
|
|
filename = '%s-%s' % (draft.name,draft.rev)
|
|
for ext in types:
|
|
path = os.path.join(settings.IDSUBMIT_MANUAL_STAGING_DIR, filename + ext)
|
|
shutil.move(path,settings.INTERNET_DRAFT_PATH)
|
|
|
|
# -------------------------------------------------
|
|
# Action Button Functions
|
|
# -------------------------------------------------
|
|
'''
|
|
These functions handle the real work of the action buttons: database updates,
|
|
moving files, etc. Generally speaking the action buttons trigger a multi-page
|
|
sequence where information may be gathered using a custom form, an email
|
|
may be produced and presented to the user to edit, and only then when confirmation
|
|
is given will the action work take place. That's when these functions are called.
|
|
The details of the action are stored in request.session.
|
|
'''
|
|
|
|
def do_extend(draft, request):
|
|
'''
|
|
Actions:
|
|
- update revision_date
|
|
- set extension_date
|
|
'''
|
|
|
|
e = DocEvent.objects.create(
|
|
type='changed_document',
|
|
by=request.user.person,
|
|
doc=draft,
|
|
time=draft.time,
|
|
desc='Extended expiry',
|
|
)
|
|
draft.expires = request.session['data']['expiration_date']
|
|
draft.save_with_history([e])
|
|
|
|
# save scheduled announcement
|
|
announcement_from_form(request.session['email'],by=request.user.person)
|
|
|
|
return
|
|
|
|
def do_replace(draft, request):
|
|
'Perform document replace'
|
|
|
|
replaced = request.session['data']['replaced'] # a DocAlias
|
|
replaced_by = request.session['data']['replaced_by'] # a Document
|
|
|
|
# create relationship
|
|
RelatedDocument.objects.create(source=replaced_by,
|
|
target=replaced,
|
|
relationship=DocRelationshipName.objects.get(slug='replaces'))
|
|
|
|
|
|
|
|
draft.set_state(State.objects.get(type="draft", slug="repl"))
|
|
|
|
e = DocEvent.objects.create(
|
|
type='changed_document',
|
|
by=request.user.person,
|
|
doc=replaced_by,
|
|
time=draft.time,
|
|
desc='This document now replaces <b>%s</b>' % request.session['data']['replaced'],
|
|
)
|
|
|
|
draft.save_with_history([e])
|
|
|
|
# move replaced document to archive
|
|
archive_draft_files(replaced.document.name + '-' + replaced.document.rev)
|
|
|
|
# send announcement
|
|
announcement_from_form(request.session['email'],by=request.user.person)
|
|
|
|
return
|
|
|
|
def do_resurrect(draft, request):
|
|
'''
|
|
Actions
|
|
- restore last archived version
|
|
- change state to Active
|
|
- reset expires
|
|
- create DocEvent
|
|
'''
|
|
# restore latest revision documents file from archive
|
|
files = glob.glob(os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR,draft.name) + '-??.*')
|
|
sorted_files = sorted(files)
|
|
latest,ext = os.path.splitext(sorted_files[-1])
|
|
files = glob.glob(os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR,latest) + '.*')
|
|
for file in files:
|
|
shutil.move(file,settings.INTERNET_DRAFT_PATH)
|
|
|
|
# Update draft record
|
|
draft.set_state(State.objects.get(type="draft", slug="active"))
|
|
|
|
# set expires
|
|
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
|
|
|
|
# create DocEvent
|
|
e = NewRevisionDocEvent.objects.create(type='completed_resurrect',
|
|
by=request.user.person,
|
|
doc=draft,
|
|
rev=draft.rev,
|
|
time=draft.time)
|
|
|
|
draft.save_with_history([e])
|
|
|
|
# send announcement
|
|
announcement_from_form(request.session['email'],by=request.user.person)
|
|
|
|
return
|
|
|
|
def do_revision(draft, request):
|
|
'''
|
|
This function handles adding a new revision of an existing Internet-Draft.
|
|
Prerequisites: draft must be active
|
|
Input: title, revision_date, pages, abstract, file input fields to upload new
|
|
draft document(s)
|
|
Actions
|
|
- move current doc(s) to archive directory
|
|
- upload new docs to live dir
|
|
- save doc in history
|
|
- increment revision
|
|
- reset expires
|
|
- create DocEvent
|
|
- handle sub-state
|
|
- schedule notification
|
|
'''
|
|
|
|
# TODO this behavior may change with archive strategy
|
|
archive_draft_files(draft.name + '-' + draft.rev)
|
|
|
|
# save form data
|
|
form = BaseRevisionModelForm(request.session['data'],instance=draft)
|
|
if form.is_valid():
|
|
new_draft = form.save(commit=False)
|
|
else:
|
|
raise Exception(form.errors)
|
|
raise Exception('Problem with input data %s' % form.data)
|
|
|
|
# set revision and expires
|
|
new_draft.rev = request.session['filename'][-2:]
|
|
new_draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
|
|
|
|
# create DocEvent
|
|
e = NewRevisionDocEvent.objects.create(type='new_revision',
|
|
by=request.user.person,
|
|
doc=draft,
|
|
rev=new_draft.rev,
|
|
desc='New revision available',
|
|
time=draft.time)
|
|
|
|
new_draft.save_with_history([e])
|
|
|
|
handle_substate(new_draft)
|
|
|
|
# move uploaded files to production directory
|
|
promote_files(new_draft, request.session['file_type'])
|
|
|
|
# save the submission record
|
|
post_submission(request)
|
|
|
|
# send announcement if we are in IESG process
|
|
if new_draft.get_state('draft-iesg'):
|
|
announcement_from_form(request.session['email'],by=request.user.person)
|
|
|
|
return
|
|
|
|
def do_update(draft,request):
|
|
'''
|
|
Actions
|
|
- increment revision #
|
|
- reset expires
|
|
- create DocEvent
|
|
- do substate check
|
|
- change state to Active
|
|
'''
|
|
# save form data
|
|
form = BaseRevisionModelForm(request.session['data'],instance=draft)
|
|
if form.is_valid():
|
|
new_draft = form.save(commit=False)
|
|
else:
|
|
raise Exception('Problem with input data %s' % form.data)
|
|
|
|
handle_substate(new_draft)
|
|
|
|
# update draft record
|
|
new_draft.rev = os.path.splitext(request.session['data']['filename'])[0][-2:]
|
|
new_draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
|
|
|
|
new_draft.set_state(State.objects.get(type="draft", slug="active"))
|
|
|
|
# create DocEvent
|
|
e = NewRevisionDocEvent.objects.create(type='new_revision',
|
|
by=request.user.person,
|
|
doc=new_draft,
|
|
rev=new_draft.rev,
|
|
desc='New revision available',
|
|
time=new_draft.time)
|
|
|
|
new_draft.save_with_history([e])
|
|
|
|
# move uploaded files to production directory
|
|
promote_files(new_draft, request.session['file_type'])
|
|
|
|
# save the submission record
|
|
post_submission(request)
|
|
|
|
# send announcement
|
|
announcement_from_form(request.session['email'],by=request.user.person)
|
|
|
|
return
|
|
|
|
def do_withdraw(draft,request):
|
|
'''
|
|
Actions
|
|
- change state to withdrawn
|
|
- TODO move file to archive
|
|
'''
|
|
withdraw_type = request.session['data']['type']
|
|
|
|
prev_state = draft.get_state("draft")
|
|
new_state = None
|
|
if withdraw_type == 'ietf':
|
|
new_state = State.objects.get(type="draft", slug="ietf-rm")
|
|
elif withdraw_type == 'author':
|
|
new_state = State.objects.get(type="draft", slug="auth-rm")
|
|
|
|
if not new_state:
|
|
return
|
|
|
|
draft.set_state(new_state)
|
|
|
|
e = add_state_change_event(draft, request.user.person, prev_state, new_state)
|
|
if e:
|
|
draft.save_with_history([e])
|
|
|
|
# send announcement
|
|
announcement_from_form(request.session['email'],by=request.user.person)
|
|
|
|
return
|
|
|
|
# -------------------------------------------------
|
|
# Standard View Functions
|
|
# -------------------------------------------------
|
|
@role_required('Secretariat')
|
|
def abstract(request, id):
|
|
'''
|
|
View Internet Draft Abstract
|
|
|
|
**Templates:**
|
|
|
|
* ``drafts/abstract.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* draft
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
return render(request, 'drafts/abstract.html', {
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def add(request):
|
|
'''
|
|
Add Internet Draft
|
|
|
|
**Templates:**
|
|
|
|
* ``drafts/add.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* form
|
|
'''
|
|
clear_non_auth(request.session)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts')
|
|
|
|
upload_form = UploadForm(request.POST, request.FILES)
|
|
form = AddModelForm(request.POST)
|
|
if form.is_valid() and upload_form.is_valid():
|
|
draft = form.save(commit=False)
|
|
|
|
# process files
|
|
filename,revision,file_type_list = process_files(request, draft)
|
|
name = get_base(filename)
|
|
|
|
# set fields (set stream or intended status?)
|
|
draft.rev = revision
|
|
draft.name = name
|
|
draft.type_id = 'draft'
|
|
|
|
# set stream based on document name
|
|
if not draft.stream:
|
|
stream_slug = None
|
|
if draft.name.startswith("draft-iab-"):
|
|
stream_slug = "iab"
|
|
elif draft.name.startswith("draft-irtf-"):
|
|
stream_slug = "irtf"
|
|
elif draft.name.startswith("draft-ietf-") and (draft.group.type_id != "individ"):
|
|
stream_slug = "ietf"
|
|
|
|
if stream_slug:
|
|
draft.stream = StreamName.objects.get(slug=stream_slug)
|
|
|
|
# set expires
|
|
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
|
|
|
|
draft.save(force_insert=True)
|
|
|
|
# set state
|
|
draft.set_state(State.objects.get(type="draft", slug="active"))
|
|
|
|
# automatically set state "WG Document"
|
|
if draft.stream_id == "ietf" and draft.group.type_id == "wg":
|
|
draft.set_state(State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
|
|
|
|
# create DocAlias
|
|
DocAlias.objects.get_or_create(name=name, document=draft)
|
|
|
|
# create DocEvent
|
|
NewRevisionDocEvent.objects.create(type='new_revision',
|
|
by=request.user.person,
|
|
doc=draft,
|
|
rev=draft.rev,
|
|
time=draft.time,
|
|
desc="New revision available")
|
|
|
|
# move uploaded files to production directory
|
|
promote_files(draft, file_type_list)
|
|
|
|
# save the submission record
|
|
post_submission(request)
|
|
|
|
request.session['action'] = 'add'
|
|
|
|
messages.success(request, 'New draft added successfully!')
|
|
return redirect('drafts_authors', id=draft.name)
|
|
|
|
else:
|
|
form = AddModelForm()
|
|
upload_form = UploadForm()
|
|
|
|
return render(request, 'drafts/add.html', {
|
|
'form': form,
|
|
'upload_form': upload_form},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def announce(request, id):
|
|
'''
|
|
Schedule announcement of new Internet-Draft to I-D Announce list
|
|
|
|
**Templates:**
|
|
|
|
* none
|
|
|
|
**Template Variables:**
|
|
|
|
* none
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
email_form = EmailForm(get_email_initial(draft,type='new'))
|
|
|
|
announcement_from_form(email_form.data,
|
|
by=request.user.person,
|
|
from_val='Internet-Drafts@ietf.org',
|
|
content_type='Multipart/Mixed; Boundary="NextPart"')
|
|
|
|
messages.success(request, 'Announcement scheduled successfully!')
|
|
return redirect('drafts_view', id=id)
|
|
|
|
@role_required('Secretariat')
|
|
def approvals(request):
|
|
'''
|
|
This view handles setting Initial Approval for drafts
|
|
'''
|
|
|
|
approved = Preapproval.objects.all().order_by('name')
|
|
form = None
|
|
|
|
return render(request, 'drafts/approvals.html', {
|
|
'form': form,
|
|
'approved': approved},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def author_delete(request, id, oid):
|
|
'''
|
|
This view deletes the specified author(email) from the draft
|
|
'''
|
|
DocumentAuthor.objects.get(id=oid).delete()
|
|
messages.success(request, 'The author was deleted successfully')
|
|
return redirect('drafts_authors', id=id)
|
|
|
|
@role_required('Secretariat')
|
|
def authors(request, id):
|
|
'''
|
|
Edit Internet Draft Authors
|
|
|
|
**Templates:**
|
|
|
|
* ``drafts/authors.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* form, draft
|
|
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
form = AuthorForm(request.POST)
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Done':
|
|
action = request.session.get('action','')
|
|
if action == 'add':
|
|
del request.session['action']
|
|
return redirect('drafts_announce', id=id)
|
|
|
|
return redirect('drafts_view', id=id)
|
|
|
|
if form.is_valid():
|
|
author = form.cleaned_data['email']
|
|
authors = draft.documentauthor_set.all()
|
|
if authors:
|
|
order = authors.aggregate(Max('order')).values()[0] + 1
|
|
else:
|
|
order = 1
|
|
DocumentAuthor.objects.create(document=draft,author=author,order=order)
|
|
|
|
messages.success(request, 'Author added successfully!')
|
|
return redirect('drafts_authors', id=id)
|
|
|
|
else:
|
|
form = AuthorForm()
|
|
|
|
return render(request, 'drafts/authors.html', {
|
|
'draft': draft,
|
|
'form': form},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def confirm(request, id):
|
|
'''
|
|
This view displays changes that will be made and calls appropriate
|
|
function if the user elects to proceed. If the user cancels then
|
|
the session data is cleared and view page is returned.
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
# TODO do cancel functions from session (ie remove uploaded files?)
|
|
# clear session data
|
|
clear_non_auth(request.session)
|
|
return redirect('drafts_view', id=id)
|
|
|
|
action = request.session['action']
|
|
if action == 'revision':
|
|
func = do_revision
|
|
elif action == 'resurrect':
|
|
func = do_resurrect
|
|
elif action == 'replace':
|
|
func = do_replace
|
|
elif action == 'update':
|
|
func = do_update
|
|
elif action == 'extend':
|
|
func = do_extend
|
|
elif action == 'withdraw':
|
|
func = do_withdraw
|
|
|
|
func(draft,request)
|
|
|
|
# clear session data
|
|
clear_non_auth(request.session)
|
|
|
|
messages.success(request, '%s action performed successfully!' % action)
|
|
return redirect('drafts_view', id=id)
|
|
|
|
details = get_action_details(draft, request.session)
|
|
email = request.session.get('email','')
|
|
action = request.session.get('action','')
|
|
|
|
return render(request, 'drafts/confirm.html', {
|
|
'details': details,
|
|
'email': email,
|
|
'action': action,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def dates(request):
|
|
'''
|
|
Manage ID Submission Dates
|
|
|
|
**Templates:**
|
|
|
|
* none
|
|
|
|
**Template Variables:**
|
|
|
|
* none
|
|
'''
|
|
meeting = get_meeting()
|
|
|
|
return render(request, 'drafts/dates.html', {
|
|
'meeting':meeting},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def edit(request, id):
|
|
'''
|
|
Since there's a lot going on in this function we are summarizing in the docstring.
|
|
Also serves as a record of requirements.
|
|
|
|
if revision number increases add document_comments and send notify-revision
|
|
if revision date changed and not the number return error
|
|
check if using restricted words (?)
|
|
send notification based on check box
|
|
revision date = now if a new status box checked add_id5.cfm
|
|
(notify_[resurrection,revision,updated,extended])
|
|
if rfcnum="" rfcnum=0
|
|
if status != 2, expired_tombstone="0"
|
|
if new revision move current txt and ps files to archive directory (add_id5.cfm)
|
|
if status > 3 create tombstone, else send revision notification (EmailIDRevision.cfm)
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
form = EditModelForm(request.POST, instance=draft)
|
|
if form.is_valid():
|
|
if form.changed_data:
|
|
e = DocEvent.objects.create(type='changed_document',
|
|
by=request.user.person,
|
|
doc=draft,
|
|
desc='Changed field(s): %s' % ','.join(form.changed_data))
|
|
# see EditModelForm.save() for detailed logic
|
|
form.save(commit=False)
|
|
draft.save_with_history([e])
|
|
|
|
messages.success(request, 'Draft modified successfully!')
|
|
|
|
return redirect('drafts_view', id=id)
|
|
else:
|
|
#assert False, form.errors
|
|
pass
|
|
else:
|
|
form = EditModelForm(instance=draft)
|
|
|
|
return render(request, 'drafts/edit.html', {
|
|
'form': form,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def email(request, id):
|
|
'''
|
|
This function displays the notification message and allows the
|
|
user to make changes before continuing to confirmation page.
|
|
One exception is the "revision" action, save email data and go
|
|
directly to confirm page.
|
|
'''
|
|
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
# clear session data
|
|
clear_non_auth(request.session)
|
|
return redirect('drafts_view', id=id)
|
|
|
|
form = EmailForm(request.POST)
|
|
if form.is_valid():
|
|
request.session['email'] = form.data
|
|
return redirect('drafts_confirm', id=id)
|
|
else:
|
|
# the resurrect email body references the last revision number, handle
|
|
# exception if no last revision found
|
|
# if this exception was handled closer to the source it would be easier to debug
|
|
# other problems with get_email_initial
|
|
try:
|
|
form = EmailForm(initial=get_email_initial(
|
|
draft,
|
|
type=request.session['action'],
|
|
input=request.session.get('data', None)))
|
|
except Exception, e:
|
|
return render(request, 'drafts/error.html', { 'error': e},)
|
|
|
|
# for "revision" action skip email page and go directly to confirm
|
|
if request.session['action'] == 'revision':
|
|
request.session['email'] = form.initial
|
|
return redirect('drafts_confirm', id=id)
|
|
|
|
return render(request, 'drafts/email.html', {
|
|
'form': form,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def extend(request, id):
|
|
'''
|
|
This view handles extending the expiration date for an Internet-Draft
|
|
Prerequisites: draft must be active
|
|
Input: new date
|
|
Actions
|
|
- revision_date = today
|
|
# - call handle_comment
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
form = ExtendForm(request.POST)
|
|
if form.is_valid():
|
|
request.session['data'] = form.cleaned_data
|
|
request.session['action'] = 'extend'
|
|
return redirect('drafts_email', id=id)
|
|
|
|
else:
|
|
form = ExtendForm(initial={'revision_date':datetime.date.today().isoformat()})
|
|
|
|
return render(request, 'drafts/extend.html', {
|
|
'form': form,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def makerfc(request, id):
|
|
'''
|
|
Make RFC out of Internet Draft
|
|
|
|
**Templates:**
|
|
|
|
* ``drafts/makerfc.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* draft
|
|
'''
|
|
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
# raise error if draft intended standard is empty
|
|
if not draft.intended_std_level:
|
|
messages.error(request, 'ERROR: intended RFC status is not set')
|
|
return redirect('drafts_view', id=id)
|
|
|
|
ObsFormset = formset_factory(RfcObsoletesForm, extra=15, max_num=15)
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
form = RfcModelForm(request.POST, instance=draft)
|
|
obs_formset = ObsFormset(request.POST, prefix='obs')
|
|
if form.is_valid() and obs_formset.is_valid():
|
|
|
|
# TODO
|
|
archive_draft_files(draft.name + '-' + draft.rev)
|
|
|
|
rfc = form.save(commit=False)
|
|
|
|
# create DocEvent
|
|
e = DocEvent.objects.create(type='published_rfc',
|
|
by=request.user.person,
|
|
doc=rfc,
|
|
desc="Published RFC")
|
|
|
|
# change state
|
|
draft.set_state(State.objects.get(type="draft", slug="rfc"))
|
|
|
|
# handle rfc_obsoletes formset
|
|
# NOTE: because we are just adding RFCs in this form we don't need to worry
|
|
# about the previous state of the obs forms
|
|
for obs_form in obs_formset.forms:
|
|
if obs_form.has_changed():
|
|
rfc_acted_on = obs_form.cleaned_data.get('rfc','')
|
|
target = DocAlias.objects.get(name="rfc%s" % rfc_acted_on)
|
|
relation = obs_form.cleaned_data.get('relation','')
|
|
if rfc and relation:
|
|
# form validation ensures the rfc_acted_on exists, can safely use get
|
|
RelatedDocument.objects.create(source=draft,
|
|
target=target,
|
|
relationship=DocRelationshipName.objects.get(slug=relation))
|
|
|
|
rfc.save_with_history([e])
|
|
|
|
messages.success(request, 'RFC created successfully!')
|
|
return redirect('drafts_view', id=id)
|
|
else:
|
|
# assert False, (form.errors, obs_formset.errors)
|
|
pass
|
|
else:
|
|
form = RfcModelForm(instance=draft)
|
|
obs_formset = ObsFormset(prefix='obs')
|
|
|
|
return render(request, 'drafts/makerfc.html', {
|
|
'form': form,
|
|
'obs_formset': obs_formset,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def nudge_report(request):
|
|
'''
|
|
This view produces the Nudge Report, basically a list of documents that are in the IESG
|
|
process but have not had activity in some time
|
|
'''
|
|
docs = Document.objects.filter(type='draft',states__slug='active')
|
|
docs = docs.filter(states=12,tags='need-rev')
|
|
|
|
return render(request, 'drafts/report_nudge.html', {
|
|
'docs': docs},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def replace(request, id):
|
|
'''
|
|
This view handles replacing one Internet-Draft with another
|
|
Prerequisites: draft must be active
|
|
Input: replacement draft filename
|
|
|
|
# TODO: support two different replaced messages in email
|
|
'''
|
|
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
form = ReplaceForm(request.POST, draft=draft)
|
|
if form.is_valid():
|
|
request.session['data'] = form.cleaned_data
|
|
request.session['action'] = 'replace'
|
|
return redirect('drafts_email', id=id)
|
|
|
|
else:
|
|
form = ReplaceForm(draft=draft)
|
|
|
|
return render(request, 'drafts/replace.html', {
|
|
'form': form,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def resurrect(request, id):
|
|
'''
|
|
This view handles resurrection of an Internet-Draft
|
|
Prerequisites: draft must be expired
|
|
Input: none
|
|
'''
|
|
|
|
request.session['action'] = 'resurrect'
|
|
return redirect('drafts_email', id=id)
|
|
|
|
@role_required('Secretariat')
|
|
def revision(request, id):
|
|
'''
|
|
This function presents the input form for the New Revision action. If submitted
|
|
form is valid state is saved in the session and the email page is returned.
|
|
'''
|
|
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
upload_form = UploadForm(request.POST, request.FILES, draft=draft)
|
|
form = RevisionModelForm(request.POST, instance=draft)
|
|
if form.is_valid() and upload_form.is_valid():
|
|
# process files
|
|
filename,revision,file_type_list = process_files(request,draft)
|
|
|
|
# save info in session and proceed to email page
|
|
request.session['data'] = form.cleaned_data
|
|
request.session['action'] = 'revision'
|
|
request.session['filename'] = filename
|
|
request.session['revision'] = revision
|
|
request.session['file_type'] = file_type_list
|
|
|
|
return redirect('drafts_email', id=id)
|
|
|
|
else:
|
|
form = RevisionModelForm(instance=draft,initial={'revision_date':datetime.date.today().isoformat()})
|
|
upload_form = UploadForm(draft=draft)
|
|
|
|
return render(request, 'drafts/revision.html', {
|
|
'form': form,
|
|
'upload_form': upload_form,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def search(request):
|
|
'''
|
|
Search Internet Drafts
|
|
|
|
**Templates:**
|
|
|
|
* ``drafts/search.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* form, results
|
|
|
|
'''
|
|
results = []
|
|
clear_non_auth(request.session)
|
|
|
|
if request.method == 'POST':
|
|
form = SearchForm(request.POST)
|
|
if request.POST['submit'] == 'Add':
|
|
return redirect('sec.drafts.views.add')
|
|
|
|
if form.is_valid():
|
|
kwargs = {}
|
|
intended_std_level = form.cleaned_data['intended_std_level']
|
|
title = form.cleaned_data['document_title']
|
|
group = form.cleaned_data['group']
|
|
name = form.cleaned_data['filename']
|
|
state = form.cleaned_data['state']
|
|
revision_date_start = form.cleaned_data['revision_date_start']
|
|
revision_date_end = form.cleaned_data['revision_date_end']
|
|
# construct seach query
|
|
if intended_std_level:
|
|
kwargs['intended_std_level'] = intended_std_level
|
|
if title:
|
|
kwargs['title__istartswith'] = title
|
|
if state:
|
|
kwargs['states__type'] = 'draft'
|
|
kwargs['states'] = state
|
|
if name:
|
|
kwargs['name__istartswith'] = name
|
|
if group:
|
|
kwargs['group__acronym__istartswith'] = group
|
|
if revision_date_start:
|
|
kwargs['docevent__type'] = 'new_revision'
|
|
kwargs['docevent__time__gte'] = revision_date_start
|
|
if revision_date_end:
|
|
kwargs['docevent__type'] = 'new_revision'
|
|
kwargs['docevent__time__lte'] = revision_date_end
|
|
|
|
# perform query
|
|
#assert False, kwargs
|
|
if kwargs:
|
|
qs = Document.objects.filter(**kwargs)
|
|
else:
|
|
qs = Document.objects.all()
|
|
#results = qs.order_by('group__name')
|
|
results = qs.order_by('name')
|
|
|
|
# if there's just one result go straight to view
|
|
if len(results) == 1:
|
|
return redirect('drafts_view', id=results[0].name)
|
|
else:
|
|
active_state = State.objects.get(type='draft',slug='active')
|
|
form = SearchForm(initial={'state':active_state.pk})
|
|
|
|
return render(request, 'drafts/search.html', {
|
|
'results': results,
|
|
'form': form},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def update(request, id):
|
|
'''
|
|
This view handles the Update action for an Internet-Draft
|
|
Update is when an expired draft gets a new revision, (the state does not change?)
|
|
Prerequisites: draft must be expired
|
|
Input: upload new files, pages, abstract, title
|
|
'''
|
|
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
upload_form = UploadForm(request.POST, request.FILES, draft=draft)
|
|
form = RevisionModelForm(request.POST, instance=draft)
|
|
if form.is_valid() and upload_form.is_valid():
|
|
# process files
|
|
filename,revision,file_type_list = process_files(request,draft)
|
|
|
|
# save state in session and proceed to email page
|
|
request.session['data'] = form.data
|
|
request.session['action'] = 'update'
|
|
request.session['revision'] = revision
|
|
request.session['data']['filename'] = filename
|
|
request.session['file_type'] = file_type_list
|
|
|
|
return redirect('drafts_email', id=id)
|
|
|
|
else:
|
|
form = RevisionModelForm(instance=draft,initial={'revision_date':datetime.date.today().isoformat()})
|
|
upload_form = UploadForm(draft=draft)
|
|
|
|
return render(request, 'drafts/revision.html', {
|
|
'form': form,
|
|
'upload_form':upload_form,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def view(request, id):
|
|
'''
|
|
View Internet Draft
|
|
|
|
**Templates:**
|
|
|
|
* ``drafts/view.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* draft, area, id_tracker_state
|
|
'''
|
|
draft = get_object_or_404(Document, name=id)
|
|
#clear_non_auth(request.session)
|
|
|
|
# TODO fix in Django 1.2
|
|
# some boolean state variables for use in the view.html template to manage display
|
|
# of action buttons. NOTE: Django 1.2 support new smart if tag in templates which
|
|
# will remove the need for these variables
|
|
state = draft.get_state_slug()
|
|
is_active = True if state == 'active' else False
|
|
is_expired = True if state == 'expired' else False
|
|
is_withdrawn = True if (state in ('auth-rm','ietf-rm')) else False
|
|
|
|
# TODO should I rewrite all these or just use proxy.InternetDraft?
|
|
# add legacy fields
|
|
draft.iesg_state = draft.get_state('draft-iesg')
|
|
draft.review_by_rfc_editor = bool(draft.tags.filter(slug='rfc-rev'))
|
|
|
|
# can't assume there will be a new_revision record
|
|
r_event = draft.latest_event(type__in=('new_revision','completed_resurrect'))
|
|
draft.revision_date = r_event.time.date() if r_event else None
|
|
|
|
draft.start_date = get_start_date(draft)
|
|
|
|
e = draft.latest_event(type__in=('expired_document', 'new_revision', "completed_resurrect"))
|
|
draft.expiration_date = e.time.date() if e and e.type == "expired_document" else None
|
|
draft.rfc_number = get_rfc_num(draft)
|
|
|
|
# check for replaced bys
|
|
qs = Document.objects.filter(relateddocument__target__document=draft, relateddocument__relationship='replaces')
|
|
if qs:
|
|
draft.replaced_by = qs[0]
|
|
|
|
# check for DEVELOPMENT setting and pass to template
|
|
is_development = False
|
|
try:
|
|
is_development = settings.DEVELOPMENT
|
|
except AttributeError:
|
|
pass
|
|
|
|
return render(request, 'drafts/view.html', {
|
|
'is_active': is_active,
|
|
'is_expired': is_expired,
|
|
'is_withdrawn': is_withdrawn,
|
|
'is_development': is_development,
|
|
'draft': draft},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def withdraw(request, id):
|
|
'''
|
|
This view handles withdrawing an Internet-Draft
|
|
Prerequisites: draft must be active
|
|
Input: by IETF or Author
|
|
'''
|
|
|
|
draft = get_object_or_404(Document, name=id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('drafts_view', id=id)
|
|
|
|
form = WithdrawForm(request.POST)
|
|
if form.is_valid():
|
|
# save state in session and proceed to email page
|
|
request.session['data'] = form.data
|
|
request.session['action'] = 'withdraw'
|
|
|
|
return redirect('drafts_email', id=id)
|
|
|
|
else:
|
|
form = WithdrawForm()
|
|
|
|
return render(request, 'drafts/withdraw.html', {
|
|
'draft': draft,
|
|
'form': form},
|
|
)
|
|
|