diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 43853ca7..1f11b0a3 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -14,12 +14,13 @@ import smtplib import idna -class Idna(db.TypeDecorator): +class IdnaDomain(db.TypeDecorator): """ Stores a Unicode string in it's IDNA representation (ASCII only) """ impl = db.String + def process_bind_param(self, value, dialect): return idna.encode(value) @@ -27,10 +28,36 @@ class Idna(db.TypeDecorator): return idna.decode(value) +class IdnaEmail(db.TypeDecorator): + """ Stores a Unicode string in it's IDNA representation (ASCII only) + """ + + impl = db.String + + + def process_bind_param(self, value, dialect): + localpart, domain_name = value.split('@') + + email = "{0}@{1}".format( + localpart, + idna.encode(domain_name).decode('ascii'), + ) + return email + + def process_result_value(self, value, dialect): + localpart, domain_name = value.split('@') + + email = "{0}@{1}".format( + localpart, + idna.decode(domain_name), + ) + return email + + # Many-to-many association table for domain managers managers = db.Table('manager', - db.Column('domain_name', Idna, db.ForeignKey('domain.name')), - db.Column('user_email', db.String(255), db.ForeignKey('user.email')) + db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')), + db.Column('user_email', IdnaEmail, db.ForeignKey('user.email')) ) @@ -40,6 +67,7 @@ class CommaSeparatedList(db.TypeDecorator): impl = db.String + def process_bind_param(self, value, dialect): if type(value) is not list: raise TypeError("Shoud be a list") @@ -68,7 +96,7 @@ class Domain(Base): """ __tablename__ = "domain" - name = db.Column(Idna, primary_key=True, nullable=False) + name = db.Column(IdnaDomain, primary_key=True, nullable=False) managers = db.relationship('User', secondary=managers, backref=db.backref('manager_of'), lazy='dynamic') max_users = db.Column(db.Integer, nullable=False, default=0) @@ -124,8 +152,8 @@ class Alternative(Base): __tablename__ = "alternative" - name = db.Column(Idna, primary_key=True, nullable=False) - domain_name = db.Column(Idna, db.ForeignKey(Domain.name)) + name = db.Column(IdnaDomain, primary_key=True, nullable=False) + domain_name = db.Column(IdnaDomain, db.ForeignKey(Domain.name)) domain = db.relationship(Domain, backref=db.backref('alternatives', cascade='all, delete-orphan')) @@ -155,19 +183,19 @@ class Email(object): @declarative.declared_attr def domain_name(cls): - return db.Column(Idna, db.ForeignKey(Domain.name), - nullable=False) + return db.Column(IdnaDomain, db.ForeignKey(Domain.name), + nullable=False, default=IdnaDomain) # This field is redundant with both localpart and domain name. # It is however very useful for quick lookups without joining tables, - # especially when the mail server il reading the database. + # especially when the mail server is reading the database. @declarative.declared_attr def email(cls): updater = lambda context: "{0}@{1}".format( context.current_parameters["localpart"], - idna.encode(context.current_parameters["domain_name"]).decode('ascii'), + context.current_parameters["domain_name"], ) - return db.Column(db.String(255, collation="NOCASE"), + return db.Column(IdnaEmail, primary_key=True, nullable=False, default=updater)