diff --git a/ietf/mailtrigger/models.py b/ietf/mailtrigger/models.py index 40392e0e4..db691750c 100644 --- a/ietf/mailtrigger/models.py +++ b/ietf/mailtrigger/models.py @@ -177,7 +177,7 @@ class Recipient(models.Model): addrs = [] if 'submission' in kwargs: submission = kwargs['submission'] - addrs.extend(["%s <%s>" % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]]) + addrs.extend(["%s <%s>" % (author["name"], author["email"]) for author in submission.authors if author.get("email")]) return addrs def gather_submission_group_chairs(self, **kwargs): @@ -200,7 +200,7 @@ class Recipient(models.Model): doc=submission.existing_document() if doc: old_authors = [author.formatted_email() for author in doc.documentauthor_set.all() if author.email] - new_authors = [u'"%s" <%s>' % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]] + new_authors = [u'"%s" <%s>' % (author["name"], author["email"]) for author in submission.authors if author.get("email")] addrs.extend(old_authors) if doc.group and set(old_authors)!=set(new_authors): if doc.group.type_id in ['wg','rg','ag']: @@ -212,7 +212,7 @@ class Recipient(models.Model): if doc.stream_id and doc.stream_id not in ['ietf']: addrs.extend(Recipient.objects.get(slug='stream_managers').gather(**{'streams':[doc.stream_id]})) else: - addrs.extend([u"%s <%s>" % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]]) + addrs.extend([u"%s <%s>" % (author["name"], author["email"]) for author in submission.authors if author.get("email")]) if submission.submitter_parsed()["email"]: addrs.append(submission.submitter) return addrs diff --git a/ietf/secr/drafts/views.py b/ietf/secr/drafts/views.py index eb77abd64..f431516f6 100644 --- a/ietf/secr/drafts/views.py +++ b/ietf/secr/drafts/views.py @@ -574,8 +574,6 @@ def authors(request, id): return redirect('drafts_view', id=id) - print form.is_valid(), form.errors - if form.is_valid(): person = form.cleaned_data['person'] email = form.cleaned_data['email'] diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py index f01cb44ab..6af82a6c3 100644 --- a/ietf/submit/forms.py +++ b/ietf/submit/forms.py @@ -12,6 +12,8 @@ from django.conf import settings from django.utils.html import mark_safe from django.core.urlresolvers import reverse as urlreverse +from django_countries.fields import countries + import debug # pyflakes:ignore from ietf.doc.models import Document @@ -30,6 +32,14 @@ from ietf.submit.parsers.ps_parser import PSParser from ietf.submit.parsers.xml_parser import XMLParser from ietf.utils.draft import Draft +def clean_country(country): + country = country.upper() + for code, name in countries: + if country == code: + return code + if country == name.upper(): + return code + return "" # unknown class SubmissionUploadForm(forms.Form): txt = forms.FileField(label=u'.txt format', required=False) @@ -178,18 +188,14 @@ class SubmissionUploadForm(forms.Form): self.abstract = self.xmlroot.findtext('front/abstract').strip() if type(self.abstract) is unicode: self.abstract = unidecode(self.abstract) - self.author_list = [] author_info = self.xmlroot.findall('front/author') for author in author_info: - author_dict = dict( - company = author.findtext('organization'), - last_name = author.attrib.get('surname'), - full_name = author.attrib.get('fullname'), - email = author.findtext('address/email'), - ) - self.author_list.append(author_dict) - line = "%(full_name)s <%(email)s>" % author_dict - self.authors.append(line) + self.authors.append({ + "name": author.attrib.get('fullname'), + "email": author.findtext('address/email'), + "affiliation": author.findtext('organization'), + "country": clean_country(author.findtext('address/postal/country')), + }) except forms.ValidationError: raise except Exception as e: @@ -325,18 +331,12 @@ class SubmissionUploadForm(forms.Form): return None class NameEmailForm(forms.Form): - """For validating supplied submitter and author information.""" 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) + email = forms.EmailField(label=u'Email address', required=True) def __init__(self, *args, **kwargs): - email_required = kwargs.pop("email_required", True) super(NameEmailForm, self).__init__(*args, **kwargs) - self.fields["email"].required = email_required self.fields["name"].widget.attrs["class"] = "name" self.fields["email"].widget.attrs["class"] = "email" @@ -346,6 +346,18 @@ class NameEmailForm(forms.Form): def clean_email(self): return self.cleaned_data["email"].replace("\n", "").replace("\r", "").replace("<", "").replace(">", "").strip() +class AuthorForm(NameEmailForm): + affiliation = forms.CharField(max_length=100, required=False) + country = forms.ChoiceField(choices=[('', "(Not specified)")] + list(countries), required=False) + + def __init__(self, *args, **kwargs): + super(AuthorForm, self).__init__(*args, **kwargs) + self.fields["email"].required = False + +class SubmitterForm(NameEmailForm): + #Fields for secretariat only + approvals_received = forms.BooleanField(label=u'Approvals received', required=False, initial=False) + def cleaned_line(self): line = self.cleaned_data["name"] email = self.cleaned_data.get("email") diff --git a/ietf/submit/models.py b/ietf/submit/models.py index 260494360..396811005 100644 --- a/ietf/submit/models.py +++ b/ietf/submit/models.py @@ -39,7 +39,7 @@ class Submission(models.Model): words = models.IntegerField(null=True, blank=True) formal_languages = models.ManyToManyField(FormalLanguageName, blank=True, help_text="Formal languages used in document") - authors = models.TextField(blank=True, help_text="List of author names and emails, one author per line, e.g. \"John Doe <john@example.org>\".") + authors = jsonfield.JSONField(default=list, help_text="List of authors with name, email, affiliation and country code.") note = models.TextField(blank=True) replaces = models.CharField(max_length=1000, blank=True) @@ -56,21 +56,6 @@ class Submission(models.Model): def __unicode__(self): return u"%s-%s" % (self.name, self.rev) - def authors_parsed(self): - if not hasattr(self, '_cached_authors_parsed'): - from ietf.submit.utils import ensure_person_email_info_exists - res = [] - for line in self.authors.replace("\r", "").split("\n"): - line = line.strip() - if line: - parsed = parse_email_line(line) - if not parsed["email"]: - person, email = ensure_person_email_info_exists(**parsed) - parsed["email"] = email.address - res.append(parsed) - self._cached_authors_parsed = res - return self._cached_authors_parsed - def submitter_parsed(self): return parse_email_line(self.submitter) diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 62ab8c3f2..955901761 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -125,7 +125,7 @@ class SubmitTests(TestCase): q = PyQuery(r.content) print(q('div.has-error div.alert').text()) - self.assertEqual(r.status_code, 302) + self.assertNoFormPostErrors(r, ".has-error,.alert-danger") status_url = r["Location"] for format in formats: @@ -133,10 +133,12 @@ class SubmitTests(TestCase): 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(len(submission.authors), 1) + author = submission.authors[0] self.assertEqual(author["name"], "Author Name") self.assertEqual(author["email"], "author@example.com") + self.assertEqual(author["affiliation"], "Test Centre Inc.") + # FIXMEself.assertEqual(author["country"], "UK") return status_url @@ -664,7 +666,7 @@ class SubmitTests(TestCase): "authors-prefix": ["authors-", "authors-0", "authors-1", "authors-2"], }) - self.assertEqual(r.status_code, 302) + self.assertNoFormPostErrors(r, ".has-error,.alert-danger") submission = Submission.objects.get(name=name) self.assertEqual(submission.title, "some title") @@ -676,14 +678,14 @@ class SubmitTests(TestCase): self.assertEqual(submission.replaces, draft.docalias_set.all().first().name) self.assertEqual(submission.state_id, "manual") - authors = submission.authors_parsed() + authors = submission.authors self.assertEqual(len(authors), 3) self.assertEqual(authors[0]["name"], "Person 1") self.assertEqual(authors[0]["email"], "person1@example.com") self.assertEqual(authors[1]["name"], "Person 2") self.assertEqual(authors[1]["email"], "person2@example.com") self.assertEqual(authors[2]["name"], "Person 3") - self.assertEqual(authors[2]["email"], "unknown-email-Person-3") + self.assertEqual(authors[2]["email"], "") self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Manual Post Requested" in outbox[-1]["Subject"]) @@ -939,7 +941,6 @@ class SubmitTests(TestCase): files = {"txt": submission_file(name, rev, group, "txt", "test_submission.nonascii", author=author) } r = self.client.post(url, files) - self.assertEqual(r.status_code, 302) status_url = r["Location"] r = self.client.get(status_url) @@ -1443,8 +1444,8 @@ Subject: test 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(len(submission.authors), 1) + author = submission.authors[0] self.assertEqual(author["name"], "Author Name") self.assertEqual(author["email"], "author@example.com") diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py index ad5f02c6a..54852349d 100644 --- a/ietf/submit/utils.py +++ b/ietf/submit/utils.py @@ -42,7 +42,7 @@ def validate_submission(submission): if not submission.abstract: errors['abstract'] = 'Abstract is empty or was not found' - if not submission.authors_parsed(): + if not submission.authors: errors['authors'] = 'No authors found' # revision @@ -427,14 +427,16 @@ def ensure_person_email_info_exists(name, email): def update_authors(draft, submission): persons = [] - for order, author in enumerate(submission.authors_parsed()): - person, email = ensure_person_email_info_exists(author["name"], author["email"]) + for order, author in enumerate(submission.authors): + person, email = ensure_person_email_info_exists(author["name"], author.get("email")) a = DocumentAuthor.objects.filter(document=draft, person=person).first() if not a: a = DocumentAuthor(document=draft, person=person) a.email = email + a.affiliation = author.get("affiliation") or "" + a.country = author.get("country") or "" a.order = order a.save() diff --git a/ietf/submit/views.py b/ietf/submit/views.py index 05a1aa379..bd2305779 100644 --- a/ietf/submit/views.py +++ b/ietf/submit/views.py @@ -21,7 +21,7 @@ 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.name.models import FormalLanguageName -from ietf.submit.forms import ( SubmissionUploadForm, NameEmailForm, EditSubmissionForm, +from ietf.submit.forms import ( SubmissionUploadForm, AuthorForm, SubmitterForm, 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 ) @@ -83,8 +83,7 @@ def upload_submission(request): for author in form.parsed_draft.get_author_list(): full_name, first_name, middle_initial, last_name, name_suffix, email, company = author - line = full_name.replace("\n", "").replace("\r", "").replace("<", "").replace(">", "").strip() - email = (email or "").strip() + name = full_name.replace("\n", "").replace("\r", "").replace("<", "").replace(">", "").strip() if email: try: @@ -92,29 +91,31 @@ def upload_submission(request): except ValidationError: email = "" - if email: - # Try various ways of handling name and email, in order to avoid - # triggering a 500 error here. If the document contains non-ascii - # characters, it will be flagged later by the idnits check. - try: - line += u" <%s>" % email - except UnicodeDecodeError: + def turn_into_unicode(s): + if s is None: + return u"" + + if isinstance(s, unicode): + return s + else: try: - line = line.decode('utf-8') - email = email.decode('utf-8') - line += u" <%s>" % email + return s.decode("utf-8") except UnicodeDecodeError: try: - line = line.decode('latin-1') - email = email.decode('latin-1') - line += u" <%s>" % email + return s.decode("latin-1") except UnicodeDecodeError: - try: - line += " <%s>" % email - except UnicodeDecodeError: - pass + return "" - authors.append(line) + name = turn_into_unicode(name) + email = turn_into_unicode(email) + company = turn_into_unicode(company) + + authors.append({ + "name": name, + "email": email, + "affiliation": company, + # FIXME: missing country + }) if form.abstract: abstract = form.abstract @@ -143,7 +144,7 @@ def upload_submission(request): submission.abstract = abstract submission.pages = form.parsed_draft.get_pagecount() submission.words = form.parsed_draft.get_wordcount() - submission.authors = "\n".join(authors) + submission.authors = authors submission.first_two_pages = ''.join(form.parsed_draft.pages[:2]) submission.file_size = file_size submission.file_types = ','.join(form.file_types) @@ -260,7 +261,7 @@ def submission_status(request, submission_id, access_token=None): doc = submission.existing_document() if doc and doc.group: old_authors = [ author.person for author in doc.documentauthor_set.all() ] - new_authors = [ get_person_from_name_email(**p) for p in submission.authors_parsed() ] + new_authors = [ get_person_from_name_email(author["name"], author.get("email")) for author in submission.authors ] group_authors_changed = set(old_authors)!=set(new_authors) message = None @@ -275,7 +276,7 @@ def submission_status(request, submission_id, access_token=None): message = ('success', 'The submission is pending approval by the authors of the previous version. An email has been sent to: %s' % ", ".join(confirmation_list)) - submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter") + submitter_form = SubmitterForm(initial=submission.submitter_parsed(), prefix="submitter") replaces_form = ReplacesForm(name=submission.name,initial=DocAlias.objects.filter(name__in=submission.replaces.split(","))) if request.method == 'POST': @@ -284,7 +285,7 @@ def submission_status(request, submission_id, access_token=None): if not can_edit: return HttpResponseForbidden("You do not have permission to perform this action") - submitter_form = NameEmailForm(request.POST, prefix="submitter") + submitter_form = SubmitterForm(request.POST, prefix="submitter") replaces_form = ReplacesForm(request.POST, name=submission.name) validations = [submitter_form.is_valid(), replaces_form.is_valid()] if all(validations): @@ -432,7 +433,7 @@ def edit_submission(request, submission_id, access_token=None): # submission itself, one for the submitter, and a list of forms # for the authors - empty_author_form = NameEmailForm(email_required=False) + empty_author_form = AuthorForm() if request.method == 'POST': # get a backup submission now, the model form may change some @@ -440,9 +441,9 @@ def edit_submission(request, submission_id, access_token=None): prev_submission = Submission.objects.get(pk=submission.pk) edit_form = EditSubmissionForm(request.POST, instance=submission, prefix="edit") - submitter_form = NameEmailForm(request.POST, prefix="submitter") + submitter_form = SubmitterForm(request.POST, prefix="submitter") replaces_form = ReplacesForm(request.POST,name=submission.name) - author_forms = [ NameEmailForm(request.POST, email_required=False, prefix=prefix) + author_forms = [ AuthorForm(request.POST, prefix=prefix) for prefix in request.POST.getlist("authors-prefix") if prefix != "authors-" ] @@ -454,9 +455,9 @@ def edit_submission(request, submission_id, access_token=None): submission.submitter = submitter_form.cleaned_line() replaces = replaces_form.cleaned_data.get("replaces", []) submission.replaces = ",".join(o.name for o in replaces) - submission.authors = "\n".join(f.cleaned_line() for f in author_forms) - if hasattr(submission, '_cached_authors_parsed'): - del submission._cached_authors_parsed + submission.authors = [ { attr: f.cleaned_data.get(attr) or "" + for attr in ["name", "email", "affiliation", "country"] } + for f in author_forms ] edit_form.save(commit=False) # transfer changes if submission.rev != prev_submission.rev: @@ -491,10 +492,10 @@ def edit_submission(request, submission_id, access_token=None): form_errors = True else: edit_form = EditSubmissionForm(instance=submission, prefix="edit") - submitter_form = NameEmailForm(initial=submission.submitter_parsed(), prefix="submitter") + submitter_form = SubmitterForm(initial=submission.submitter_parsed(), prefix="submitter") replaces_form = ReplacesForm(name=submission.name,initial=DocAlias.objects.filter(name__in=submission.replaces.split(","))) - author_forms = [ NameEmailForm(initial=author, email_required=False, prefix="authors-%s" % i) - for i, author in enumerate(submission.authors_parsed()) ] + author_forms = [ AuthorForm(initial=author, prefix="authors-%s" % i) + for i, author in enumerate(submission.authors) ] return render(request, 'submit/edit_submission.html', {'selected': 'status', diff --git a/ietf/templates/submit/announce_to_lists.txt b/ietf/templates/submit/announce_to_lists.txt index 0d9817d57..30939a8d0 100644 --- a/ietf/templates/submit/announce_to_lists.txt +++ b/ietf/templates/submit/announce_to_lists.txt @@ -3,7 +3,7 @@ A New Internet-Draft is available from the on-line Internet-Drafts directories. {% if submission.group %}This draft is a work item of the {{ submission.group.name }}{% if group.type.name %} {{ group.type.name }}{% endif %} of the {% if group.type_id == "rg" %}IRTF{% else %}IETF{% endif %}.{% endif %} Title : {{ submission.title }} - Author{{ submission.authors_parsed|pluralize:" ,s" }} : {% for author in submission.authors_parsed %}{{ author.name }}{% if not forloop.last %} + Author{{ submission.authors|pluralize:" ,s" }} : {% for author in submission.authors %}{{ author.name }}{% if not forloop.last %} {% endif %}{% endfor %} Filename : {{ submission.name }}-{{ submission.rev }}.txt Pages : {{ submission.pages }} diff --git a/ietf/templates/submit/approval_request.txt b/ietf/templates/submit/approval_request.txt index 83677d166..828b14bc4 100644 --- a/ietf/templates/submit/approval_request.txt +++ b/ietf/templates/submit/approval_request.txt @@ -22,7 +22,7 @@ To approve the draft, go to this URL (note: you need to login to be able to appr Authors: -{% for author in submission.authors_parsed %} {{ author.name }}{% if author.email %} <{{ author.email }}>{% endif%} +{% for author in submission.authors %} {{ author.name }}{% if author.email %} <{{ author.email }}>{% endif%} {% endfor %} {% endautoescape %} diff --git a/ietf/templates/submit/manual_post_request.txt b/ietf/templates/submit/manual_post_request.txt index 1620a476f..8ff38a6fa 100644 --- a/ietf/templates/submit/manual_post_request.txt +++ b/ietf/templates/submit/manual_post_request.txt @@ -22,7 +22,7 @@ I-D Submission Tool URL: Authors: -{% for author in submission.authors_parsed %} {{ author.name }}{% if author.email %} <{{ author.email }}>{% endif%} +{% for author in submission.authors %} {{ author.name }}{% if author.email %} <{{ author.email }}>{% endif%} {% endfor %} Comment to the secretariat: diff --git a/ietf/templates/submit/submission_status.html b/ietf/templates/submit/submission_status.html index 4f67c29de..3bf65c096 100644 --- a/ietf/templates/submit/submission_status.html +++ b/ietf/templates/submit/submission_status.html @@ -2,7 +2,7 @@ {# Copyright The IETF Trust 2015, All Rights Reserved #} {% load origin %} {% load staticfiles %} -{% load ietf_filters submit_tags %} +{% load ietf_filters submit_tags country %} {% block title %}Submission status of {{ submission.name }}-{{ submission.rev }}{% endblock %} @@ -195,17 +195,21 @@ Authors - {% with submission.authors_parsed as authors %} + {% with submission.authors as authors %} {{ authors|length }} author{{ authors|pluralize }} {% endwith %} {% if errors.authors %}

