Reworked the email address handling in order to be able to support non-ascii names as part of email address fields. Reworked the generation of user names in the test suite to generate names from multiple non-ascii locales. Fixes issue #2080.

- Legacy-Id: 12872
This commit is contained in:
Henrik Levkowetz 2017-02-18 21:50:18 +00:00
parent a78c419845
commit cf4a4b02a7
13 changed files with 95 additions and 33 deletions

View file

@ -1,15 +1,18 @@
# Copyright The IETF Trust 2007, All Rights Reserved
import datetime
import email.utils
from urlparse import urljoin
from django.db import models
import debug # pyflakes:ignore
from ietf.group.colors import fg_group_colors, bg_group_colors
from ietf.name.models import GroupStateName, GroupTypeName, DocTagName, GroupMilestoneStateName, RoleName
from ietf.person.models import Email, Person
from ietf.utils.mail import formataddr
import debug # pyflakes:ignore
class GroupInfo(models.Model):
time = models.DateTimeField(default=datetime.datetime.now)
@ -254,8 +257,11 @@ class Role(models.Model):
def __unicode__(self):
return u"%s is %s in %s" % (self.person.plain_name(), self.name.name, self.group.acronym or self.group.name)
def formatted_ascii_email(self):
return email.utils.formataddr((self.person.plain_ascii(), self.email.address))
def formatted_email(self):
return u'"%s" <%s>' % (self.person.plain_name(), self.email.address)
return formataddr((self.person.plain_name(), self.email.address))
class RoleHistory(models.Model):
# RoleHistory doesn't have a time field as it's not supposed to be

View file

@ -127,9 +127,9 @@ def all_id2_txt():
else:
l.append(a.author.person.plain_name())
shepherds = dict((e.pk, e.formatted_email().replace('"', ''))
shepherds = dict((e.pk, e.formatted_ascii_email().replace('"', ''))
for e in Email.objects.filter(shepherd_document_set__type="draft").select_related("person").distinct())
ads = dict((p.pk, p.formatted_email().replace('"', ''))
ads = dict((p.pk, p.formatted_ascii_email().replace('"', ''))
for p in Person.objects.filter(ad_document_set__type="draft").distinct())
res = []

View file

@ -4,6 +4,10 @@ from django.db import models
from django.template import Template, Context
from email.utils import parseaddr
from ietf.utils.mail import formataddr
import debug # pyflakes:ignore
from ietf.group.models import Role
@ -14,7 +18,7 @@ def clean_duplicates(addrlist):
if (name,addr)==('',''):
retval.add(a)
elif name:
retval.add('"%s" <%s>'%(name,addr))
retval.add(formataddr((name,addr)))
else:
retval.add(addr)
return list(retval)
@ -200,7 +204,7 @@ class Recipient(models.Model):
doc=submission.existing_document()
if doc:
old_authors = [i.author.formatted_email() for i in doc.documentauthor_set.all() if not i.author.invalid_address()]
new_authors = [u'"%s" <%s>' % (author["name"], author["email"]) for author in submission.authors_parsed() if author["email"]]
new_authors = [ formataddr((author["name"], author["email"])) for author in submission.authors_parsed() if author["email"]]
addrs.extend(old_authors)
if doc.group and set(old_authors)!=set(new_authors):
if doc.group.type_id in ['wg','rg','ag']:

View file

@ -5,15 +5,15 @@ from django import template
from django.conf import settings
from django.template.defaultfilters import linebreaksbr, force_escape
from ietf.utils.pipe import pipe
from ietf.utils.log import log
from ietf.doc.templatetags.ietf_filters import wrap_text
from ietf.person.models import Person
from ietf.nomcom.utils import get_nomcom_by_year, retrieve_nomcom_private_key
import debug # pyflakes:ignore
from ietf.doc.templatetags.ietf_filters import wrap_text
from ietf.nomcom.utils import get_nomcom_by_year, retrieve_nomcom_private_key
from ietf.person.models import Person
from ietf.utils.log import log
from ietf.utils.mail import formataddr
from ietf.utils.pipe import pipe
register = template.Library()
@ -41,7 +41,7 @@ def formatted_email(address):
persons = Person.objects.filter(email__address__in=[address])
person = persons and persons[0] or None
if person and person.name:
return u'"%s" <%s>' % (person.plain_name(), address)
return formataddr((person.plain_name(), address))
else:
return address

View file

@ -1705,7 +1705,7 @@ Junk body for testing
'duplicate_persons':[nominee2.person.pk]})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(outbox),1)
self.assertTrue(all([str(x.person.pk) in unicode(outbox[0]) for x in [nominee1,nominee2]]))
self.assertTrue(all([str(x.person.pk) in outbox[0].get_payload(decode=True) for x in [nominee1,nominee2]]))
class NomComIndexTests(TestCase):

View file

@ -2,11 +2,15 @@ import os
import factory
import faker
import shutil
import random
import faker.config
from unidecode import unidecode
from django.conf import settings
from django.contrib.auth.models import User
import debug # pyflakes:ignore
from ietf.person.models import Person, Alias, Email
fake = faker.Factory.create()
@ -15,10 +19,12 @@ class UserFactory(factory.DjangoModelFactory):
class Meta:
model = User
django_get_or_create = ('username',)
exclude = ['locale', ]
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.LazyAttributeSequence(lambda u, n: '%s.%s_%d@%s'%(u.first_name,u.last_name,n,fake.domain_name()))
locale = random.sample(faker.config.AVAILABLE_LOCALES, 1)[0]
first_name = factory.Faker('first_name', locale)
last_name = factory.Faker('last_name', locale)
email = factory.LazyAttributeSequence(lambda u, n: '%s.%s_%d@%s'%(unidecode(u.first_name),unidecode(u.last_name),n, fake.domain_name()))
username = factory.LazyAttribute(lambda u: u.email)
@factory.post_generation

