chore: remove unused submit by email functionality (#7249)

This commit is contained in:
Robert Sparks 2024-03-26 09:44:49 -05:00 committed by GitHub
parent 891c0e975d
commit a00dfc0c27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 43 additions and 1158 deletions

View file

@ -149,9 +149,5 @@ def gather_relevant_expansions(**kwargs):
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]

View file

@ -1,33 +1,22 @@
# Copyright The IETF Trust 2013-2020, All Rights Reserved
# -*- coding: utf-8 -*-
import re
import email
import base64
import os
import pyzmail
from django.conf import settings
from django.urls import reverse as urlreverse
from django.core.exceptions import ValidationError
from django.contrib.sites.models import Site
from django.template.loader import render_to_string
from django.utils.encoding import force_str
import debug # pyflakes:ignore
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, MessageAttachment
from ietf.message.models import Message
from ietf.utils.accesstoken import generate_access_token
from ietf.mailtrigger.utils import gather_address_lists, get_base_submission_message_address
from ietf.submit.models import SubmissionEmailEvent, Submission
from ietf.mailtrigger.utils import gather_address_lists
from ietf.submit.checkers import DraftIdnitsChecker
from ietf.utils.timezone import date_today
def send_submission_confirmation(request, submission, chair_notice=False):
@ -196,181 +185,3 @@ def announce_to_authors(request, 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 = force_str(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(force_str(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("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=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(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', '')),
content_type = message.get('content_type', 'text/plain'),
)
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)

View file

@ -1,32 +0,0 @@
# Copyright The IETF Trust 2016-2020, All Rights Reserved
# -*- coding: utf-8 -*-
import io
import sys
from django.core.management.base import BaseCommand, CommandError
from ietf.submit.mail import process_response_email
import debug # pyflakes:ignore
class Command(BaseCommand):
help = ("Process incoming manual post email responses")
def add_arguments(self, parser):
parser.add_argument('--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 = io.open(email, "r").read()
try:
process_response_email(msg)
except ValueError as e:
raise CommandError(e)

View file

@ -42,17 +42,15 @@ 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.meeting.factories import MeetingFactory
from ietf.message.models import Message
from ietf.name.models import FormalLanguageName
from ietf.person.models import Person
from ietf.person.factories import UserFactory, PersonFactory, EmailFactory
from ietf.submit.factories import SubmissionFactory, SubmissionExtResourceFactory
from ietf.submit.forms import SubmissionBaseUploadForm, SubmissionAutoUploadForm
from ietf.submit.models import Submission, Preapproval, SubmissionExtResource
from ietf.submit.mail import add_submission_email, process_response_email
from ietf.submit.tasks import cancel_stale_submissions, process_and_accept_uploaded_submission_task
from ietf.utils.accesstoken import generate_access_token
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
from ietf.utils.mail import outbox, get_payload_text
from ietf.utils.models import VersionInfo
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
from ietf.utils.timezone import date_today
@ -198,6 +196,28 @@ def create_draft_submission_with_rev_mismatch(rev='01'):
return draft, sub
class ManualSubmissionTests(TestCase):
def test_manualpost_view(self):
submission = SubmissionFactory(state_id="manual")
url = urlreverse("ietf.submit.views.manualpost")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(
urlreverse(
"ietf.submit.views.submission_status",
kwargs=dict(submission_id=submission.pk)
),
q("#manual.submissions td a").attr("href")
)
self.assertIn(
submission.name,
q("#manual.submissions td a").text()
)
def test_manualpost_cancel(self):
pass
class SubmitTests(BaseSubmitTestCase):
def setUp(self):
super().setUp()
@ -2293,451 +2313,6 @@ class ApprovalsTestCase(BaseSubmitTestCase):
self.assertEqual(len(Preapproval.objects.filter(name=preapproval.name)), 0)
class ManualPostsTestCase(BaseSubmitTestCase):
def test_manual_posts(self):
GroupFactory(acronym='mars')
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=date_today(),
state_id="manual")
Submission.objects.create(name="draft-ietf-mars-bar",
group=Group.objects.get(acronym="mars"),
submission_date=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(timezone.now().ctime())
message = email.message_from_string(force_str(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, timezone.now().ctime())
message = email.message_from_string(force_str(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(timezone.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, "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(timezone.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)
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, {
"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, timezone.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], author = submission_file(f'{name}-{rev}', f'{name}-{rev}.{format}', group, "test_submission.%s" % format)
r = self.post_to_upload_submission(url, files)
if r.status_code != 302:
q = PyQuery(r.content)
print(q('div.invalid-feedback span.form-text 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, "%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), 1)
author = submission.authors[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, email.utils.formataddr((submitter_name, submitter_email)))
return r
# Transaction.on_commit() requires use of TransactionTestCase, but that has a performance penalty. Replace it
# with a no-op for testing purposes.

View file

@ -17,15 +17,6 @@ urlpatterns = [
url(r'^approvals/cancelpreapproval/(?P<preapproval_id>[a-f\d]+)/$', views.cancel_preapproval),
url(r'^manualpost/$', views.manualpost),
url(r'^manualpost/addemail$', views.add_manualpost_email),
url(r'^manualpost/addemail/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.add_manualpost_email),
url(r'^manualpost/attachment/(?P<submission_id>\d+)/(?P<message_id>\d+)/(?P<filename>.*)$', views.show_submission_email_attachment),
url(r'^manualpost/cancel$', views.cancel_waiting_for_draft),
url(r'^manualpost/email/(?P<submission_id>\d+)/(?P<message_id>\d+)/$', views.show_submission_email_message),
url(r'^manualpost/email/(?P<submission_id>\d+)/(?P<message_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.show_submission_email_message),
url(r'^manualpost/replyemail/(?P<submission_id>\d+)/(?P<message_id>\d+)/$', views.send_submission_email),
url(r'^manualpost/sendemail/(?P<submission_id>\d+)/$', views.send_submission_email),
# proof-of-concept for celery async tasks
url(r'^async-poke/?$', views.async_poke_test),
]
]

View file

@ -3,7 +3,6 @@
import re
import base64
import datetime
from typing import Optional, cast # pyflakes:ignore
@ -22,31 +21,30 @@ from django.views.decorators.csrf import csrf_exempt
import debug # pyflakes:ignore
from ietf.doc.models import Document, AddedMessageEvent
from ietf.doc.models import Document
from ietf.doc.forms import ExtResourceForm
from ietf.group.models import Group
from ietf.group.utils import group_features_group_filter
from ietf.ietfauth.utils import has_role, role_required
from ietf.mailtrigger.utils import gather_address_lists
from ietf.message.models import Message, MessageAttachment
from ietf.person.models import Email
from ietf.submit.forms import (SubmissionAutoUploadForm, AuthorForm, SubmitterForm, EditSubmissionForm,
PreapprovalForm, ReplacesForm, SubmissionEmailForm, MessageModelForm,
PreapprovalForm, ReplacesForm,
DeprecatedSubmissionAutoUploadForm, SubmissionManualUploadForm)
from ietf.submit.mail import send_full_url, send_manual_post_request, add_submission_email, get_reply_to
from ietf.submit.mail import send_full_url, send_manual_post_request
from ietf.submit.models import (Submission, Preapproval, SubmissionExtResource,
DraftSubmissionStateName, SubmissionEmailEvent )
DraftSubmissionStateName )
from ietf.submit.tasks import process_uploaded_submission_task, process_and_accept_uploaded_submission_task, poke
from ietf.submit.utils import ( approvable_submissions_for_user, preapprovals_for_user,
recently_approved_by_user, validate_submission, create_submission_event, docevent_from_submission,
post_submission, cancel_submission, rename_submission_files, remove_submission_files, get_draft_meta,
get_submission, fill_in_submission, apply_checkers, save_files, clear_existing_files,
check_submission_revision_consistency, accept_submission, accept_submission_requires_group_approval,
accept_submission_requires_prev_auth_approval, update_submission_external_resources, remote_ip )
accept_submission_requires_prev_auth_approval, update_submission_external_resources)
from ietf.stats.utils import clean_country_name
from ietf.utils.accesstoken import generate_access_token
from ietf.utils.log import log
from ietf.utils.mail import parseaddr, send_mail_message
from ietf.utils.mail import parseaddr
from ietf.utils.response import permission_denied
from ietf.utils.timezone import date_today
@ -780,243 +778,14 @@ def manualpost(request):
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:
permission_denied(request, '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(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=remote_ip(request),
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 = {
return render(
request,
'submit/manual_post.html',
{
'manual': manual,
'selected': 'manual_posts'
}
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):

View file

@ -1,32 +0,0 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load django_bootstrap5 %}
{% 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>
<p class="my-3">
{% if submission == None %}
A new submission request will be created for the given name and revision. The
name must take the form <code>draft-xxx-nn</code> where <code>xxx</code> is lowercase letters, digits or dashes
and <code>nn</code> is the revision number, <code>00</code> for the initial revision. For example,
<code>draft-my-spec-00</code>.
{% else %}
The email will be added to the submission history for {{ submission.name }}.
{% endif %}
</p>
<form class="add-email my-3" method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Add Email</button>
<a class="btn btn-secondary float-end"
href="{% url "ietf.submit.views.manualpost" %}">Back</a>
</form>
{% endblock %}

View file

@ -1,25 +0,0 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load django_bootstrap5 %}
{% block title %}Email related to{{ submission.name }}{% endblock %}
{% block pagehead %}
<link rel="stylesheet" href="{% static 'ietf/css/datepicker.css' %}">
{% endblock %}
{% block content %}
{% origin %}
<h1>
Email related to
<br>
<small class="text-body-secondary">{{ submission.name }}</small>
</h1>
<form method="post" class="show-required my-3">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Send email</button>
</form>
{% endblock %}
{% block js %}
<script src="{% static 'ietf/js/datepicker.js' %}"></script>
{% endblock %}

View file

@ -58,76 +58,6 @@
</tbody>
</table>
{% endif %}
<h2 class="mt-3" id="id_upload">Submissions awaiting Internet-Draft upload</h2>
{% if not waiting_for_draft %}
<p class="alert alert-info my-3" id="no-waiting-for-draft">
There are no submissions awaiting Internet-Draft upload.
</p>
{% else %}
<table id="waiting-for-draft"
class="waiting-for-draft table table-sm table-striped tablesorter">
<thead>
<tr>
<th scope="col" data-sort="name">Name</th>
<th scope="col" data-sort="rev">Rev</th>
<th scope="col" data-sort="date">Submitted</th>
<th scope="col"></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>
<td>
{% if user|has_role:"Secretariat" %}
<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-sm float-end ms-1"
type="submit"
data-bs-toggle="tooltip"
title="Cancels the submission permanently.">
Cancel submission
</button>
</form>
{% endif %}
{% if user|has_role:"Secretariat" %}
<a id="add-submission-email{{ s.pk }}"
class="btn btn-primary btn-sm float-end ms-1"
href="{% url "ietf.submit.views.add_manualpost_email" submission_id=s.pk access_token=s.access_token %}">
Add email
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if user|has_role:"Secretariat" %}
<a id="new-submission-email"
class="btn btn-primary"
href="{% url "ietf.submit.views.add_manualpost_email" %}">
New submission from email
</a>
{% endif %}
{% endblock %}
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>

View file

@ -1,61 +0,0 @@
{% extends "submit/submit_base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin static textfilters ietf_filters %}
{% block title %}Submission email{% endblock %}
{% load ietf_filters %}
{% block submit_content %}
{% origin %}
<h2 id="approvals">Email for {{ submission.name }}</h2>
<dl id="email-details" class="emails row my-3">
<dt class="col-sm-2">
Uploaded
</dt>
<dd class="col-sm-10">
{{ message.time }}
</dd>
<dt class="col-sm-2">
Date
</dt>
<dd class="col-sm-10">
{{ message.message.time }}
</dd>
<dt class="col-sm-2">
From
</dt>
<dd class="col-sm-10">
{{ message.message.frm|linkify }}
</dd>
<dt class="col-sm-2">
Subject
</dt>
<dd class="col-sm-10">
{{ message.message.subject }}
</dd>
<dt class="col-sm-2">
Message
</dt>
<dd class="col-sm-10">
<pre>{{ message.message.body|urlize_ietf_docs|linkify|linebreaksbr }}</pre>
</dd>
<dt class="col-sm-2">
Attachment
</dt>
<dd class="col-sm-10">
{% for a in attachments %}
<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>
<br>
{% endfor %}
</dd>
</dl>
{% if user|has_role:"Secretariat" %}
<a id="reply{{ submission.pk }}"
class="btn btn-primary"
href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk message_id=message.pk %}"
title="Reply">
<i class="bi bi-envelope" aria-hidden="true"></i> Reply
</a>
{% endif %}
{% endblock %}

View file

@ -520,14 +520,6 @@
</button>
</form>
{% endif %}
{% if user|has_role:"Secretariat" %}
<a id="send{{ submission.pk }}"
class="btn btn-primary mt-3"
href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk %}"
title="Email submitter">
<i class="bi bi-envelope" aria-hidden="true"></i> Send Email
</a>
{% endif %}
{% if show_send_full_url %}
<div class="alert alert-info my-3">
<p>
@ -584,39 +576,10 @@
{% endif %}
</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-primary btn-sm"
href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk message_id=e.submissionemailevent.pk %}"
title="Reply">
<i class="bi bi-envelope" aria-hidden="true"></i> 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|urlize_ietf_docs|linkify }}
</td>
{% endif %}
<td>
{{ e.desc|urlize_ietf_docs|linkify }}
</td>
</tr>
{% endfor %}
</tbody>