Merged in ^/branch/scow/track-manual-id-posts-6.31.1.dev0, which provides secretariat support for managing draft submissions outside of the automated datatracker draft submission tool. In addition to making the secreatiat handling of draft submissions that come in by email or other paths easier, it also improves the datatracker submission and document history for these documents.
- Legacy-Id: 11959
This commit is contained in:
commit
22b2953b18
33
ietf/doc/migrations/0014_auto_20160524_2147.py
Normal file
33
ietf/doc/migrations/0014_auto_20160524_2147.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('message', '__first__'),
|
||||
('doc', '0013_auto_20151027_1127'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AddedMessageEvent',
|
||||
fields=[
|
||||
('docevent_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='doc.DocEvent')),
|
||||
('msgtype', models.CharField(max_length=25)),
|
||||
('in_reply_to', models.ForeignKey(related_name='doc_irtomanual', blank=True, to='message.Message', null=True)),
|
||||
('message', models.ForeignKey(related_name='doc_manualevents', blank=True, to='message.Message', null=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('doc.docevent',),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='docevent',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50, choices=[(b'new_revision', b'Added new revision'), (b'changed_document', b'Changed document metadata'), (b'added_comment', b'Added comment'), (b'added_message', b'Added message'), (b'deleted', b'Deleted document'), (b'changed_state', b'Changed state'), (b'changed_stream', b'Changed document stream'), (b'expired_document', b'Expired document'), (b'extended_expiry', b'Extended expiry of document'), (b'requested_resurrect', b'Requested resurrect'), (b'completed_resurrect', b'Completed resurrect'), (b'changed_consensus', b'Changed consensus'), (b'published_rfc', b'Published RFC'), (b'added_suggested_replaces', b'Added suggested replacement relationships'), (b'reviewed_suggested_replaces', b'Reviewed suggested replacement relationships'), (b'changed_group', b'Changed group'), (b'changed_protocol_writeup', b'Changed protocol writeup'), (b'changed_charter_milestone', b'Changed charter milestone'), (b'initial_review', b'Set initial review time'), (b'changed_review_announcement', b'Changed WG Review text'), (b'changed_action_announcement', b'Changed WG Action text'), (b'started_iesg_process', b'Started IESG process on document'), (b'created_ballot', b'Created ballot'), (b'closed_ballot', b'Closed ballot'), (b'sent_ballot_announcement', b'Sent ballot announcement'), (b'changed_ballot_position', b'Changed ballot position'), (b'changed_ballot_approval_text', b'Changed ballot approval text'), (b'changed_ballot_writeup_text', b'Changed ballot writeup text'), (b'changed_rfc_editor_note_text', b'Changed RFC Editor Note text'), (b'changed_last_call_text', b'Changed last call text'), (b'requested_last_call', b'Requested last call'), (b'sent_last_call', b'Sent last call'), (b'scheduled_for_telechat', b'Scheduled for telechat'), (b'iesg_approved', b'IESG approved document (no problem)'), (b'iesg_disapproved', b'IESG disapproved document (do not publish)'), (b'approved_in_minute', b'Approved in minute'), (b'iana_review', b'IANA review comment'), (b'rfc_in_iana_registry', b'RFC is in IANA registry'), (b'rfc_editor_received_announcement', b'Announcement was received by RFC Editor'), (b'requested_publication', b'Publication at RFC Editor requested')]),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -7,7 +7,7 @@ from django.db import models, migrations
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('doc', '0013_auto_20151027_1127'),
|
||||
('doc', '0014_auto_20160524_2147'),
|
||||
]
|
||||
|
||||
operations = [
|
|
@ -631,6 +631,7 @@ EVENT_TYPES = [
|
|||
("new_revision", "Added new revision"),
|
||||
("changed_document", "Changed document metadata"),
|
||||
("added_comment", "Added comment"),
|
||||
("added_message", "Added message"),
|
||||
|
||||
("deleted", "Deleted document"),
|
||||
|
||||
|
@ -823,6 +824,13 @@ class InitialReviewDocEvent(DocEvent):
|
|||
expires = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
|
||||
class AddedMessageEvent(DocEvent):
|
||||
import ietf.message.models
|
||||
message = models.ForeignKey(ietf.message.models.Message, null=True, blank=True,related_name='doc_manualevents')
|
||||
msgtype = models.CharField(max_length=25)
|
||||
in_reply_to = models.ForeignKey(ietf.message.models.Message, null=True, blank=True,related_name='doc_irtomanual')
|
||||
|
||||
|
||||
# dumping store for removed events
|
||||
class DeletedEvent(models.Model):
|
||||
content_type = models.ForeignKey(ContentType)
|
||||
|
|
|
@ -11,7 +11,7 @@ from ietf.doc.models import (BallotType, DeletedEvent, StateType, State, Documen
|
|||
DocumentAuthor, DocEvent, StateDocEvent, DocHistory, ConsensusDocEvent, DocAlias,
|
||||
TelechatDocEvent, DocReminder, LastCallDocEvent, NewRevisionDocEvent, WriteupDocEvent,
|
||||
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
|
||||
RelatedDocHistory, BallotPositionDocEvent)
|
||||
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent)
|
||||
|
||||
|
||||
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
|
||||
|
@ -513,3 +513,32 @@ class BallotPositionDocEventResource(ModelResource):
|
|||
}
|
||||
api.doc.register(BallotPositionDocEventResource())
|
||||
|
||||
|
||||
|
||||
from ietf.person.resources import PersonResource
|
||||
from ietf.message.resources import MessageResource
|
||||
class AddedMessageEventResource(ModelResource):
|
||||
by = ToOneField(PersonResource, 'by')
|
||||
doc = ToOneField(DocumentResource, 'doc')
|
||||
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
|
||||
message = ToOneField(MessageResource, 'message', null=True)
|
||||
in_reply_to = ToOneField(MessageResource, 'in_reply_to', null=True)
|
||||
class Meta:
|
||||
queryset = AddedMessageEvent.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'addedmessageevent'
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"time": ALL,
|
||||
"type": ALL,
|
||||
"desc": ALL,
|
||||
"msgtype": ALL,
|
||||
"by": ALL_WITH_RELATIONS,
|
||||
"doc": ALL_WITH_RELATIONS,
|
||||
"docevent_ptr": ALL_WITH_RELATIONS,
|
||||
"message": ALL_WITH_RELATIONS,
|
||||
"in_reply_to": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.doc.register(AddedMessageEventResource())
|
||||
|
||||
|
|
|
@ -278,6 +278,16 @@ def add_links_in_new_revision_events(doc, events, diff_revisions):
|
|||
prev = diff_url
|
||||
|
||||
|
||||
def add_events_message_info(events):
|
||||
for e in events:
|
||||
if not e.type == "added_message":
|
||||
continue
|
||||
|
||||
e.message = e.addedmessageevent.message
|
||||
e.msgtype = e.addedmessageevent.msgtype
|
||||
e.in_reply_to = e.addedmessageevent.in_reply_to
|
||||
|
||||
|
||||
def get_document_content(key, filename, split=True, markup=True):
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
|
|
|
@ -50,7 +50,8 @@ from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDo
|
|||
from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_with_revision,
|
||||
can_adopt_draft, get_chartering_type, get_document_content, get_tags_for_stream_id,
|
||||
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
||||
get_initial_notify, make_notify_changed_event, crawl_history, default_consensus)
|
||||
get_initial_notify, make_notify_changed_event, crawl_history, default_consensus,
|
||||
add_events_message_info)
|
||||
from ietf.community.utils import augment_docs_with_tracking_info
|
||||
from ietf.group.models import Role
|
||||
from ietf.group.utils import can_manage_group, can_manage_materials
|
||||
|
@ -657,6 +658,7 @@ def document_history(request, name):
|
|||
|
||||
augment_events_with_revision(doc, events)
|
||||
add_links_in_new_revision_events(doc, events, diff_revisions)
|
||||
add_events_message_info(events)
|
||||
|
||||
# figure out if the current user can add a comment to the history
|
||||
if doc.type_id == "draft" and doc.group != None:
|
||||
|
|
32
ietf/mailtrigger/migrations/0006_auto_20160707_1933.py
Normal file
32
ietf/mailtrigger/migrations/0006_auto_20160707_1933.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
|
||||
rc = Recipient.objects.create
|
||||
|
||||
rc(slug='submission_manualpost_handling',
|
||||
desc='IETF manual post handling',
|
||||
template='<ietf-manualpost@ietf.org>')
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
Recipient=apps.get_model('mailtrigger','Recipient')
|
||||
|
||||
Recipient.objects.filter(slug='submission_manualpost_handling').delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0005_interim_trigger'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
|
@ -80,6 +80,9 @@ def gather_relevant_expansions(**kwargs):
|
|||
rule_list.append((mailtrigger.slug,mailtrigger.desc,addrs.to,addrs.cc))
|
||||
return sorted(rule_list)
|
||||
|
||||
def get_base_submission_message_address():
|
||||
return Recipient.objects.get(slug='submission_manualpost_handling').gather()[0]
|
||||
|
||||
def get_base_ipr_request_address():
|
||||
return Recipient.objects.get(slug='ipr_requests').gather()[0]
|
||||
|
||||
|
|
|
@ -28,6 +28,19 @@ class Message(models.Model):
|
|||
def __unicode__(self):
|
||||
return "'%s' %s -> %s" % (self.subject, self.frm, self.to)
|
||||
|
||||
|
||||
class MessageAttachment(models.Model):
|
||||
message = models.ForeignKey(Message)
|
||||
filename = models.CharField(max_length=255, db_index=True, blank=True)
|
||||
content_type = models.CharField(max_length=255, blank=True)
|
||||
encoding = models.CharField(max_length=255, blank=True)
|
||||
removed = models.BooleanField(default=False)
|
||||
body = models.TextField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.filename
|
||||
|
||||
|
||||
class SendQueue(models.Model):
|
||||
time = models.DateTimeField(default=datetime.datetime.now)
|
||||
by = models.ForeignKey(Person)
|
||||
|
|
|
@ -7,12 +7,11 @@ from tastypie.cache import SimpleCache
|
|||
|
||||
from ietf import api
|
||||
|
||||
from ietf.message.models import Message, SendQueue
|
||||
|
||||
|
||||
from ietf.message.models import Message, SendQueue, MessageAttachment
|
||||
from ietf.person.resources import PersonResource
|
||||
from ietf.group.resources import GroupResource
|
||||
from ietf.doc.resources import DocumentResource
|
||||
|
||||
class MessageResource(ModelResource):
|
||||
by = ToOneField(PersonResource, 'by')
|
||||
related_groups = ToManyField(GroupResource, 'related_groups', null=True)
|
||||
|
@ -59,3 +58,21 @@ class SendQueueResource(ModelResource):
|
|||
}
|
||||
api.message.register(SendQueueResource())
|
||||
|
||||
|
||||
|
||||
class MessageAttachmentResource(ModelResource):
|
||||
message = ToOneField(MessageResource, 'message')
|
||||
class Meta:
|
||||
queryset = MessageAttachment.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'messageattachment'
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"filename": ALL,
|
||||
"removed": ALL,
|
||||
"body": ALL,
|
||||
"message": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.message.register(MessageAttachmentResource())
|
||||
|
||||
|
|
|
@ -188,7 +188,7 @@
|
|||
"order": 0,
|
||||
"revname": "Conflict reviewed by",
|
||||
"used": true,
|
||||
"name": "Conflict reviews",
|
||||
"name": "conflict reviews",
|
||||
"desc": ""
|
||||
},
|
||||
"model": "name.docrelationshipname",
|
||||
|
@ -872,6 +872,20 @@
|
|||
"model": "name.draftsubmissionstatename",
|
||||
"pk": "posted"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"order": 8,
|
||||
"next_states": [
|
||||
"cancel",
|
||||
"posted"
|
||||
],
|
||||
"used": true,
|
||||
"name": "Manual Post Waiting for Draft",
|
||||
"desc": ""
|
||||
},
|
||||
"model": "name.draftsubmissionstatename",
|
||||
"pk": "waiting-for-draft"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"order": 0,
|
||||
|
@ -4903,6 +4917,14 @@
|
|||
"model": "mailtrigger.recipient",
|
||||
"pk": "logged_in_person"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"template": "<ietf-manualpost@ietf.org>",
|
||||
"desc": "IETF manual post handling"
|
||||
},
|
||||
"model": "mailtrigger.recipient",
|
||||
"pk": "submission_manualpost_handling"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"template": "<new-work@ietf.org>",
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
from django.contrib import admin
|
||||
|
||||
|
||||
from ietf.submit.models import Preapproval, Submission, SubmissionEvent, SubmissionCheck
|
||||
from ietf.submit.models import Preapproval, Submission, SubmissionEvent, SubmissionCheck, SubmissionEmailEvent
|
||||
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'rev', 'draft_link', 'status_link', 'submission_date',]
|
||||
|
@ -11,7 +11,7 @@ class SubmissionAdmin(admin.ModelAdmin):
|
|||
raw_id_fields = ['group', 'draft']
|
||||
|
||||
def status_link(self, instance):
|
||||
url = urlreverse('submit_submission_status_by_hash',
|
||||
url = urlreverse('ietf.submit.views.submission_status',
|
||||
kwargs=dict(submission_id=instance.pk,
|
||||
access_token=instance.access_token()))
|
||||
return '<a href="%s">%s</a>' % (url, instance.state)
|
||||
|
@ -40,3 +40,6 @@ class PreapprovalAdmin(admin.ModelAdmin):
|
|||
pass
|
||||
admin.site.register(Preapproval, PreapprovalAdmin)
|
||||
|
||||
class SubmissionEmailEventAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'submission', 'time', 'by', 'message', 'desc', ]
|
||||
admin.site.register(SubmissionEmailEvent, SubmissionEmailEventAdmin)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import re
|
||||
import datetime
|
||||
import email
|
||||
import pytz
|
||||
import xml2rfc
|
||||
import tempfile
|
||||
|
@ -16,7 +17,9 @@ from ietf.doc.models import Document
|
|||
from ietf.group.models import Group
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.doc.fields import SearchableDocAliasesField
|
||||
from ietf.ipr.mail import utc_from_string
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.message.models import Message
|
||||
from ietf.submit.models import Submission, Preapproval
|
||||
from ietf.submit.utils import validate_submission_rev, validate_submission_document_date
|
||||
from ietf.submit.parsers.pdf_parser import PDFParser
|
||||
|
@ -223,9 +226,9 @@ class SubmissionUploadForm(forms.Form):
|
|||
self.group = self.deduce_group()
|
||||
|
||||
# check existing
|
||||
existing = Submission.objects.filter(name=self.filename, rev=self.revision).exclude(state__in=("posted", "cancel"))
|
||||
existing = Submission.objects.filter(name=self.filename, rev=self.revision).exclude(state__in=("posted", "cancel", "waiting-for-draft"))
|
||||
if existing:
|
||||
raise forms.ValidationError(mark_safe('A submission with same name and revision is currently being processed. <a href="%s">Check the status here.</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
|
||||
raise forms.ValidationError(mark_safe('A submission with same name and revision is currently being processed. <a href="%s">Check the status here.</a>' % urlreverse("ietf.submit.views.submission_status", kwargs={ 'submission_id': existing[0].pk })))
|
||||
|
||||
# cut-off
|
||||
if self.revision == '00' and self.in_first_cut_off:
|
||||
|
@ -320,6 +323,9 @@ class NameEmailForm(forms.Form):
|
|||
name = forms.CharField(required=True)
|
||||
email = forms.EmailField(label=u'Email address')
|
||||
|
||||
#Fields for secretariat only
|
||||
approvals_received = forms.BooleanField(label=u'Approvals received', required=False, initial=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
email_required = kwargs.pop("email_required", True)
|
||||
super(NameEmailForm, self).__init__(*args, **kwargs)
|
||||
|
@ -421,3 +427,86 @@ class PreapprovalForm(forms.Form):
|
|||
raise forms.ValidationError("A draft with this name has already been submitted and accepted. A pre-approval would not make any difference.")
|
||||
|
||||
return n
|
||||
|
||||
|
||||
class SubmissionEmailForm(forms.Form):
|
||||
'''
|
||||
Used to add a message to a submission or to create a new submission.
|
||||
This message is NOT a reply to a previous message but has arrived out of band
|
||||
|
||||
if submission_pk is None we are startign a new submission and name
|
||||
must be unique. Otehrwise the name must match the submission.name.
|
||||
'''
|
||||
name = forms.CharField(required=True, max_length=255, label="Draft name")
|
||||
submission_pk = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||||
direction = forms.ChoiceField(choices=(("incoming", "Incoming"), ("outgoing", "Outgoing")),
|
||||
widget=forms.RadioSelect)
|
||||
message = forms.CharField(required=True, widget=forms.Textarea,
|
||||
help_text="Copy the entire message including headers. To do so, view the source, select all, copy then paste into the text area above")
|
||||
#in_reply_to = MessageModelChoiceField(queryset=Message.objects,label="In Reply To",required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SubmissionEmailForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_message(self):
|
||||
'''Returns a ietf.message.models.Message object'''
|
||||
self.message_text = self.cleaned_data['message']
|
||||
try:
|
||||
message = email.message_from_string(self.message_text)
|
||||
except Exception as e:
|
||||
self.add_error('message', e)
|
||||
return None
|
||||
|
||||
for field in ('to','from','subject','date'):
|
||||
if not message[field]:
|
||||
raise forms.ValidationError('Error parsing email: {} field not found.'.format(field))
|
||||
date = utc_from_string(message['date'])
|
||||
if not isinstance(date,datetime.datetime):
|
||||
raise forms.ValidationError('Error parsing email date field')
|
||||
return message
|
||||
|
||||
def clean(self):
|
||||
if any(self.errors):
|
||||
return self.cleaned_data
|
||||
super(SubmissionEmailForm, self).clean()
|
||||
name = self.cleaned_data['name']
|
||||
match = re.search(r"(draft-[a-z0-9-]*)-(\d\d)", name)
|
||||
if not match:
|
||||
self.add_error('name',
|
||||
"Submission name {} must start with 'draft-' and only contain digits, lowercase letters and dash characters and end with revision.".format(name))
|
||||
else:
|
||||
self.draft_name = match.group(1)
|
||||
self.revision = match.group(2)
|
||||
|
||||
error = validate_submission_rev(self.draft_name, self.revision)
|
||||
if error:
|
||||
raise forms.ValidationError(error)
|
||||
|
||||
#in_reply_to = self.cleaned_data['in_reply_to']
|
||||
#message = self.cleaned_data['message']
|
||||
direction = self.cleaned_data['direction']
|
||||
if direction != 'incoming' and direction != 'outgoing':
|
||||
self.add_error('direction', "Must be one of 'outgoing' or 'incoming'")
|
||||
|
||||
#if in_reply_to:
|
||||
# if direction != 'incoming':
|
||||
# raise forms.ValidationError('Only incoming messages can have In Reply To selected')
|
||||
# date = utc_from_string(message['date'])
|
||||
# if date < in_reply_to.time:
|
||||
# raise forms.ValidationError('The incoming message must have a date later than the message it is replying to')
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
class MessageModelForm(forms.ModelForm):
|
||||
in_reply_to_id = forms.CharField(required=False, widget=forms.HiddenInput())
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ['to','frm','cc','bcc','reply_to','subject','body']
|
||||
exclude = ['time','by','content_type','related_groups','related_docs']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MessageModelForm, self).__init__(*args, **kwargs)
|
||||
self.fields['frm'].label='From'
|
||||
self.fields['frm'].widget.attrs['readonly'] = 'True'
|
||||
self.fields['reply_to'].widget.attrs['readonly'] = 'True'
|
||||
|
|
|
@ -1,22 +1,33 @@
|
|||
import re
|
||||
import email
|
||||
import datetime
|
||||
import base64
|
||||
import os
|
||||
import pyzmail
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.core.validators import ValidationError
|
||||
from django.contrib.sites.models import Site
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.mail import send_mail, send_mail_message
|
||||
from ietf.doc.models import Document
|
||||
from ietf.ipr.mail import utc_from_string
|
||||
from ietf.person.models import Person
|
||||
from ietf.message.models import Message
|
||||
from ietf.message.models import Message, MessageAttachment
|
||||
from ietf.utils.accesstoken import generate_access_token
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.mailtrigger.utils import gather_address_lists, get_base_submission_message_address
|
||||
from ietf.submit.models import SubmissionEmailEvent, Submission
|
||||
|
||||
def send_submission_confirmation(request, submission, chair_notice=False):
|
||||
subject = 'Confirm submission of I-D %s' % submission.name
|
||||
from_email = settings.IDSUBMIT_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('sub_confirmation_requested',submission=submission)
|
||||
|
||||
confirm_url = settings.IDTRACKER_BASE_URL + urlreverse('submit_confirm_submission', kwargs=dict(submission_id=submission.pk, auth_token=generate_access_token(submission.auth_key)))
|
||||
status_url = settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status_by_hash', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token()))
|
||||
confirm_url = settings.IDTRACKER_BASE_URL + urlreverse('ietf.submit.views.confirm_submission', kwargs=dict(submission_id=submission.pk, auth_token=generate_access_token(submission.auth_key)))
|
||||
status_url = settings.IDTRACKER_BASE_URL + urlreverse('ietf.submit.views.submission_status', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token()))
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'submit/confirm_submission.txt',
|
||||
{
|
||||
|
@ -35,7 +46,7 @@ def send_full_url(request, submission):
|
|||
subject = 'Full URL for managing submission of draft %s' % submission.name
|
||||
from_email = settings.IDSUBMIT_FROM_EMAIL
|
||||
(to_email, cc) = gather_address_lists('sub_management_url_requested',submission=submission)
|
||||
url = settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status_by_hash', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token()))
|
||||
url = settings.IDTRACKER_BASE_URL + urlreverse('ietf.submit.views.submission_status', kwargs=dict(submission_id=submission.pk, access_token=submission.access_token()))
|
||||
|
||||
send_mail(request, to_email, from_email, subject, 'submit/full_url.txt',
|
||||
{
|
||||
|
@ -71,7 +82,7 @@ def send_manual_post_request(request, submission, errors):
|
|||
(to_email,cc) = gather_address_lists('sub_manual_post_requested',submission=submission)
|
||||
send_mail(request, to_email, from_email, subject, 'submit/manual_post_request.txt', {
|
||||
'submission': submission,
|
||||
'url': settings.IDTRACKER_BASE_URL + urlreverse('submit_submission_status', kwargs=dict(submission_id=submission.pk)),
|
||||
'url': settings.IDTRACKER_BASE_URL + urlreverse('ietf.submit.views.submission_status', kwargs=dict(submission_id=submission.pk)),
|
||||
'errors': errors,
|
||||
}, cc=cc)
|
||||
|
||||
|
@ -121,3 +132,183 @@ def announce_to_authors(request, submission):
|
|||
{'submission': submission,
|
||||
'group': group},
|
||||
cc=cc)
|
||||
|
||||
|
||||
def get_reply_to():
|
||||
"""Returns a new reply-to address for use with an outgoing message. This is an
|
||||
address with "plus addressing" using a random string. Guaranteed to be unique"""
|
||||
local,domain = get_base_submission_message_address().split('@')
|
||||
while True:
|
||||
rand = base64.urlsafe_b64encode(os.urandom(12))
|
||||
address = "{}+{}@{}".format(local,rand,domain)
|
||||
q = Message.objects.filter(reply_to=address)
|
||||
if not q:
|
||||
return address
|
||||
|
||||
|
||||
def process_response_email(msg):
|
||||
"""Saves an incoming message. msg=string. Message "To" field is expected to
|
||||
be in the format ietf-submit+[identifier]@ietf.org. Expect to find a message with
|
||||
a matching value in the reply_to field, associated to a submission.
|
||||
Create a Message object for the incoming message and associate it to
|
||||
the original message via new SubmissionEvent"""
|
||||
message = email.message_from_string(msg)
|
||||
to = message.get('To')
|
||||
|
||||
# exit if this isn't a response we're interested in (with plus addressing)
|
||||
local,domain = get_base_submission_message_address().split('@')
|
||||
if not re.match(r'^{}\+[a-zA-Z0-9_\-]{}@{}'.format(local,'{16}',domain),to):
|
||||
return None
|
||||
|
||||
try:
|
||||
to_message = Message.objects.get(reply_to=to)
|
||||
except Message.DoesNotExist:
|
||||
log('Error finding matching message ({})'.format(to))
|
||||
return None
|
||||
|
||||
try:
|
||||
submission = to_message.manualevents.first().submission
|
||||
except:
|
||||
log('Error processing message ({})'.format(to))
|
||||
return None
|
||||
|
||||
if not submission:
|
||||
log('Error processing message - no submission ({})'.format(to))
|
||||
return None
|
||||
|
||||
parts = pyzmail.parse.get_mail_parts(message)
|
||||
body=''
|
||||
for part in parts:
|
||||
if part.is_body == 'text/plain' and part.disposition == None:
|
||||
payload, used_charset = pyzmail.decode_text(part.get_payload(), part.charset, None)
|
||||
body = body + payload + '\n'
|
||||
|
||||
by = Person.objects.get(name="(System)")
|
||||
msg = submit_message_from_message(message, body, by)
|
||||
|
||||
desc = "Email: received message - manual post - {}-{}".format(
|
||||
submission.name,
|
||||
submission.rev)
|
||||
|
||||
submission_email_event = SubmissionEmailEvent.objects.create(
|
||||
submission = submission,
|
||||
desc = desc,
|
||||
msgtype = 'msgin',
|
||||
by = by,
|
||||
message = msg,
|
||||
in_reply_to = to_message
|
||||
)
|
||||
|
||||
save_submission_email_attachments(submission_email_event, parts)
|
||||
|
||||
log(u"Received submission email from %s" % msg.frm)
|
||||
return msg
|
||||
|
||||
|
||||
def add_submission_email(request, remote_ip, name, rev, submission_pk, message, by, msgtype):
|
||||
"""Add email to submission history"""
|
||||
|
||||
#in_reply_to = form.cleaned_data['in_reply_to']
|
||||
# create Message
|
||||
parts = pyzmail.parse.get_mail_parts(message)
|
||||
body=''
|
||||
for part in parts:
|
||||
if part.is_body == 'text/plain' and part.disposition == None:
|
||||
payload, used_charset = pyzmail.decode_text(part.get_payload(), part.charset, None)
|
||||
body = body + payload + '\n'
|
||||
|
||||
msg = submit_message_from_message(message, body, by)
|
||||
|
||||
if (submission_pk != None):
|
||||
# Must exist - we're adding a message to an existing submission
|
||||
submission = Submission.objects.get(pk=submission_pk)
|
||||
else:
|
||||
# Must not exist
|
||||
submissions = Submission.objects.filter(name=name,rev=rev).exclude(state_id='cancel')
|
||||
if submissions.count() > 0:
|
||||
raise ValidationError("Submission {} already exists".format(name))
|
||||
|
||||
# create Submission using the name
|
||||
try:
|
||||
submission = Submission.objects.create(
|
||||
state_id="waiting-for-draft",
|
||||
remote_ip=remote_ip,
|
||||
name=name,
|
||||
rev=rev,
|
||||
title=name,
|
||||
note="",
|
||||
submission_date=datetime.date.today(),
|
||||
replaces="",
|
||||
)
|
||||
from ietf.submit.utils import create_submission_event, docevent_from_submission
|
||||
desc = "Submission created for rev {} in response to email".format(rev)
|
||||
create_submission_event(request,
|
||||
submission,
|
||||
desc)
|
||||
docevent_from_submission(request,
|
||||
submission,
|
||||
desc)
|
||||
except Exception as e:
|
||||
log("Exception: %s\n" % e)
|
||||
raise
|
||||
|
||||
if msgtype == 'msgin':
|
||||
rs = "Received"
|
||||
else:
|
||||
rs = "Sent"
|
||||
|
||||
desc = "{} message - manual post - {}-{}".format(rs, name, rev)
|
||||
submission_email_event = SubmissionEmailEvent.objects.create(
|
||||
desc = desc,
|
||||
submission = submission,
|
||||
msgtype = msgtype,
|
||||
by = by,
|
||||
message = msg)
|
||||
#in_reply_to = in_reply_to
|
||||
|
||||
save_submission_email_attachments(submission_email_event, parts)
|
||||
return submission, submission_email_event
|
||||
|
||||
|
||||
def submit_message_from_message(message,body,by=None):
|
||||
"""Returns a ietf.message.models.Message. msg=email.Message
|
||||
A copy of mail.message_from_message with different body handling
|
||||
"""
|
||||
if not by:
|
||||
by = Person.objects.get(name="(System)")
|
||||
msg = Message.objects.create(
|
||||
by = by,
|
||||
subject = message.get('subject',''),
|
||||
frm = message.get('from',''),
|
||||
to = message.get('to',''),
|
||||
cc = message.get('cc',''),
|
||||
bcc = message.get('bcc',''),
|
||||
reply_to = message.get('reply_to',''),
|
||||
body = body,
|
||||
time = utc_from_string(message.get('date', ''))
|
||||
)
|
||||
return msg
|
||||
|
||||
def save_submission_email_attachments(submission_email_event, parts):
|
||||
for part in parts:
|
||||
if part.disposition != 'attachment':
|
||||
continue
|
||||
|
||||
if part.type == 'text/plain':
|
||||
payload, used_charset = pyzmail.decode_text(part.get_payload(),
|
||||
part.charset,
|
||||
None)
|
||||
encoding = ""
|
||||
else:
|
||||
# Need a better approach - for the moment we'll just handle these
|
||||
# and encode as base64
|
||||
payload = base64.b64encode(part.get_payload())
|
||||
encoding = "base64"
|
||||
|
||||
#name = submission_email_event.submission.name
|
||||
|
||||
MessageAttachment.objects.create(message = submission_email_event.message,
|
||||
content_type = part.type,
|
||||
encoding = encoding,
|
||||
filename=part.filename,
|
||||
body=payload)
|
||||
|
|
0
ietf/submit/management/__init__.py
Normal file
0
ietf/submit/management/__init__.py
Normal file
0
ietf/submit/management/commands/__init__.py
Normal file
0
ietf/submit/management/commands/__init__.py
Normal file
27
ietf/submit/management/commands/manualpost_email.py
Normal file
27
ietf/submit/management/commands/manualpost_email.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import sys
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from ietf.submit.mail import process_response_email
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (u"Process incoming manual post email responses")
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--email-file', dest='email', help='File containing email (default: stdin)'),)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
email = options.get('email', None)
|
||||
msg = None
|
||||
|
||||
if not email:
|
||||
msg = sys.stdin.read()
|
||||
else:
|
||||
msg = open(email, "r").read()
|
||||
|
||||
try:
|
||||
process_response_email(msg)
|
||||
except ValueError as e:
|
||||
raise CommandError(e)
|
28
ietf/submit/migrations/0011_submissionemail.py
Normal file
28
ietf/submit/migrations/0011_submissionemail.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('message', '__first__'),
|
||||
('submit', '0010_data_set_submission_check_symbol'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SubmissionEmailEvent',
|
||||
fields=[
|
||||
('submissionevent_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='submit.SubmissionEvent')),
|
||||
('msgtype', models.CharField(max_length=25)),
|
||||
('in_reply_to', models.ForeignKey(related_name='irtomanual', blank=True, to='message.Message', null=True)),
|
||||
('message', models.ForeignKey(related_name='manualevents', blank=True, to='message.Message', null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-time', '-id'],
|
||||
},
|
||||
bases=('submit.submissionevent',),
|
||||
),
|
||||
]
|
25
ietf/submit/migrations/0012_auto_20160414_1902.py
Normal file
25
ietf/submit/migrations/0012_auto_20160414_1902.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
def add_draft_submission_state_name(apps, schema_editor):
|
||||
# We can't import the model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
DraftSubmissionStateName = apps.get_model("name", "DraftSubmissionStateName")
|
||||
DraftSubmissionStateName.objects.create(slug="waiting-for-draft",
|
||||
name="Manual Post Waiting for Draft",
|
||||
desc="",
|
||||
used=True,
|
||||
order=8)
|
||||
|
||||
|
||||
dependencies = [
|
||||
('submit', '0011_submissionemail'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_draft_submission_state_name),
|
||||
]
|
39
ietf/submit/migrations/0013_auto_20160415_2120.py
Normal file
39
ietf/submit/migrations/0013_auto_20160415_2120.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import date
|
||||
|
||||
from django.db import migrations
|
||||
from ietf.submit.utils import remove_submission_files
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
def remove_old_submissions(apps, schema_editor):
|
||||
"""
|
||||
We'll remove any submissions awaiting manual post that are older
|
||||
than a date provided here.
|
||||
|
||||
These all showed up when we added the ability to list submissions
|
||||
awaiting manual post and go back many years
|
||||
"""
|
||||
|
||||
# We can't import the model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
before=date(2016, 3, 1)
|
||||
Submission = apps.get_model("submit", "Submission")
|
||||
DraftSubmissionStateName = apps.get_model("name", "DraftSubmissionStateName")
|
||||
|
||||
cancelled = DraftSubmissionStateName.objects.get(slug="cancel")
|
||||
for submission in Submission.objects.filter(state_id = "manual", submission_date__lt=before).distinct():
|
||||
submission.state = cancelled
|
||||
submission.save()
|
||||
|
||||
remove_submission_files(submission)
|
||||
|
||||
dependencies = [
|
||||
('submit', '0012_auto_20160414_1902'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_old_submissions),
|
||||
]
|
25
ietf/submit/migrations/0014_auto_20160627_1945.py
Normal file
25
ietf/submit/migrations/0014_auto_20160627_1945.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
def add_next_states(apps, schema_editor):
|
||||
DraftSubmissionStateName = apps.get_model("name", "DraftSubmissionStateName")
|
||||
|
||||
cancelled = DraftSubmissionStateName.objects.get(slug="cancel")
|
||||
posted = DraftSubmissionStateName.objects.get(slug="posted")
|
||||
mad = DraftSubmissionStateName.objects.get(slug="waiting-for-draft")
|
||||
|
||||
mad.next_states.add(cancelled)
|
||||
mad.next_states.add(posted)
|
||||
mad.save()
|
||||
|
||||
dependencies = [
|
||||
('submit', '0013_auto_20160415_2120'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_next_states),
|
||||
]
|
|
@ -7,6 +7,7 @@ import jsonfield
|
|||
from ietf.doc.models import Document
|
||||
from ietf.person.models import Person
|
||||
from ietf.group.models import Group
|
||||
from ietf.message.models import Message
|
||||
from ietf.name.models import DraftSubmissionStateName
|
||||
from ietf.utils.accesstoken import generate_random_key, generate_access_token
|
||||
|
||||
|
@ -106,3 +107,15 @@ class Preapproval(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class SubmissionEmailEvent(SubmissionEvent):
|
||||
message = models.ForeignKey(Message, null=True, blank=True,related_name='manualevents')
|
||||
msgtype = models.CharField(max_length=25)
|
||||
in_reply_to = models.ForeignKey(Message, null=True, blank=True,related_name='irtomanual')
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s %s by %s at %s" % (self.submission.name, self.desc, self.by.plain_name() if self.by else "(unknown)", self.time)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-time', '-id']
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ from tastypie.constants import ALL, ALL_WITH_RELATIONS
|
|||
from tastypie.cache import SimpleCache
|
||||
|
||||
from ietf import api
|
||||
|
||||
from ietf.submit.models import Preapproval, SubmissionCheck, Submission, SubmissionEvent
|
||||
|
||||
|
||||
from ietf.submit.models import ( Preapproval, SubmissionCheck, Submission,
|
||||
SubmissionEmailEvent, SubmissionEvent )
|
||||
from ietf.person.resources import PersonResource
|
||||
|
||||
|
||||
class PreapprovalResource(ModelResource):
|
||||
by = ToOneField(PersonResource, 'by')
|
||||
class Meta:
|
||||
|
@ -100,3 +100,31 @@ class SubmissionCheckResource(ModelResource):
|
|||
}
|
||||
api.submit.register(SubmissionCheckResource())
|
||||
|
||||
|
||||
|
||||
from ietf.person.resources import PersonResource
|
||||
from ietf.message.resources import MessageResource
|
||||
class SubmissionEmailEventResource(ModelResource):
|
||||
submission = ToOneField(SubmissionResource, 'submission')
|
||||
by = ToOneField(PersonResource, 'by', null=True)
|
||||
submissionevent_ptr = ToOneField(SubmissionEventResource, 'submissionevent_ptr')
|
||||
message = ToOneField(MessageResource, 'message', null=True)
|
||||
in_reply_to = ToOneField(MessageResource, 'in_reply_to', null=True)
|
||||
class Meta:
|
||||
queryset = SubmissionEmailEvent.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'submissionemailevent'
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"time": ALL,
|
||||
"desc": ALL,
|
||||
"msgtype": ALL,
|
||||
"submission": ALL_WITH_RELATIONS,
|
||||
"by": ALL_WITH_RELATIONS,
|
||||
"submissionevent_ptr": ALL_WITH_RELATIONS,
|
||||
"message": ALL_WITH_RELATIONS,
|
||||
"in_reply_to": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.submit.register(SubmissionEmailEventResource())
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import email
|
||||
|
||||
from StringIO import StringIO
|
||||
from pyquery import PyQuery
|
||||
|
||||
|
@ -9,17 +11,38 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, unicontent
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.submit.utils import expirable_submissions, expire_submission, ensure_person_email_info_exists
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.group.models import Group
|
||||
from ietf.doc.models import Document, DocAlias, DocEvent, State, BallotDocEvent, BallotPositionDocEvent, DocumentAuthor
|
||||
from ietf.submit.models import Submission, Preapproval
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import setup_default_community_list_for_group
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.message.models import Message
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.submit.models import Submission, Preapproval
|
||||
from ietf.submit.mail import add_submission_email, process_response_email
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, TestCase
|
||||
|
||||
|
||||
def submission_file(name, rev, group, format, templatename):
|
||||
# construct appropriate text draft
|
||||
f = open(os.path.join(settings.BASE_DIR, "submit", templatename))
|
||||
template = f.read()
|
||||
f.close()
|
||||
|
||||
submission_text = template % dict(
|
||||
date=datetime.date.today().strftime("%d %B %Y"),
|
||||
expiration=(datetime.date.today() + datetime.timedelta(days=100)).strftime("%d %B, %Y"),
|
||||
year=datetime.date.today().strftime("%Y"),
|
||||
month=datetime.date.today().strftime("%B"),
|
||||
name="%s-%s" % (name, rev),
|
||||
group=group or "",
|
||||
)
|
||||
|
||||
file = StringIO(str(submission_text))
|
||||
file.name = "%s-%s.%s" % (name, rev, format)
|
||||
return file
|
||||
|
||||
class SubmitTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -70,31 +93,12 @@ class SubmitTests(TestCase):
|
|||
settings.YANG_INVAL_MODEL_DIR = self.saved_yang_inval_model_dir
|
||||
|
||||
|
||||
def submission_file(self, name, rev, group, format, templatename):
|
||||
# construct appropriate text draft
|
||||
f = open(os.path.join(settings.BASE_DIR, "submit", templatename))
|
||||
template = f.read()
|
||||
f.close()
|
||||
|
||||
submission_text = template % dict(
|
||||
date=datetime.date.today().strftime("%d %B %Y"),
|
||||
expiration=(datetime.date.today() + datetime.timedelta(days=100)).strftime("%d %B, %Y"),
|
||||
year=datetime.date.today().strftime("%Y"),
|
||||
month=datetime.date.today().strftime("%B"),
|
||||
name="%s-%s" % (name, rev),
|
||||
group=group or "",
|
||||
)
|
||||
|
||||
file = StringIO(str(submission_text))
|
||||
file.name = "%s-%s.%s" % (name, rev, format)
|
||||
return file
|
||||
|
||||
def do_submission(self, name, rev, group=None, formats=["txt",]):
|
||||
# break early in case of missing configuration
|
||||
self.assertTrue(os.path.exists(settings.IDSUBMIT_IDNITS_BINARY))
|
||||
|
||||
# get
|
||||
url = urlreverse('submit_upload_submission')
|
||||
url = urlreverse('ietf.submit.views.upload_submission')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
@ -104,7 +108,7 @@ class SubmitTests(TestCase):
|
|||
# submit
|
||||
files = {}
|
||||
for format in formats:
|
||||
files[format] = self.submission_file(name, rev, group, format, "test_submission.%s" % format)
|
||||
files[format] = submission_file(name, rev, group, format, "test_submission.%s" % format)
|
||||
|
||||
r = self.client.post(url, files)
|
||||
if r.status_code != 302:
|
||||
|
@ -346,6 +350,12 @@ class SubmitTests(TestCase):
|
|||
r = self.client.post(confirm_url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
# check we have document events
|
||||
doc_events = draft.docevent_set.filter(type="added_comment")
|
||||
edescs = '::'.join([x.desc for x in doc_events])
|
||||
self.assertTrue('New version approved' in edescs)
|
||||
self.assertTrue('Uploaded new revision' in edescs)
|
||||
|
||||
draft = Document.objects.get(docalias__name=name)
|
||||
self.assertEqual(draft.rev, rev)
|
||||
self.assertEqual(draft.group.acronym, name.split("-")[2])
|
||||
|
@ -635,12 +645,12 @@ class SubmitTests(TestCase):
|
|||
self.do_submission(name, rev)
|
||||
|
||||
# search status page
|
||||
r = self.client.get(urlreverse("submit_search_submission"))
|
||||
r = self.client.get(urlreverse("ietf.submit.views.search_submission"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("submission status" in unicontent(r))
|
||||
|
||||
# search
|
||||
r = self.client.post(urlreverse("submit_search_submission"), dict(name=name))
|
||||
r = self.client.post(urlreverse("ietf.submit.views.search_submission"), dict(name=name))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
unprivileged_status_url = r['Location']
|
||||
|
||||
|
@ -680,7 +690,7 @@ class SubmitTests(TestCase):
|
|||
self.do_submission(name, rev)
|
||||
|
||||
submission = Submission.objects.get(name=name)
|
||||
url = urlreverse('submit_submission_status', kwargs=dict(submission_id=submission.pk))
|
||||
url = urlreverse('ietf.submit.views.submission_status', kwargs=dict(submission_id=submission.pk))
|
||||
|
||||
# check we got request full URL button
|
||||
r = self.client.get(url)
|
||||
|
@ -752,17 +762,17 @@ class SubmitTests(TestCase):
|
|||
self.assertEqual(s.state_id, "cancel")
|
||||
|
||||
def test_help_pages(self):
|
||||
r = self.client.get(urlreverse("submit_note_well"))
|
||||
r = self.client.get(urlreverse("ietf.submit.views.note_well"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("submit_tool_instructions"))
|
||||
r = self.client.get(urlreverse("ietf.submit.views.tool_instructions"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
def test_blackout_access(self):
|
||||
make_test_data()
|
||||
|
||||
# get
|
||||
url = urlreverse('submit_upload_submission')
|
||||
url = urlreverse('ietf.submit.views.upload_submission')
|
||||
# set meeting to today so we're in blackout period
|
||||
meeting = Meeting.get_current_meeting()
|
||||
meeting.date = datetime.datetime.utcnow()
|
||||
|
@ -792,7 +802,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue(os.path.exists(settings.IDSUBMIT_IDNITS_BINARY))
|
||||
|
||||
# get
|
||||
url = urlreverse('submit_upload_submission')
|
||||
url = urlreverse('ietf.submit.views.upload_submission')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
@ -800,7 +810,7 @@ class SubmitTests(TestCase):
|
|||
# submit
|
||||
files = {}
|
||||
for format in formats:
|
||||
files[format] = self.submission_file(name, rev, group, "bad", "test_submission.bad")
|
||||
files[format] = submission_file(name, rev, group, "bad", "test_submission.bad")
|
||||
|
||||
r = self.client.post(url, files)
|
||||
|
||||
|
@ -840,7 +850,7 @@ class ApprovalsTestCase(TestCase):
|
|||
def test_approvals(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('submit_approvals')
|
||||
url = urlreverse('ietf.submit.views.approvals')
|
||||
self.client.login(username="marschairman", password="marschairman+password")
|
||||
|
||||
Preapproval.objects.create(name="draft-ietf-mars-foo", by=Person.objects.get(user__username="marschairman"))
|
||||
|
@ -871,7 +881,7 @@ class ApprovalsTestCase(TestCase):
|
|||
def test_add_preapproval(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('submit_add_preapproval')
|
||||
url = urlreverse('ietf.submit.views.add_preapproval')
|
||||
login_testing_unauthorized(self, "marschairman", url)
|
||||
|
||||
# get
|
||||
|
@ -898,7 +908,7 @@ class ApprovalsTestCase(TestCase):
|
|||
|
||||
preapproval = Preapproval.objects.create(name="draft-ietf-mars-foo", by=Person.objects.get(user__username="marschairman"))
|
||||
|
||||
url = urlreverse('submit_cancel_preapproval', kwargs=dict(preapproval_id=preapproval.pk))
|
||||
url = urlreverse('ietf.submit.views.cancel_preapproval', kwargs=dict(preapproval_id=preapproval.pk))
|
||||
login_testing_unauthorized(self, "marschairman", url)
|
||||
|
||||
# get
|
||||
|
@ -912,3 +922,450 @@ class ApprovalsTestCase(TestCase):
|
|||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
self.assertEqual(len(Preapproval.objects.filter(name=preapproval.name)), 0)
|
||||
|
||||
class ManualPostsTestCase(TestCase):
|
||||
def test_manual_posts(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('ietf.submit.views.manualpost')
|
||||
# Secretariat has access
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
Submission.objects.create(name="draft-ietf-mars-foo",
|
||||
group=Group.objects.get(acronym="mars"),
|
||||
submission_date=datetime.date.today(),
|
||||
state_id="manual")
|
||||
Submission.objects.create(name="draft-ietf-mars-bar",
|
||||
group=Group.objects.get(acronym="mars"),
|
||||
submission_date=datetime.date.today(),
|
||||
rev="00",
|
||||
state_id="grp-appr")
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
self.assertEqual(len(q('.submissions a:contains("draft-ietf-mars-foo")')), 1)
|
||||
self.assertEqual(len(q('.submissions a:contains("draft-ietf-mars-bar")')), 0)
|
||||
|
||||
def test_waiting_for_draft(self):
|
||||
message_string = """To: somebody@ietf.org
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: test submission via email
|
||||
|
||||
Please submit my draft at http://test.com/mydraft.txt
|
||||
|
||||
Thank you
|
||||
""".format(datetime.datetime.now().ctime())
|
||||
message = email.message_from_string(message_string)
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin") )
|
||||
|
||||
url = urlreverse('ietf.submit.views.manualpost')
|
||||
# Secretariat has access
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
self.assertEqual(len(q('.waiting-for-draft a:contains("draft-my-new-draft")')), 1)
|
||||
|
||||
# Same name should raise an error
|
||||
with self.assertRaises(Exception):
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin")
|
||||
|
||||
# Cancel this one
|
||||
r = self.client.post(urlreverse("ietf.submit.views.cancel_waiting_for_draft"), {
|
||||
"submission_id": submission.pk,
|
||||
"access_token": submission.access_token(),
|
||||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
url = r["Location"]
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('.waiting-for-draft a:contains("draft-my-new-draft")')), 0)
|
||||
|
||||
# Should now be able to add it again
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin") )
|
||||
|
||||
|
||||
def test_waiting_for_draft_with_attachment(self):
|
||||
frm = "joe@test.com"
|
||||
|
||||
message_string = """To: somebody@ietf.org
|
||||
From: {}
|
||||
Date: {}
|
||||
Subject: A very important message with a small attachment
|
||||
Content-Type: multipart/mixed; boundary="------------090908050800030909090207"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
The message body will probably say something about the attached document
|
||||
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=UTF-8; name="attach.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="attach.txt"
|
||||
|
||||
QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs
|
||||
ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
|
||||
--------------090908050800030909090207--
|
||||
""".format(frm, datetime.datetime.now().ctime())
|
||||
|
||||
message = email.message_from_string(message_string)
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin") )
|
||||
|
||||
manualpost_page_url = urlreverse('ietf.submit.views.manualpost')
|
||||
# Secretariat has access
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
self.check_manualpost_page(submission=submission,
|
||||
submission_email_event=submission_email_event,
|
||||
the_url=manualpost_page_url,
|
||||
submission_name_fragment='draft-my-new-draft',
|
||||
frm=frm,
|
||||
is_secretariat=True)
|
||||
|
||||
# Try the status page with no credentials
|
||||
self.client.logout()
|
||||
|
||||
self.check_manualpost_page(submission=submission,
|
||||
submission_email_event=submission_email_event,
|
||||
the_url=manualpost_page_url,
|
||||
submission_name_fragment='draft-my-new-draft',
|
||||
frm=frm,
|
||||
is_secretariat=False)
|
||||
|
||||
# Post another message to this submission using the link
|
||||
message_string = """To: somebody@ietf.org
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: A new submission message with a small attachment
|
||||
Content-Type: multipart/mixed; boundary="------------090908050800030909090207"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
The message body will probably say something more about the attached document
|
||||
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=UTF-8; name="attach.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="attachment.txt"
|
||||
|
||||
QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs
|
||||
ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
|
||||
--------------090908050800030909090207--
|
||||
""".format(datetime.datetime.now().ctime())
|
||||
|
||||
# Back to secretariat
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
r, q = self.request_and_parse(manualpost_page_url)
|
||||
|
||||
url = self.get_href(q, "a#new-submission-email:contains('New submission from email')")
|
||||
|
||||
# Get the form
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
#self.assertEqual(len(q('input[name=edit-title]')), 1)
|
||||
|
||||
# Post the new message
|
||||
r = self.client.post(url, {
|
||||
"name": "draft-my-next-new-draft-00",
|
||||
"direction": "incoming",
|
||||
"message": message_string,
|
||||
})
|
||||
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print q
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
#self.check_manualpost_page(submission, submission_email_event,
|
||||
# url, 'draft-my-next-new-draft'
|
||||
# 'Another very important message',
|
||||
# true)
|
||||
|
||||
def check_manualpost_page(self, submission, submission_email_event,
|
||||
the_url, submission_name_fragment,
|
||||
frm,
|
||||
is_secretariat):
|
||||
# get the page listing manual posts
|
||||
r, q = self.request_and_parse(the_url)
|
||||
selector = "#waiting-for-draft a#add-submission-email%s:contains('Add email')" % submission.pk
|
||||
|
||||
if is_secretariat:
|
||||
# Can add an email to the submission
|
||||
add_email_url = self.get_href(q, selector)
|
||||
else:
|
||||
# No add email button button
|
||||
self.assertEqual(len(q(selector)), 0)
|
||||
|
||||
# Find the link for our submission in those awaiting drafts
|
||||
submission_url = self.get_href(q, "#waiting-for-draft a#aw{}:contains({})".
|
||||
format(submission.pk, submission_name_fragment))
|
||||
|
||||
# Follow the link to the status page for this submission
|
||||
r, q = self.request_and_parse(submission_url)
|
||||
|
||||
selector = "#history a#reply%s:contains('Reply')" % submission.pk
|
||||
|
||||
if is_secretariat:
|
||||
# check that reply button is visible and get the form
|
||||
reply_url = self.get_href(q, selector)
|
||||
|
||||
# Get the form
|
||||
r = self.client.get(reply_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
reply_q = PyQuery(r.content)
|
||||
self.assertEqual(len(reply_q('input[name=to]')), 1)
|
||||
else:
|
||||
# No reply button
|
||||
self.assertEqual(len(q(selector)), 0)
|
||||
|
||||
if is_secretariat:
|
||||
# Now try to send an email using the send email link
|
||||
|
||||
selector = "a#send%s:contains('Send Email')" % submission.pk
|
||||
send_url = self.get_href(q, selector)
|
||||
|
||||
self.do_submission_email(the_url = send_url,
|
||||
to = frm,
|
||||
body = "A new message")
|
||||
|
||||
# print q
|
||||
# print submission.pk
|
||||
# print submission_email_event.pk
|
||||
|
||||
# Find the link for our message in the list
|
||||
url = self.get_href(q, "#aw{}-{}:contains('{}')".format(submission.pk,
|
||||
submission_email_event.message.pk,
|
||||
"Received message - manual post"))
|
||||
|
||||
# Page displaying message details
|
||||
r, q = self.request_and_parse(url)
|
||||
|
||||
if is_secretariat:
|
||||
# check that reply button is visible
|
||||
|
||||
reply_href = self.get_href(q, "#email-details a#reply%s:contains('Reply')" % submission.pk)
|
||||
|
||||
else:
|
||||
# No reply button
|
||||
self.assertEqual(len(q(selector)), 0)
|
||||
reply_href = None
|
||||
|
||||
# check that attachment link is visible
|
||||
|
||||
url = self.get_href(q, "#email-details a#attach{}:contains('attach.txt')".format(submission.pk))
|
||||
|
||||
# Fetch the attachment
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Attempt a reply if we can
|
||||
if reply_href == None:
|
||||
return
|
||||
|
||||
self.do_submission_email(the_url = reply_href,
|
||||
to = frm,
|
||||
body = "A reply to the message")
|
||||
|
||||
# try adding an email to the submission
|
||||
# Use the add email link from the manual post listing page
|
||||
|
||||
if is_secretariat:
|
||||
# Can add an email to the submission
|
||||
# add_email_url set previously
|
||||
r = self.client.get(add_email_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
add_email_q = PyQuery(r.content)
|
||||
self.assertEqual(len(add_email_q('input[name=submission_pk]')), 1)
|
||||
|
||||
# Add a simple email
|
||||
new_message_string = """To: somebody@ietf.org
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: Another message
|
||||
|
||||
About my submission
|
||||
|
||||
Thank you
|
||||
""".format(datetime.datetime.now().ctime())
|
||||
|
||||
r = self.client.post(add_email_url, {
|
||||
"name": "{}-{}".format(submission.name, submission.rev),
|
||||
"direction": "incoming",
|
||||
"submission_pk": submission.pk,
|
||||
"message": new_message_string,
|
||||
})
|
||||
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print q
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
def request_and_parse(self, url):
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return r, PyQuery(r.content)
|
||||
|
||||
|
||||
def get_href(self, q, query):
|
||||
link = q(query)
|
||||
self.assertEqual(len(link), 1)
|
||||
|
||||
return PyQuery(link[0]).attr('href')
|
||||
|
||||
|
||||
def do_submission_email(self, the_url, to, body):
|
||||
# check the page
|
||||
r = self.client.get(the_url)
|
||||
q = PyQuery(r.content)
|
||||
post_button = q('[type=submit]:contains("Send Email")')
|
||||
self.assertEqual(len(post_button), 1)
|
||||
action = post_button.parents("form").find('input[type=hidden][name="action"]').val()
|
||||
subject = post_button.parents("form").find('input[name="subject"]').val()
|
||||
frm = post_button.parents("form").find('input[name="frm"]').val()
|
||||
cc = post_button.parents("form").find('input[name="cc"]').val()
|
||||
reply_to = post_button.parents("form").find('input[name="reply_to"]').val()
|
||||
|
||||
empty_outbox()
|
||||
|
||||
# post submitter info
|
||||
r = self.client.post(the_url, {
|
||||
"action": action,
|
||||
"subject": subject,
|
||||
"frm": frm,
|
||||
"to": to,
|
||||
"cc": cc,
|
||||
"reply_to": reply_to,
|
||||
"body": body,
|
||||
})
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
self.assertEqual(len(outbox), 1)
|
||||
|
||||
outmsg = outbox[0]
|
||||
self.assertTrue(to in outmsg['To'])
|
||||
|
||||
reply_to = outmsg['Reply-to']
|
||||
self.assertIsNotNone(reply_to, "Expected Reply-to")
|
||||
|
||||
# Build a reply
|
||||
|
||||
message_string = """To: {}
|
||||
From: {}
|
||||
Date: {}
|
||||
Subject: test
|
||||
""".format(reply_to, to, datetime.datetime.now().ctime())
|
||||
|
||||
result = process_response_email(message_string)
|
||||
self.assertIsInstance(result, Message)
|
||||
|
||||
return r
|
||||
|
||||
def do_submission(self, name, rev, group=None, formats=["txt",]):
|
||||
# We're not testing the submission process - just the submission status
|
||||
|
||||
# get
|
||||
url = urlreverse('ietf.submit.views.upload_submission')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('input[type=file][name=txt]')), 1)
|
||||
self.assertEqual(len(q('input[type=file][name=xml]')), 1)
|
||||
|
||||
# submit
|
||||
files = {}
|
||||
for format in formats:
|
||||
files[format] = submission_file(name, rev, group, format, "test_submission.%s" % format)
|
||||
|
||||
r = self.client.post(url, files)
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print(q('div.has-error span.help-block div').text)
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
status_url = r["Location"]
|
||||
for format in formats:
|
||||
self.assertTrue(os.path.exists(os.path.join(self.staging_dir, u"%s-%s.%s" % (name, rev, format))))
|
||||
self.assertEqual(Submission.objects.filter(name=name).count(), 1)
|
||||
submission = Submission.objects.get(name=name)
|
||||
self.assertTrue(all([ c.passed!=False for c in submission.checks.all() ]))
|
||||
self.assertEqual(len(submission.authors_parsed()), 1)
|
||||
author = submission.authors_parsed()[0]
|
||||
self.assertEqual(author["name"], "Author Name")
|
||||
self.assertEqual(author["email"], "author@example.com")
|
||||
|
||||
return status_url
|
||||
|
||||
|
||||
def supply_extra_metadata(self, name, status_url, submitter_name, submitter_email):
|
||||
# check the page
|
||||
r = self.client.get(status_url)
|
||||
q = PyQuery(r.content)
|
||||
post_button = q('[type=submit]:contains("Post")')
|
||||
self.assertEqual(len(post_button), 1)
|
||||
action = post_button.parents("form").find('input[type=hidden][name="action"]').val()
|
||||
|
||||
# post submitter info
|
||||
r = self.client.post(status_url, {
|
||||
"action": action,
|
||||
"submitter-name": submitter_name,
|
||||
"submitter-email": submitter_email,
|
||||
"approvals_received": True,
|
||||
})
|
||||
|
||||
if r.status_code == 302:
|
||||
submission = Submission.objects.get(name=name)
|
||||
self.assertEqual(submission.submitter, u"%s <%s>" % (submitter_name, submitter_email))
|
||||
|
||||
return r
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import patterns
|
||||
|
||||
from ietf.submit import views
|
||||
|
||||
urlpatterns = patterns('ietf.submit.views',
|
||||
url(r'^$', 'upload_submission', name='submit_upload_submission'),
|
||||
url(r'^status/$', 'search_submission', name='submit_search_submission'),
|
||||
url(r'^status/(?P<submission_id>\d+)/$', 'submission_status', name='submit_submission_status'),
|
||||
url(r'^status/(?P<submission_id>\d+)/edit/$', 'edit_submission', name='submit_edit_submission'),
|
||||
url(r'^status/(?P<submission_id>\d+)/confirm/(?P<auth_token>[a-f\d]+)/$', 'confirm_submission', name='submit_confirm_submission'),
|
||||
url(r'^status/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]*)/$', 'submission_status', name='submit_submission_status_by_hash'),
|
||||
url(r'^status/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]+)/edit/$', 'edit_submission', name='submit_edit_submission_by_hash'),
|
||||
url(r'^note-well/$', 'note_well', name='submit_note_well'),
|
||||
url(r'^tool-instructions/$', 'tool_instructions', name='submit_tool_instructions'),
|
||||
(r'^$', views.upload_submission),
|
||||
(r'^status/$', views.search_submission),
|
||||
(r'^status/(?P<submission_id>\d+)/$', views.submission_status),
|
||||
(r'^status/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.submission_status),
|
||||
(r'^status/(?P<submission_id>\d+)/confirm/(?P<auth_token>[a-f\d]+)/$', views.confirm_submission),
|
||||
(r'^status/(?P<submission_id>\d+)/edit/$', views.edit_submission),
|
||||
(r'^status/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]+)/edit/$', views.edit_submission),
|
||||
(r'^note-well/$', views.note_well),
|
||||
(r'^tool-instructions/$', views.tool_instructions),
|
||||
|
||||
url(r'^approvals/$', 'approvals', name='submit_approvals'),
|
||||
url(r'^approvals/addpreapproval/$', 'add_preapproval', name='submit_add_preapproval'),
|
||||
url(r'^approvals/cancelpreapproval/(?P<preapproval_id>[a-f\d]+)/$', 'cancel_preapproval', name='submit_cancel_preapproval'),
|
||||
(r'^approvals/$', views.approvals),
|
||||
(r'^approvals/addpreapproval/$', views.add_preapproval),
|
||||
(r'^approvals/cancelpreapproval/(?P<preapproval_id>[a-f\d]+)/$', views.cancel_preapproval),
|
||||
|
||||
(r'^manualpost/$', views.manualpost),
|
||||
(r'^manualpost/addemail$', views.add_manualpost_email),
|
||||
(r'^manualpost/addemail/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.add_manualpost_email),
|
||||
(r'^manualpost/attachment/(?P<submission_id>\d+)/(?P<message_id>\d+)/(?P<filename>.*)$', views.show_submission_email_attachment),
|
||||
(r'^manualpost/cancel$', views.cancel_waiting_for_draft),
|
||||
(r'^manualpost/email/(?P<submission_id>\d+)/(?P<message_id>\d+)/$', views.show_submission_email_message),
|
||||
(r'^manualpost/email/(?P<submission_id>\d+)/(?P<message_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.show_submission_email_message),
|
||||
(r'^manualpost/replyemail/(?P<submission_id>\d+)/(?P<message_id>\d+)/$', views.send_submission_email),
|
||||
(r'^manualpost/sendemail/(?P<submission_id>\d+)/$', views.send_submission_email),
|
||||
)
|
||||
|
|
|
@ -3,7 +3,8 @@ import datetime
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.doc.models import Document, State, DocAlias, DocEvent, DocumentAuthor
|
||||
from ietf.doc.models import ( Document, State, DocAlias, DocEvent,
|
||||
DocumentAuthor, AddedMessageEvent )
|
||||
from ietf.doc.models import NewRevisionDocEvent
|
||||
from ietf.doc.models import RelatedDocument, DocRelationshipName
|
||||
from ietf.doc.utils import add_state_change_event, rebuild_reference_relations
|
||||
|
@ -19,6 +20,7 @@ from ietf.submit.models import Submission, SubmissionEvent, Preapproval, DraftSu
|
|||
from ietf.utils import unaccent
|
||||
from ietf.utils.log import log
|
||||
|
||||
|
||||
def validate_submission(submission):
|
||||
errors = {}
|
||||
|
||||
|
@ -108,8 +110,62 @@ def create_submission_event(request, submission, desc):
|
|||
SubmissionEvent.objects.create(submission=submission, by=by, desc=desc)
|
||||
|
||||
|
||||
def post_submission(request, submission):
|
||||
# find out who did it
|
||||
def docevent_from_submission(request, submission, desc):
|
||||
system = Person.objects.get(name="(System)")
|
||||
|
||||
try:
|
||||
draft = Document.objects.get(name=submission.name)
|
||||
except Document.DoesNotExist:
|
||||
# Assume this is revision 00 - we'll do this later
|
||||
return
|
||||
|
||||
submitter_parsed = submission.submitter_parsed()
|
||||
if submitter_parsed["name"] and submitter_parsed["email"]:
|
||||
submitter = ensure_person_email_info_exists(submitter_parsed["name"], submitter_parsed["email"]).person
|
||||
else:
|
||||
submitter = system
|
||||
|
||||
e = DocEvent(doc=draft)
|
||||
e.by = submitter
|
||||
e.type = "added_comment"
|
||||
e.desc = desc
|
||||
e.save()
|
||||
|
||||
|
||||
def post_rev00_submission_events(draft, submission, submitter):
|
||||
# Add previous submission events as docevents
|
||||
# For now we'll filter based on the description
|
||||
for subevent in submission.submissionevent_set.all():
|
||||
desc = subevent.desc
|
||||
if desc.startswith("Uploaded submission"):
|
||||
desc = "Uploaded new revision"
|
||||
e = DocEvent(type="added_comment", doc=draft)
|
||||
elif desc.startswith("Submission created"):
|
||||
e = DocEvent(type="added_comment", doc=draft)
|
||||
elif desc.startswith("Set submitter to"):
|
||||
pos = subevent.desc.find("sent confirmation email")
|
||||
e = DocEvent(type="added_comment", doc=draft)
|
||||
if pos > 0:
|
||||
desc = "Request for posting confirmation emailed %s" % (subevent.desc[pos + 23:])
|
||||
else:
|
||||
pos = subevent.desc.find("sent appproval email")
|
||||
if pos > 0:
|
||||
desc = "Request for posting approval emailed %s" % (subevent.desc[pos + 19:])
|
||||
elif desc.startswith("Received message") or desc.startswith("Sent message"):
|
||||
e = AddedMessageEvent(type="added_message", doc=draft)
|
||||
e.message = subevent.submissionemailevent.message
|
||||
e.msgtype = subevent.submissionemailevent.msgtype
|
||||
e.in_reply_to = subevent.submissionemailevent.in_reply_to
|
||||
else:
|
||||
continue
|
||||
|
||||
e.time = subevent.time #submission.submission_date
|
||||
e.by = submitter
|
||||
e.desc = desc
|
||||
e.save()
|
||||
|
||||
|
||||
def post_submission(request, submission, approvedDesc):
|
||||
system = Person.objects.get(name="(System)")
|
||||
submitter_parsed = submission.submitter_parsed()
|
||||
if submitter_parsed["name"] and submitter_parsed["email"]:
|
||||
|
@ -175,8 +231,29 @@ def post_submission(request, submission):
|
|||
trouble = rebuild_reference_relations(draft, filename=os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (submission.name, submission.rev)))
|
||||
if trouble:
|
||||
log('Rebuild_reference_relations trouble: %s'%trouble)
|
||||
|
||||
if draft.rev == '00':
|
||||
# Add all the previous submission events as docevents
|
||||
post_rev00_submission_events(draft, submission, submitter)
|
||||
|
||||
# Add an approval docevent
|
||||
e = DocEvent(type="added_comment", doc=draft)
|
||||
e.time = draft.time #submission.submission_date
|
||||
e.by = submitter
|
||||
e.desc = approvedDesc
|
||||
e.save()
|
||||
|
||||
# new revision event
|
||||
e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev)
|
||||
e.time = draft.time #submission.submission_date
|
||||
e.by = submitter
|
||||
e.desc = "New version available: <b>%s-%s.txt</b>" % (draft.name, draft.rev)
|
||||
e.save()
|
||||
|
||||
if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
|
||||
# automatically set state "WG Document"
|
||||
draft.set_state(State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
|
||||
|
||||
# automatic state changes
|
||||
if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"):
|
||||
prev_state = draft.get_state("draft-iana-review")
|
||||
next_state = State.objects.get(used=True, type="draft-iana-review", slug="changed")
|
||||
|
@ -435,4 +512,4 @@ def expire_submission(submission, by):
|
|||
submission.state_id = "cancel"
|
||||
submission.save()
|
||||
|
||||
SubmissionEvent.objects.create(submission=submission, by=by, desc="Canceled expired submission")
|
||||
SubmissionEvent.objects.create(submission=submission, by=by, desc="Cancelled expired submission")
|
||||
|
|
|
@ -1,31 +1,40 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
import base64
|
||||
import datetime
|
||||
import os
|
||||
import xml2rfc
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.core.validators import validate_email, ValidationError
|
||||
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden
|
||||
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import Document, DocAlias
|
||||
from ietf.doc.models import Document, DocAlias, AddedMessageEvent
|
||||
from ietf.doc.utils import prettify_std_name
|
||||
from ietf.group.models import Group
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.submit.forms import SubmissionUploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm, ReplacesForm
|
||||
from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, send_manual_post_request
|
||||
from ietf.submit.models import Submission, SubmissionCheck, Preapproval, DraftSubmissionStateName
|
||||
from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.message.models import Message, MessageAttachment
|
||||
from ietf.submit.forms import ( SubmissionUploadForm, NameEmailForm, EditSubmissionForm,
|
||||
PreapprovalForm, ReplacesForm, SubmissionEmailForm, MessageModelForm )
|
||||
from ietf.submit.mail import ( send_full_url, send_approval_request_to_group,
|
||||
send_submission_confirmation, send_manual_post_request, add_submission_email, get_reply_to )
|
||||
from ietf.submit.models import (Submission, SubmissionCheck, Preapproval,
|
||||
DraftSubmissionStateName, SubmissionEmailEvent )
|
||||
from ietf.submit.utils import ( approvable_submissions_for_user, preapprovals_for_user,
|
||||
recently_approved_by_user )
|
||||
from ietf.submit.utils import validate_submission, create_submission_event
|
||||
from ietf.submit.utils import docevent_from_submission
|
||||
from ietf.submit.utils import post_submission, cancel_submission, rename_submission_files
|
||||
from ietf.utils.accesstoken import generate_random_key, generate_access_token
|
||||
from ietf.utils.draft import Draft
|
||||
from ietf.utils.log import log
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.utils.mail import send_mail_message
|
||||
|
||||
|
||||
def upload_submission(request):
|
||||
|
@ -93,29 +102,59 @@ def upload_submission(request):
|
|||
else:
|
||||
abstract = form.parsed_draft.get_abstract()
|
||||
|
||||
# save submission
|
||||
try:
|
||||
submission = Submission.objects.create(
|
||||
state=DraftSubmissionStateName.objects.get(slug="uploaded"),
|
||||
remote_ip=form.remote_ip,
|
||||
name=form.filename,
|
||||
group=form.group,
|
||||
title=form.title,
|
||||
abstract=abstract,
|
||||
rev=form.revision,
|
||||
pages=form.parsed_draft.get_pagecount(),
|
||||
authors="\n".join(authors),
|
||||
note="",
|
||||
first_two_pages=''.join(form.parsed_draft.pages[:2]),
|
||||
file_size=file_size,
|
||||
file_types=','.join(form.file_types),
|
||||
submission_date=datetime.date.today(),
|
||||
document_date=form.parsed_draft.get_creation_date(),
|
||||
replaces="",
|
||||
# See if there is a Submission in state waiting-for-draft
|
||||
# for this revision.
|
||||
# If so - we're going to update it otherwise we create a new object
|
||||
|
||||
submission = Submission.objects.filter(name=form.filename,
|
||||
rev=form.revision,
|
||||
state_id = "waiting-for-draft").distinct()
|
||||
if (len(submission) == 0):
|
||||
submission = None
|
||||
elif (len(submission) == 1):
|
||||
submission = submission[0]
|
||||
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="uploaded")
|
||||
submission.remote_ip=form.remote_ip
|
||||
submission.title=form.title
|
||||
submission.abstract=abstract
|
||||
submission.rev=form.revision
|
||||
submission.pages=form.parsed_draft.get_pagecount()
|
||||
submission.authors="\n".join(authors)
|
||||
submission.first_two_pages=''.join(form.parsed_draft.pages[:2])
|
||||
submission.file_size=file_size
|
||||
submission.file_types=','.join(form.file_types)
|
||||
submission.submission_date=datetime.date.today()
|
||||
submission.document_date=form.parsed_draft.get_creation_date()
|
||||
submission.replaces=""
|
||||
|
||||
submission.save()
|
||||
else:
|
||||
raise Exception("Multiple submissions found waiting for upload")
|
||||
|
||||
if (submission == None):
|
||||
try:
|
||||
submission = Submission.objects.create(
|
||||
state=DraftSubmissionStateName.objects.get(slug="uploaded"),
|
||||
remote_ip=form.remote_ip,
|
||||
name=form.filename,
|
||||
group=form.group,
|
||||
title=form.title,
|
||||
abstract=abstract,
|
||||
rev=form.revision,
|
||||
pages=form.parsed_draft.get_pagecount(),
|
||||
authors="\n".join(authors),
|
||||
note="",
|
||||
first_two_pages=''.join(form.parsed_draft.pages[:2]),
|
||||
file_size=file_size,
|
||||
file_types=','.join(form.file_types),
|
||||
submission_date=datetime.date.today(),
|
||||
document_date=form.parsed_draft.get_creation_date(),
|
||||
replaces="",
|
||||
)
|
||||
except Exception as e:
|
||||
log("Exception: %s\n" % e)
|
||||
raise
|
||||
except Exception as e:
|
||||
log("Exception: %s\n" % e)
|
||||
raise
|
||||
|
||||
# run submission checkers
|
||||
def apply_check(submission, checker, method, fn):
|
||||
|
@ -135,8 +174,9 @@ def upload_submission(request):
|
|||
break
|
||||
|
||||
create_submission_event(request, submission, desc="Uploaded submission")
|
||||
docevent_from_submission(request, submission, desc="Uploaded new revision")
|
||||
|
||||
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_token=submission.access_token())
|
||||
return redirect("ietf.submit.views.submission_status", submission_id=submission.pk, access_token=submission.access_token())
|
||||
except IOError as e:
|
||||
if "read error" in str(e): # The server got an IOError when trying to read POST data
|
||||
form = SubmissionUploadForm(request=request)
|
||||
|
@ -197,9 +237,14 @@ def submission_status(request, submission_id, access_token=None):
|
|||
can_edit = can_edit_submission(request.user, submission, access_token) 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")
|
||||
|
||||
can_force_post = (
|
||||
is_secretariat
|
||||
and submission.state.next_states.filter(slug="posted").exists()
|
||||
and submission.state_id != "waiting-for-draft")
|
||||
show_send_full_url = (
|
||||
not key_matched
|
||||
and not is_secretariat
|
||||
and not submission.state_id in ("cancel", "posted") )
|
||||
addrs = gather_address_lists('sub_confirmation_requested',submission=submission)
|
||||
confirmation_list = addrs.to
|
||||
confirmation_list.extend(addrs.cc)
|
||||
|
@ -218,7 +263,7 @@ def submission_status(request, submission_id, access_token=None):
|
|||
message = None
|
||||
|
||||
if submission.state_id == "cancel":
|
||||
message = ('error', 'This submission has been canceled, modification is no longer possible.')
|
||||
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":
|
||||
|
@ -244,45 +289,60 @@ def submission_status(request, submission_id, access_token=None):
|
|||
replaces = replaces_form.cleaned_data.get("replaces", [])
|
||||
submission.replaces = ",".join(o.name for o in replaces)
|
||||
|
||||
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)
|
||||
approvals_received = submitter_form.cleaned_data['approvals_received']
|
||||
|
||||
if approvals_received:
|
||||
if not is_secretariat:
|
||||
return HttpResponseForbidden('You do not have permission to perform this action')
|
||||
|
||||
# go directly to posting submission
|
||||
desc = u"Secretariat manually posting. Approvals already received"
|
||||
post_submission(request, submission, desc)
|
||||
create_submission_event(request, submission, desc)
|
||||
else:
|
||||
submission.auth_key = generate_random_key()
|
||||
if requires_prev_authors_approval:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="aut-appr")
|
||||
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)
|
||||
docDesc = u"Request for posting approval emailed to group chairs: %s" % u", ".join(sent_to)
|
||||
|
||||
else:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="auth")
|
||||
submission.save()
|
||||
submission.auth_key = generate_random_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, chair_notice=group_authors_changed)
|
||||
|
||||
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)
|
||||
|
||||
msg = u"Set submitter to \"%s\", replaces to %s and %s" % (
|
||||
submission.submitter,
|
||||
", ".join(prettify_std_name(r.name) for r in replaces) if replaces else "(none)",
|
||||
desc)
|
||||
create_submission_event(request, submission, msg)
|
||||
sent_to = send_submission_confirmation(request, submission, chair_notice=group_authors_changed)
|
||||
|
||||
if submission.state_id == "aut-appr":
|
||||
desc = u"sent confirmation email to previous authors: %s" % u", ".join(sent_to)
|
||||
docDesc = "Request for posting confirmation emailed to previous authors: %s" % u", ".join(sent_to)
|
||||
else:
|
||||
desc = u"sent confirmation email to submitter and authors: %s" % u", ".join(sent_to)
|
||||
docDesc = "Request for posting confirmation emailed to submitter and authors: %s" % u", ".join(sent_to)
|
||||
|
||||
msg = u"Set submitter to \"%s\", replaces to %s and %s" % (
|
||||
submission.submitter,
|
||||
", ".join(prettify_std_name(r.name) for r in replaces) if replaces else "(none)",
|
||||
desc)
|
||||
create_submission_event(request, submission, msg)
|
||||
docevent_from_submission(request, submission, docDesc)
|
||||
|
||||
if access_token:
|
||||
return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_token=access_token)
|
||||
return redirect("ietf.submit.views.submission_status", submission_id=submission.pk, access_token=access_token)
|
||||
else:
|
||||
return redirect("submit_submission_status", submission_id=submission.pk)
|
||||
return redirect("ietf.submit.views.submission_status", submission_id=submission.pk)
|
||||
|
||||
elif action == "edit" and submission.state_id == "uploaded":
|
||||
if access_token:
|
||||
return redirect("submit_edit_submission_by_hash", submission_id=submission.pk, access_token=access_token)
|
||||
return redirect("ietf.submit.views.edit_submission", submission_id=submission.pk, access_token=access_token)
|
||||
else:
|
||||
return redirect("submit_edit_submission", submission_id=submission.pk)
|
||||
return redirect("ietf.submit.views.edit_submission", submission_id=submission.pk)
|
||||
|
||||
elif action == "sendfullurl" and submission.state_id not in ("cancel", "posted"):
|
||||
sent_to = send_full_url(request, submission)
|
||||
|
@ -297,16 +357,16 @@ def submission_status(request, submission_id, access_token=None):
|
|||
|
||||
cancel_submission(submission)
|
||||
|
||||
create_submission_event(request, submission, "Canceled submission")
|
||||
create_submission_event(request, submission, "Cancelled submission")
|
||||
|
||||
return redirect("submit_submission_status", submission_id=submission_id)
|
||||
return redirect("ietf.submit.views.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)
|
||||
post_submission(request, submission, "WG -00 approved")
|
||||
|
||||
create_submission_event(request, submission, "Approved and posted submission")
|
||||
|
||||
|
@ -317,13 +377,13 @@ def submission_status(request, submission_id, access_token=None):
|
|||
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"
|
||||
|
||||
post_submission(request, submission, desc)
|
||||
|
||||
create_submission_event(request, submission, desc)
|
||||
|
||||
return redirect("doc_view", name=submission.name)
|
||||
|
@ -409,7 +469,7 @@ def edit_submission(request, submission_id, access_token=None):
|
|||
|
||||
create_submission_event(request, submission, desc)
|
||||
|
||||
return redirect("submit_submission_status", submission_id=submission.pk)
|
||||
return redirect("ietf.submit.views.submission_status", submission_id=submission.pk)
|
||||
else:
|
||||
form_errors = True
|
||||
else:
|
||||
|
@ -439,7 +499,16 @@ def confirm_submission(request, submission_id, auth_token):
|
|||
if not key_matched: key_matched = auth_token == submission.auth_key # backwards-compat
|
||||
|
||||
if request.method == 'POST' and submission.state_id in ("auth", "aut-appr") and key_matched:
|
||||
post_submission(request, submission)
|
||||
submitter_parsed = submission.submitter_parsed()
|
||||
if submitter_parsed["name"] and submitter_parsed["email"]:
|
||||
# We know who approved it
|
||||
desc = "New version approved"
|
||||
elif submission.state_id == "auth":
|
||||
desc = "New version approved by author"
|
||||
else:
|
||||
desc = "New version approved by previous author"
|
||||
|
||||
post_submission(request, submission, desc)
|
||||
|
||||
create_submission_event(request, submission, "Confirmed and posted submission")
|
||||
|
||||
|
@ -482,7 +551,7 @@ def add_preapproval(request):
|
|||
p.by = request.user.person
|
||||
p.save()
|
||||
|
||||
return HttpResponseRedirect(urlreverse("submit_approvals") + "#preapprovals")
|
||||
return HttpResponseRedirect(urlreverse("ietf.submit.views.approvals") + "#preapprovals")
|
||||
else:
|
||||
form = PreapprovalForm()
|
||||
|
||||
|
@ -501,8 +570,271 @@ def cancel_preapproval(request, preapproval_id):
|
|||
if request.method == "POST" and request.POST.get("action", "") == "cancel":
|
||||
preapproval.delete()
|
||||
|
||||
return HttpResponseRedirect(urlreverse("submit_approvals") + "#preapprovals")
|
||||
return HttpResponseRedirect(urlreverse("ietf.submit.views.approvals") + "#preapprovals")
|
||||
|
||||
return render(request, 'submit/cancel_preapproval.html',
|
||||
{'selected': 'approvals',
|
||||
'preapproval': preapproval })
|
||||
|
||||
|
||||
def manualpost(request):
|
||||
'''
|
||||
Main view for manual post requests
|
||||
'''
|
||||
|
||||
manual = Submission.objects.filter(state_id = "manual").distinct()
|
||||
|
||||
for s in manual:
|
||||
s.passes_checks = all([ c.passed!=False for c in s.checks.all() ])
|
||||
s.errors = validate_submission(s)
|
||||
|
||||
waiting_for_draft = Submission.objects.filter(state_id = "waiting-for-draft").distinct()
|
||||
|
||||
return render(request, 'submit/manual_post.html',
|
||||
{'manual': manual,
|
||||
'selected': 'manual_posts',
|
||||
'waiting_for_draft': waiting_for_draft})
|
||||
|
||||
|
||||
def cancel_waiting_for_draft(request):
|
||||
if request.method == 'POST':
|
||||
can_cancel = has_role(request.user, "Secretariat")
|
||||
|
||||
if not can_cancel:
|
||||
return HttpResponseForbidden('You do not have permission to perform this action')
|
||||
|
||||
submission_id = request.POST.get('submission_id', '')
|
||||
access_token = request.POST.get('access_token', '')
|
||||
|
||||
submission = get_submission_or_404(submission_id, access_token = access_token)
|
||||
cancel_submission(submission)
|
||||
|
||||
create_submission_event(request, submission, "Cancelled submission")
|
||||
if (submission.rev != "00"):
|
||||
# Add a doc event
|
||||
docevent_from_submission(request,
|
||||
submission,
|
||||
"Cancelled submission for rev {}".format(submission.rev))
|
||||
|
||||
return redirect("ietf.submit.views.manualpost")
|
||||
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def add_manualpost_email(request, submission_id=None, access_token=None):
|
||||
"""Add email to submission history"""
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
return redirect("submit/manual_post.html")
|
||||
|
||||
form = SubmissionEmailForm(request.POST)
|
||||
if form.is_valid():
|
||||
submission_pk = form.cleaned_data['submission_pk']
|
||||
message = form.cleaned_data['message']
|
||||
#in_reply_to = form.cleaned_data['in_reply_to']
|
||||
# create Message
|
||||
|
||||
if form.cleaned_data['direction'] == 'incoming':
|
||||
msgtype = 'msgin'
|
||||
else:
|
||||
msgtype = 'msgout'
|
||||
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=request,
|
||||
remote_ip=request.META.get('REMOTE_ADDR', None),
|
||||
name = form.draft_name,
|
||||
rev=form.revision,
|
||||
submission_pk = submission_pk,
|
||||
message = message,
|
||||
by = request.user.person,
|
||||
msgtype = msgtype) )
|
||||
|
||||
messages.success(request, 'Email added.')
|
||||
|
||||
try:
|
||||
draft = Document.objects.get(name=submission.name)
|
||||
except Document.DoesNotExist:
|
||||
# Assume this is revision 00 - we'll do this later
|
||||
draft = None
|
||||
|
||||
if (draft != None):
|
||||
e = AddedMessageEvent(type="added_message", doc=draft)
|
||||
e.message = submission_email_event.submissionemailevent.message
|
||||
e.msgtype = submission_email_event.submissionemailevent.msgtype
|
||||
e.in_reply_to = submission_email_event.submissionemailevent.in_reply_to
|
||||
e.by = request.user.person
|
||||
e.desc = submission_email_event.desc
|
||||
e.time = submission_email_event.time
|
||||
e.save()
|
||||
|
||||
return redirect("ietf.submit.views.manualpost")
|
||||
except ValidationError as e:
|
||||
form = SubmissionEmailForm(request.POST)
|
||||
form._errors = {}
|
||||
form._errors["__all__"] = form.error_class(["There was a failure uploading your message. (%s)" % e.message])
|
||||
else:
|
||||
initial = {
|
||||
}
|
||||
|
||||
if (submission_id != None):
|
||||
submission = get_submission_or_404(submission_id, access_token)
|
||||
initial['name'] = "{}-{}".format(submission.name, submission.rev)
|
||||
initial['direction'] = 'incoming'
|
||||
initial['submission_pk'] = submission.pk
|
||||
else:
|
||||
initial['direction'] = 'incoming'
|
||||
|
||||
form = SubmissionEmailForm(initial=initial)
|
||||
|
||||
return render(request, 'submit/add_submit_email.html',dict(form=form))
|
||||
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def send_submission_email(request, submission_id, message_id=None):
|
||||
"""Send an email related to a submission"""
|
||||
submission = get_submission_or_404(submission_id, access_token = None)
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
return redirect('ietf.submit.views.submission_status',
|
||||
submission_id=submission.id,
|
||||
access_token=submission.access_token())
|
||||
|
||||
form = MessageModelForm(request.POST)
|
||||
if form.is_valid():
|
||||
# create Message
|
||||
msg = Message.objects.create(
|
||||
by = request.user.person,
|
||||
subject = form.cleaned_data['subject'],
|
||||
frm = form.cleaned_data['frm'],
|
||||
to = form.cleaned_data['to'],
|
||||
cc = form.cleaned_data['cc'],
|
||||
bcc = form.cleaned_data['bcc'],
|
||||
reply_to = form.cleaned_data['reply_to'],
|
||||
body = form.cleaned_data['body']
|
||||
)
|
||||
|
||||
in_reply_to_id = form.cleaned_data['in_reply_to_id']
|
||||
in_reply_to = None
|
||||
rp = ""
|
||||
|
||||
if in_reply_to_id:
|
||||
rp = " reply"
|
||||
try:
|
||||
in_reply_to = Message.objects.get(id=in_reply_to_id)
|
||||
except Message.DoesNotExist:
|
||||
log("Unable to retrieve in_reply_to message: %s" % in_reply_to_id)
|
||||
|
||||
desc = "Sent message {} - manual post - {}-{}".format(rp,
|
||||
submission.name,
|
||||
submission.rev)
|
||||
SubmissionEmailEvent.objects.create(
|
||||
submission = submission,
|
||||
desc = desc,
|
||||
msgtype = 'msgout',
|
||||
by = request.user.person,
|
||||
message = msg,
|
||||
in_reply_to = in_reply_to)
|
||||
|
||||
# send email
|
||||
send_mail_message(None,msg)
|
||||
|
||||
messages.success(request, 'Email sent.')
|
||||
return redirect('ietf.submit.views.submission_status',
|
||||
submission_id=submission.id,
|
||||
access_token=submission.access_token())
|
||||
|
||||
else:
|
||||
reply_to = get_reply_to()
|
||||
msg = None
|
||||
|
||||
if not message_id:
|
||||
addrs = gather_address_lists('sub_confirmation_requested',submission=submission).as_strings(compact=False)
|
||||
to_email = addrs.to
|
||||
cc = addrs.cc
|
||||
subject = 'Regarding {}'.format(submission.name)
|
||||
else:
|
||||
try:
|
||||
submitEmail = SubmissionEmailEvent.objects.get(id=message_id)
|
||||
msg = submitEmail.message
|
||||
|
||||
if msg:
|
||||
to_email = msg.frm
|
||||
cc = msg.cc
|
||||
subject = 'Re:{}'.format(msg.subject)
|
||||
else:
|
||||
to_email = None
|
||||
cc = None
|
||||
subject = 'Regarding {}'.format(submission.name)
|
||||
except Message.DoesNotExist:
|
||||
to_email = None
|
||||
cc = None
|
||||
subject = 'Regarding {}'.format(submission.name)
|
||||
|
||||
initial = {
|
||||
'to': to_email,
|
||||
'cc': cc,
|
||||
'frm': settings.IDSUBMIT_FROM_EMAIL,
|
||||
'subject': subject,
|
||||
'reply_to': reply_to,
|
||||
}
|
||||
|
||||
if msg:
|
||||
initial['in_reply_to_id'] = msg.id
|
||||
|
||||
form = MessageModelForm(initial=initial)
|
||||
|
||||
return render(request, "submit/email.html", {
|
||||
'submission': submission,
|
||||
'access_token': submission.access_token(),
|
||||
'form':form})
|
||||
|
||||
|
||||
def show_submission_email_message(request, submission_id, message_id, access_token=None):
|
||||
submission = get_submission_or_404(submission_id, access_token)
|
||||
|
||||
submitEmail = get_object_or_404(SubmissionEmailEvent, pk=message_id)
|
||||
attachments = submitEmail.message.messageattachment_set.all()
|
||||
|
||||
return render(request, 'submit/submission_email.html',
|
||||
{'submission': submission,
|
||||
'message': submitEmail,
|
||||
'attachments': attachments})
|
||||
|
||||
def show_submission_email_attachment(request, submission_id, message_id, filename, access_token=None):
|
||||
get_submission_or_404(submission_id, access_token)
|
||||
|
||||
message = get_object_or_404(SubmissionEmailEvent, pk=message_id)
|
||||
|
||||
attach = get_object_or_404(MessageAttachment,
|
||||
message=message.message,
|
||||
filename=filename)
|
||||
|
||||
if attach.encoding == "base64":
|
||||
body = base64.b64decode(attach.body)
|
||||
else:
|
||||
body = attach.body.encode('utf-8')
|
||||
|
||||
if attach.content_type is None:
|
||||
content_type='text/plain'
|
||||
else:
|
||||
content_type=attach.content_type
|
||||
|
||||
response = HttpResponse(body, content_type=content_type)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s' % attach.filename
|
||||
response['Content-Length'] = len(body)
|
||||
return response
|
||||
|
||||
|
||||
def get_submission_or_404(submission_id, access_token=None):
|
||||
submission = get_object_or_404(Submission, pk=submission_id)
|
||||
|
||||
key_matched = access_token and submission.access_token() == access_token
|
||||
if not key_matched: key_matched = submission.access_key == access_token # backwards-compat
|
||||
if access_token and not key_matched:
|
||||
raise Http404
|
||||
|
||||
return submission
|
||||
|
|
|
@ -43,12 +43,12 @@
|
|||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
{% endif %}
|
||||
<li><a href="{% url "submit_upload_submission" %}">Submit a draft</a></li>
|
||||
<li><a href="{% url "ietf.submit.views.upload_submission" %}">Draft submissions</a></li>
|
||||
|
||||
{% if user|has_role:"WG Chair" %}
|
||||
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
|
||||
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>WG chair</li>
|
||||
<li><a href="{% url "submit_approvals" %}">Approve a draft</a></li>
|
||||
<li><a href="{% url "ietf.submit.views.approvals" %}">Approve a draft</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user and user.is_authenticated %}
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a class="btn btn-default pull-right" href="{% url "submit_approvals" %}#preapprovals">Back</a>
|
||||
<a class="btn btn-default pull-right" href="{% url "ietf.submit.views.approvals" %}#preapprovals">Back</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
|
|
37
ietf/templates/submit/add_submit_email.html
Normal file
37
ietf/templates/submit/add_submit_email.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}{% if submission == None %}Add new submission request email{% else %}Add submission request email to {{ submission.name }}{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Add email</h1>
|
||||
|
||||
{% if submission == None %}
|
||||
<p>
|
||||
A new submission request will be created for the given name and revision. The
|
||||
name must take the form "draft-xxx-nn" where xxx is lowercase letters, digits or dashes
|
||||
and nn is the revision number - 00 for the initial revision. For example<br/>
|
||||
draft-my-spec-00
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
The email will be added to the submission history for {{ submission.name }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<form class="add-email" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">Add Email</button>
|
||||
<a class="btn btn-default pull-right" href="{% url "ietf.submit.views.manualpost" %}">Back</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -4,7 +4,7 @@ Hi,
|
|||
Chair approval is needed for posting of {{ submission.name }}-{{ submission.rev }}.
|
||||
|
||||
To approve the draft, go to this URL (note: you need to login to be able to approve):
|
||||
https://{{ domain }}{% url "submit_submission_status_by_hash" submission_id=submission.pk access_token=submission.access_token %}
|
||||
https://{{ domain }}{% url "ietf.submit.views.submission_status" submission_id=submission.pk access_token=submission.access_token %}
|
||||
|
||||
File name : {{ submission.name }}
|
||||
Revision : {{ submission.rev }}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<tbody>
|
||||
{% for s in approvals %}
|
||||
<tr>
|
||||
<td><a href="{% url "submit_submission_status_by_hash" submission_id=s.pk access_token=s.access_token %}">{{ s.name }}-{{ s.rev }}</a></td>
|
||||
<td><a href="{% url "ietf.submit.views.submission_status" submission_id=s.pk access_token=s.access_token %}">{{ s.name }}-{{ s.rev }}</a></td>
|
||||
<td>{{ s.submission_date }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -39,7 +39,7 @@
|
|||
<h2 class="anchor-target" id="preapprovals">Pre-approved drafts not yet submitted</h2>
|
||||
|
||||
{% if user|has_role:"Secretariat,WG Chair,RG Chair" %}
|
||||
<p><a class="btn btn-default" href="{% url "submit_add_preapproval" %}">Add pre-approval</a></p>
|
||||
<p><a class="btn btn-default" href="{% url "ietf.submit.views.add_preapproval" %}">Add pre-approval</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if not preapprovals %}
|
||||
|
@ -60,7 +60,7 @@
|
|||
<td>{{ p.name }}</td>
|
||||
<td>{{ p.time|date:"Y-m-d" }}</td>
|
||||
<td>{{ p.by }}</td>
|
||||
<td><a class="btn btn-danger btn-xs" href="{% url "submit_cancel_preapproval" preapproval_id=p.id %}">Cancel</a></td>
|
||||
<td><a class="btn btn-danger btn-xs" href="{% url "ietf.submit.views.cancel_preapproval" preapproval_id=p.id %}">Cancel</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -104,4 +104,4 @@
|
|||
|
||||
{% block js %}
|
||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-danger">Cancel pre-approval</button>
|
||||
<a class="btn btn-default pull-right" href="{% url "submit_approvals" %}#preapprovals">Back</a>
|
||||
<a class="btn btn-default pull-right" href="{% url "ietf.submit.views.approvals" %}#preapprovals">Back</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{% else %}
|
||||
<p>The submission is not in a state where it can be confirmed.</p>
|
||||
|
||||
<p>Go to the <a href="{% url "submit_submission_status" submission_id=submission.pk %}">status page</a>
|
||||
<p>Go to the <a href="{% url "ietf.submit.views.submission_status" submission_id=submission.pk %}">status page</a>
|
||||
to see what has happened to it.</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
@ -23,7 +23,7 @@
|
|||
<p class="error">Incorrect authorization key.</p>
|
||||
|
||||
<p>Double-check the link you followed. If everything fails, you can go to
|
||||
the <a href="{% url "submit_submission_status" submission_id=submission.pk %}">status page</a>,
|
||||
the <a href="{% url "ietf.submit.views.submission_status" submission_id=submission.pk %}">status page</a>,
|
||||
cancel the submission and try again.</p>
|
||||
{% else %}
|
||||
<p>Authorization key accepted.</p>
|
||||
|
|
30
ietf/templates/submit/email.html
Normal file
30
ietf/templates/submit/email.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load staticfiles %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Email related to{{ submission.name }}{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-datepicker/css/bootstrap-datepicker3.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Email related to<br><small>{{ submission.name }}</small></h1>
|
||||
|
||||
<form method="post" class="show-required">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">Send Email</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
|
||||
{% endblock %}
|
98
ietf/templates/submit/manual_post.html
Normal file
98
ietf/templates/submit/manual_post.html
Normal file
|
@ -0,0 +1,98 @@
|
|||
{% extends "submit/submit_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin staticfiles %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Draft submissions awaiting manual posting{% endblock %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block submit_content %}
|
||||
{% origin %}
|
||||
|
||||
<h2 class="anchor-target" id="approvals">Submissions needing manual posting</h2>
|
||||
|
||||
{% if not manual %}
|
||||
<p id="no-manual">There are no submissions needing manual posting.</p>
|
||||
{% else %}
|
||||
<table id="manual" class="submissions table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Draft</th>
|
||||
<th>Submitted</th>
|
||||
<th>Passes Checks</th>
|
||||
<th>Metadata</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in manual %}
|
||||
<tr>
|
||||
{% if user.is_authenticated %}
|
||||
<td><a href="{% url "ietf.submit.views.submission_status" submission_id=s.pk access_token=s.access_token %}">{{ s.name }}-{{ s.rev }}</a></td>
|
||||
{% else %}
|
||||
<td><a href="{% url "ietf.submit.views.submission_status" submission_id=s.pk %}">{{ s.name }}-{{ s.rev }}</a></td>
|
||||
{% endif %}
|
||||
<td>{{ s.submission_date }}</td>
|
||||
<td>{% if s.passes_checks %}Ok{% else %}Fails{% endif %}</td>
|
||||
<td>{% if s.errors %}Errors{% else %}Ok{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="anchor-target" id="approvals">Submissions awaiting draft upload</h2>
|
||||
|
||||
{% if not waiting_for_draft %}
|
||||
<p id="no-waiting-for-draft">There are no submissions awaiting draft upload.</p>
|
||||
{% else %}
|
||||
<table id="waiting-for-draft" class="waiting-for-draft table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Rev</th>
|
||||
<th>Submitted</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in waiting_for_draft %}
|
||||
<tr>
|
||||
{% if user.is_authenticated %}
|
||||
<td><a id="aw{{ s.pk }}" href="{% url "ietf.submit.views.submission_status" submission_id=s.pk access_token=s.access_token %}">{{ s.name }}</a></td>
|
||||
{% else %}
|
||||
<td><a id="aw{{ s.pk }}" href="{% url "ietf.submit.views.submission_status" submission_id=s.pk %}">{{ s.name }}</a></td>
|
||||
{% endif %}
|
||||
<td>{{ s.rev }}</td>
|
||||
<td>{{ s.submission_date }}</td>
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<td>
|
||||
<form id="cancel-submission" action="/submit/awaitingdraft/cancel" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="submission_id" value="{{ s.pk }}">
|
||||
<input type="hidden" name="access_token" value="{{ s.access_token }}">
|
||||
<button class="btn btn-danger btn-xs" type="submit" data-toggle="tooltip" title="Cancels the submission permanently.">Cancel submission</button>
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<td><a id="add-submission-email{{ s.pk }}" class="btn btn-default btn-xs" href="{% url "ietf.submit.views.add_manualpost_email" submission_id=s.pk access_token=s.access_token %}">Add email</a></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a id="new-submission-email" class="btn btn-default btn-sm" href="{% url "ietf.submit.views.add_manualpost_email" %}">New submission from email</a>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
{% endblock %}
|
60
ietf/templates/submit/submission_email.html
Normal file
60
ietf/templates/submit/submission_email.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
{% extends "submit/submit_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin staticfiles %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Submission email{% endblock %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block submit_content %}
|
||||
{% origin %}
|
||||
<h2 class="anchor-target" id="approvals">Email for {{ submission.name }}</h2>
|
||||
|
||||
<table id="email-details" class="emails table table-condensed table-striped tablesorter">
|
||||
<tr>
|
||||
<th>Uploaded</th>
|
||||
<td>{{ message.time }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<td>{{ message.message.time }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>From</th>
|
||||
<td>{{ message.message.frm }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Subject</th>
|
||||
<td>{{ message.message.subject }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Message</th>
|
||||
<td>{{ message.message.body|linebreaksbr }}</td>
|
||||
</tr>
|
||||
|
||||
{% for a in attachments %}
|
||||
<tr>
|
||||
<th>Attachment</th>
|
||||
<td><a id="attach{{ submission.pk }}" href="{% url "ietf.submit.views.show_submission_email_attachment" submission_id=submission.pk message_id=message.pk filename=a.filename %}">{{ a.filename }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<tr>
|
||||
<td><a id="reply{{ submission.pk }}" class="btn btn-default" href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk message_id=message.pk %}" title="Reply"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> Reply</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
{% endblock %}
|
|
@ -101,28 +101,33 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% if submission.state_id == "waiting-for-draft" %}
|
||||
<p class="alert alert-warning">
|
||||
This submission is awaiting the first draft upload.
|
||||
</p>
|
||||
{% else %}
|
||||
<h2>Meta-data from the submission</h2>
|
||||
|
||||
<h2>Meta-data from the submission</h2>
|
||||
{% if errors %}
|
||||
<div class="alert alert-danger">
|
||||
<p><b>Meta-Data errors found!</b></p>
|
||||
<p >Please make sure that your Internet-Draft includes all of the required meta-data in the proper format.</p>
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-danger">
|
||||
<p><b>Meta-Data errors found!</b></p>
|
||||
<p>Please make sure that your Internet-Draft includes all of the required meta-data in the proper format.</p>
|
||||
<p>If your Internet-Draft <b>does</b> include all of the required meta-data in the proper format, and if
|
||||
the error(s) identified below are due to the failure of the tool to extract the meta-data correctly,
|
||||
then please use the "Adjust meta-data" button below, which will take you to the "Adjust screen" where
|
||||
you can correct the improperly extracted meta-data. You will then be able to submit your Internet-Draft
|
||||
to the Secretariat for manual posting.</p>
|
||||
|
||||
<p>If your Internet-Draft <b>does</b> include all of the required meta-data in the proper format, and if
|
||||
the error(s) identified below are due to the failure of the tool to extract the meta-data correctly,
|
||||
then please use the "Adjust meta-data" button below, which will take you to the "Adjust screen" where
|
||||
you can correct the improperly extracted meta-data. You will then be able to submit your Internet-Draft
|
||||
to the Secretariat for manual posting.</p>
|
||||
<p>If your Internet-Draft <b>does not</b> include all of the required meta-data in the proper format, then
|
||||
please cancel this submission, update your Internet-Draft, and resubmit it.</p>
|
||||
|
||||
<p>If your Internet-Draft <b>does not</b> include all of the required meta-data in the proper format, then
|
||||
please cancel this submission, update your Internet-Draft, and resubmit it.</p>
|
||||
|
||||
<p><b>Note:</b> The secretariat will <b>not</b> add any
|
||||
meta-data to your Internet-Draft or edit the meta-data. An
|
||||
Internet-Draft that does not include all of the required meta-data in
|
||||
the proper format <b>will</b> be returned to the submitter.</p>
|
||||
</div>
|
||||
<p><b>Note:</b> The secretariat will <b>not</b> add any
|
||||
meta-data to your Internet-Draft or edit the meta-data. An
|
||||
Internet-Draft that does not include all of the required meta-data in
|
||||
the proper format <b>will</b> be returned to the submitter.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
|
@ -305,6 +310,11 @@
|
|||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<p></p>
|
||||
<a id="send{{ submission.pk }}" class="btn btn-default" href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk %}" title="Email submitter"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span>Send Email</a>
|
||||
{% endif %}
|
||||
|
||||
{% if show_send_full_url %}
|
||||
<div class="alert alert-danger">
|
||||
<p>You are not allowed to modify or cancel this submission. You can
|
||||
|
@ -329,7 +339,7 @@
|
|||
|
||||
<h2>History</h2>
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<table id="history" class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr><th>Date</th><th>By</th><th>Event</th></tr>
|
||||
</thead>
|
||||
|
@ -338,7 +348,21 @@
|
|||
<tr>
|
||||
<td class="text-nowrap">{{ e.time|date:"Y-m-d" }}</td>
|
||||
<td>{{ e.by|default:"" }}</td>
|
||||
<td>{{ e.desc }}</td>
|
||||
{% if e.desc|startswith:"Received message" or e.desc|startswith:"Sent message" %}
|
||||
{% with m=e.submissionemailevent.message %}
|
||||
{% if user.is_authenticated %}
|
||||
<td>
|
||||
{% if e.desc|startswith:"Received message" and user|has_role:"Secretariat" %}
|
||||
<a id="reply{{ submission.pk }}" class="btn btn-default btn-xs" href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk message_id=e.submissionemailevent.pk %}" title="Reply"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> Reply</a>
|
||||
{% endif %}
|
||||
Email: <a id="aw{{ submission.pk }}-{{ m.pk }}" href="{% url "ietf.submit.views.show_submission_email_message" submission_id=submission.pk message_id=e.submissionemailevent.pk access_token=submission.access_token %}">{{ e.desc }}</a></td>
|
||||
{% else %}
|
||||
<td>Email: <a id="aw{{ submission.pk }}-{{ m.pk }}" href="{% url "ietf.submit.views.show_submission_email_message" submission_id=submission.pk message_id=e.submissionemailevent.pk %}">{{ e.desc }}</a></td>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<td>{{ e.desc }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -12,19 +12,22 @@
|
|||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li {% if selected == "index" %}class="active"{% endif %}>
|
||||
<a href="{% url "submit_upload_submission" %}">Upload</a>
|
||||
<a href="{% url "ietf.submit.views.upload_submission" %}">Upload</a>
|
||||
</li>
|
||||
<li {% if selected == "status" %}class="active"{% endif %}>
|
||||
<a href="{% url "submit_search_submission" %}">Status</a>
|
||||
<a href="{% url "ietf.submit.views.search_submission" %}">Status</a>
|
||||
</li>
|
||||
<li {% if selected == "instructions" %}class="active"{% endif %}>
|
||||
<a href="{% url "submit_tool_instructions" %}">Instructions</a>
|
||||
<a href="{% url "ietf.submit.views.tool_instructions" %}">Instructions</a>
|
||||
</li>
|
||||
<li {% if selected == "notewell" %}class="active"{% endif %}>
|
||||
<a href="{% url "submit_note_well" %}">Note Well</a>
|
||||
<a href="{% url "ietf.submit.views.note_well" %}">Note Well</a>
|
||||
</li>
|
||||
<li {% if selected == "approvals" %}class="active"{% endif %}>
|
||||
<a href="{% url "submit_approvals" %}">Approvals</a>
|
||||
<a href="{% url "ietf.submit.views.approvals" %}">Approvals</a>
|
||||
</li>
|
||||
<li {% if selected == "manual_posts" %}class="active"{% endif %}>
|
||||
<a href="{% url "ietf.submit.views.manualpost" %}">Manual Post Requests</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -8,10 +8,17 @@
|
|||
please manually enter your name and email address.
|
||||
</p>
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% buttons %}
|
||||
{% for author in submission.authors_parsed %}
|
||||
<input type="button" class="author btn btn-default" data-name="{{ author.name }}" data-email="{{ author.email }}" value="{{ author.name }}">
|
||||
{% endfor %}
|
||||
{% endbuttons %}
|
||||
|
||||
{% bootstrap_form submitter_form %}
|
||||
{% bootstrap_form_errors submitter_form %}
|
||||
{% bootstrap_field submitter_form.name %}
|
||||
{% bootstrap_field submitter_form.email %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
{% bootstrap_field submitter_form.approvals_received %}
|
||||
{% endif %}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{% origin %}
|
||||
<h2>I-D submission tool instructions</h2>
|
||||
|
||||
<p><b>Tool URL:</b> <a href="{% url "submit_upload_submission" %}">https://datatracker.ietf.org{% url "submit_upload_submission" %}</a></p>
|
||||
<p><b>Tool URL:</b> <a href="{% url "ietf.submit.views.upload_submission" %}">https://datatracker.ietf.org{% url "ietf.submit.views.upload_submission" %}</a></p>
|
||||
|
||||
<p>
|
||||
This page will explain the purpose and content of each screen in the I-D Submission Tool, and the actions that result by clicking the form buttons on each screen.
|
||||
|
|
|
@ -29,6 +29,7 @@ python-dateutil>=2.2
|
|||
python-magic>=0.4.6
|
||||
python-memcached>=1.48 # for django.core.cache.backends.memcached
|
||||
pytz>=2014.7
|
||||
pyzmail>=1.0.3
|
||||
selenium>=2.42
|
||||
six>=1.8.0
|
||||
tqdm>=3.7.0
|
||||
|
|
Loading…
Reference in a new issue