View file

@ -1,4 +1,5 @@
import json
import six
from collections import Counter
from urllib import urlencode
@ -108,7 +109,7 @@ class SearchablePersonsField(forms.CharField):
#if self.only_users:
# objs = objs.exclude(person__user=None)
found_pks = [str(o.pk) for o in objs]
found_pks = [ six.text_type(o.pk) for o in objs]
failed_pks = [x for x in pks if x not in found_pks]
if failed_pks:
raise forms.ValidationError(u"Could not recognize the following {model_name}s: {pks}. You can only input {model_name}s already registered in the Datatracker.".format(pks=", ".join(failed_pks), model_name=self.model.__name__.lower()))

View file

@ -1,6 +1,8 @@
# Copyright The IETF Trust 2007, All Rights Reserved
import datetime
import email.utils
import email.header
from hashids import Hashids
from unidecode import unidecode
from urlparse import urljoin
@ -17,6 +19,8 @@ import debug # pyflakes:ignore
from ietf.person.name import name_parts, initials
from ietf.utils.mail import send_mail_preformatted
from ietf.utils.storage import NoLocationMigrationFileSystemStorage
from ietf.utils.mail import formataddr
class PersonInfo(models.Model):
time = models.DateTimeField(default=datetime.datetime.now) # When this Person record entered the system
@ -106,6 +110,14 @@ class PersonInfo(models.Model):
return e.address
else:
return ""
def formatted_ascii_email(self):
e = self.email_set.filter(primary=True).first()
if not e:
e = self.email_set.order_by("-active", "-time").first()
if e:
return e.formatted_ascii_email()
else:
return ""
def formatted_email(self):
e = self.email_set.filter(primary=True).first()
if not e:
@ -225,9 +237,15 @@ class Email(models.Model):
def get_name(self):
return self.person.plain_name() if self.person else self.address
def formatted_ascii_email(self):
if self.person:
return email.utils.formataddr((self.person.plain_ascii(), self.address))
else:
return self.address
def formatted_email(self):
if self.person:
return u'"%s" <%s>' % (self.person.plain_ascii(), self.address)
return formataddr((self.person.plain_name(), self.address))
else:
return self.address

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
from pyquery import PyQuery
@ -41,7 +42,7 @@ class PersonTests(TestCase):
url = urlreverse("ietf.person.views.profile", kwargs={ "email_or_name": person.plain_name()})
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertIn(person.photo_name(), r.content)
self.assertIn(person.photo_name(), r.content.decode(r.charset))
q = PyQuery(r.content)
self.assertIn("Photo of %s"%person, q("div.bio-text img.bio-photo").attr("alt"))

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import pprint
from django.contrib import admin
@ -18,7 +19,7 @@ def merge_persons(source,target,stream):
if alias.name in target_aliases:
alias.delete()
else:
print >>stream,"Merging alias: {}".format(alias.name)
print >>stream, "Merging alias: {}".format(alias.name)
alias.person = target
alias.save()

View file

@ -358,8 +358,8 @@ class SubmitTests(TestCase):
if stream_type=='ise':
self.assertTrue("rfc-ise@" in confirm_email["To"].lower())
else:
self.assertTrue("chairs have been copied" not in unicode(confirm_email))
self.assertTrue("mars-chairs@" not in confirm_email["To"].lower())
self.assertNotIn("chairs have been copied", unicode(confirm_email))
self.assertNotIn("mars-chairs@", confirm_email["To"].lower())
confirm_url = self.extract_confirm_url(confirm_email)

View file

@ -1,6 +1,14 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from email.utils import make_msgid, formatdate, formataddr, parseaddr, getaddresses
import copy
import datetime
import smtplib
import sys
import textwrap
import time
import traceback
from email.utils import make_msgid, formatdate, formataddr as simple_formataddr, parseaddr, getaddresses
from email.mime.text import MIMEText
from email.mime.message import MIMEMessage
from email.mime.multipart import MIMEMultipart
@ -8,20 +16,17 @@ from email.header import Header
from email import message_from_string
from email import charset as Charset
import smtplib
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.template.loader import render_to_string
from django.template import Context,RequestContext
import debug # pyflakes:ignore
import ietf
from ietf.utils.log import log
import sys
import time
import copy
import textwrap
import traceback
import datetime
from ietf.utils.text import isascii
# Testing mode:
# import ietf.utils.mail
@ -189,9 +194,22 @@ def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=F
msg = encode_message(txt)
return send_mail_mime(request, to, frm, subject, msg, cc, extra, toUser, bcc)
def formataddr(addrtuple):
"""
Takes a name and email address, and inspects the name to see if it needs
to be encoded in an email.header.Header before being used in an email.message
address field. Does what's needed, and returns a string value suitable for
use in a To: or Cc: email header field.
"""
name, addr = addrtuple
if name and not isascii(name):
name = str(Header(name, 'utf-8'))
return simple_formataddr((name, addr))
def condition_message(to, frm, subject, msg, cc, extra):
if isinstance(frm, tuple):
frm = formataddr(frm)
frm = formataddr(frm)
if isinstance(to, list) or isinstance(to, tuple):
to = ", ".join([isinstance(addr, tuple) and formataddr(addr) or addr for addr in to if addr])
if isinstance(cc, list) or isinstance(cc, tuple):

View file

@ -49,3 +49,10 @@ def fill(text, width):
wrapped.append(para)
return "\n\n".join(wrapped)
def isascii(text):
try:
text.encode('ascii')
return True
except UnicodeEncodeError:
return False