{{ errors.authors|safe }}

{% endif %} - {% for author in submission.authors_parsed %} + {% for author in submission.authors %} Author {{ forloop.counter }} - {{ author.name }} {% if author.email %}<{{ author.email }}>{% endif %} + + {{ author.name }} {% if author.email %}<{{ author.email }}>{% endif %} + {% if author.affiliation %}- {{ author.affiliation }}{% endif %} + {% if author.country %}- {{ author.country|country_name }}{% endif %} + {% endfor %} diff --git a/ietf/templates/submit/submitter_form.html b/ietf/templates/submit/submitter_form.html index 93994c10c..1564dd3e5 100644 --- a/ietf/templates/submit/submitter_form.html +++ b/ietf/templates/submit/submitter_form.html @@ -11,8 +11,8 @@ {% load ietf_filters %} {% buttons %} - {% for author in submission.authors_parsed %} - + {% for author in submission.authors %} + {% endfor %} {% endbuttons %} diff --git a/ietf/utils/templatetags/country.py b/ietf/utils/templatetags/country.py new file mode 100644 index 000000000..7d730d2f9 --- /dev/null +++ b/ietf/utils/templatetags/country.py @@ -0,0 +1,14 @@ +from django.template.base import Library +from django.template.defaultfilters import stringfilter + +from django_countries import countries + +register = Library() + +@register.filter(is_safe=True) +@stringfilter +def country_name(value): + """ + Converts country code to country name + """ + return dict(countries).get(value, "")