------------------------------------------------------------
revno: 6517
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: 3.0
timestamp: Sun 2007-07-01 11:51:09 -0400
message:
  Support for case-preserving addresses.  When an Address is given an email
  address that is not lower cased, the original, case-preserved version is store
  on the '_original' attribute.  The lower-cased version is always used as the
  key and thus always stored on the 'address' attribute.  The IAddress interface
  is given a new 'original_address' property which returns the case-preserved
  version.
  
  Address's __str__() and __repr__() are similarly modified.  The former always
  includes the case-preserved address; the latter does too, but now also
  includes the lower-cased 'key' email address (along with the object's id).
  
  Searching for an address always does so on the lower-cased version.
  
  Test suite is updated as necessary.  Also, I'm adding the
  REPORT_ONLY_FIRST_FAILURE doctest flag so that it's easier to debug doctest
  failures without having pages of problems to scroll through.
modified:
  Mailman/database/model/address.py
  Mailman/database/usermanager.py
  Mailman/docs/addresses.txt
  Mailman/docs/users.txt
  Mailman/interfaces/address.py
  Mailman/testing/test_documentation.py

=== modified file 'Mailman/database/model/address.py'
--- a/Mailman/database/model/address.py 2007-06-16 02:37:33 +0000
+++ b/Mailman/database/model/address.py 2007-07-01 15:51:09 +0000
@@ -31,6 +31,7 @@
     implements(IAddress)
 
     has_field('address',        Unicode)
+    has_field('_original',      Unicode)
     has_field('real_name',      Unicode)
     has_field('verified',       Boolean)
     has_field('registered_on',  DateTime)
@@ -41,12 +42,26 @@
     # Options
     using_options(shortnames=True)
 
+    def __init__(self, address, real_name):
+        super(Address, self).__init__()
+        lower_case = address.lower()
+        self.address = lower_case
+        self.real_name = real_name
+        self._original = (None if lower_case == address else address)
+
     def __str__(self):
-        return formataddr((self.real_name, self.address))
+        addr = (self.address if self._original is None else self._original)
+        return formataddr((self.real_name, addr))
 
     def __repr__(self):
-        return '<Address: %s [%s]>' % (
-            str(self), ('verified' if self.verified else 'not verified'))
+        verified = ('verified' if self.verified else 'not verified')
+        address_str = str(self)
+        if self._original is None:
+            return '<Address: %s [%s] at %#x>' % (
+                address_str, verified, id(self))
+        else:
+            return '<Address: %s [%s] key: %s at %#x>' % (
+                address_str, verified, self.address, id(self))
 
     def subscribe(self, mlist, role):
         from Mailman.database.model import Member
@@ -57,3 +72,7 @@
                         address=self)
         member.preferences = Preferences()
         return member
+
+    @property
+    def original_address(self):
+        return (self.address if self._original is None else self._original)

=== modified file 'Mailman/database/usermanager.py'
--- a/Mailman/database/usermanager.py   2007-06-16 02:37:33 +0000
+++ b/Mailman/database/usermanager.py   2007-07-01 15:51:09 +0000
@@ -39,7 +39,7 @@
         user = User()
         user.real_name = (real_name if real_name is not None else '')
         if address:
-            addrobj = Address(address=address, real_name=user.real_name)
+            addrobj = Address(address, user.real_name)
             addrobj.preferences = Preferences()
             user.link(addrobj)
         user.preferences = Preferences()
@@ -54,16 +54,16 @@
             yield user
 
     def get_user(self, address):
-        found = Address.get_by(address=address)
+        found = Address.get_by(address=address.lower())
         return found and found.user
 
     def create_address(self, address, real_name=None):
-        found = Address.get_by(address=address)
+        found = Address.get_by(address=address.lower())
         if found:
-            raise Errors.ExistingAddressError(address)
+            raise Errors.ExistingAddressError(found.original_address)
         if real_name is None:
             real_name = ''
-        address = Address(address=address, real_name=real_name)
+        address = Address(address, real_name)
         address.preferences = Preferences()
         return address
 
@@ -75,7 +75,7 @@
         address.delete()
 
     def get_address(self, address):
-        return Address.get_by(address=address)
+        return Address.get_by(address=address.lower())
 
     @property
     def addresses(self):

=== modified file 'Mailman/docs/addresses.txt'
--- a/Mailman/docs/addresses.txt        2007-06-22 21:15:03 +0000
+++ b/Mailman/docs/addresses.txt        2007-07-01 15:51:09 +0000
@@ -41,6 +41,14 @@
     >>> sorted(address.real_name for address in mgr.addresses)
     ['', 'Ben Person']
 
+The str() of the address is the RFC 2822 preferred originator format, while
+the repr() carries more information.
+
+    >>> str(address_2)
+    'Ben Person <[EMAIL PROTECTED]>'
+    >>> repr(address_2)
+    '<Address: Ben Person <[EMAIL PROTECTED]> [not verified] at 0x...>'
+
 You can assign real names to existing addresses.
 
     >>> address_1.real_name = 'Anne Person'
@@ -156,7 +164,7 @@
              [EMAIL PROTECTED] as MemberRole.member>
     >>> flush()
 
-Now that Elly is both an owner and a member of the mailing list.
+Now Elly is both an owner and a member of the mailing list.
 
     >>> sorted(mlist.owners.members)
     [<Member: Elly Person <[EMAIL PROTECTED]> on
@@ -174,3 +182,57 @@
               [EMAIL PROTECTED] as MemberRole.member>]
     >>> sorted(mlist.digest_members.members)
     []
+
+
+Case-preserved addresses
+------------------------
+
+Technically speaking, email addresses are case sensitive in the local part.
+Mailman preserves the case of addresses and uses the case preserved version
+when sending the user a message, but it treats addresses that are different in
+case equivalently in all other situations.
+
+    >>> address_6 = mgr.create_address('[EMAIL PROTECTED]', 'Frank Person')
+    >>> flush()
+
+The str() of such an address prints the RFC 2822 preferred originator format
+with the original case-preserved address.  The repr() contains all the gory
+details.
+
+    >>> str(address_6)
+    'Frank Person <[EMAIL PROTECTED]>'
+    >>> repr(address_6)
+    '<Address: Frank Person <[EMAIL PROTECTED]> [not verified]
+               key: [EMAIL PROTECTED] at 0x...>'
+
+Both the case-insensitive version of the address and the original
+case-preserved version are available on attributes of the IAddress object.
+
+    >>> address_6.address
+    '[EMAIL PROTECTED]'
+    >>> address_6.original_address
+    '[EMAIL PROTECTED]'
+
+Because addresses are case-insensitive for all other purposes, you cannot
+create an address that differs only in case.
+
+    >>> mgr.create_address('[EMAIL PROTECTED]')
+    Traceback (most recent call last):
+    ...
+    ExistingAddressError: [EMAIL PROTECTED]
+    >>> mgr.create_address('[EMAIL PROTECTED]')
+    Traceback (most recent call last):
+    ...
+    ExistingAddressError: [EMAIL PROTECTED]
+    >>> mgr.create_address('[EMAIL PROTECTED]')
+    Traceback (most recent call last):
+    ...
+    ExistingAddressError: [EMAIL PROTECTED]
+
+You can get the address using either the lower cased version or case-preserved
+version.  In fact, searching for an address is case insensitive.
+
+    >>> mgr.get_address('[EMAIL PROTECTED]').address
+    '[EMAIL PROTECTED]'
+    >>> mgr.get_address('[EMAIL PROTECTED]').address
+    '[EMAIL PROTECTED]'

=== modified file 'Mailman/docs/users.txt'
--- a/Mailman/docs/users.txt    2007-06-22 21:15:03 +0000
+++ b/Mailman/docs/users.txt    2007-07-01 15:51:09 +0000
@@ -48,9 +48,9 @@
 address on a user object.
 
     >>> user_1.register('[EMAIL PROTECTED]', 'Zoe Person')
-    <Address: Zoe Person <[EMAIL PROTECTED]> [not verified]>
+    <Address: Zoe Person <[EMAIL PROTECTED]> [not verified] at 0x...>
     >>> user_1.register('[EMAIL PROTECTED]')
-    <Address: [EMAIL PROTECTED] [not verified]>
+    <Address: [EMAIL PROTECTED] [not verified] at 0x...>
     >>> flush()
     >>> sorted(address.address for address in user_1.addresses)
     ['[EMAIL PROTECTED]', '[EMAIL PROTECTED]']

=== modified file 'Mailman/interfaces/address.py'
--- a/Mailman/interfaces/address.py     2007-06-15 04:50:40 +0000
+++ b/Mailman/interfaces/address.py     2007-07-01 15:51:09 +0000
@@ -27,6 +27,19 @@
     address = Attribute(
         """Read-only text email address.""")
 
+    original_address = Attribute(
+        """Read-only original case-preserved address.
+
+        For almost all intents and purposes, addresses in Mailman are case
+        insensitive, however because RFC 2821 allows for case sensitive local
+        parts, Mailman preserves the case of the original address when
+        emailing the user.
+
+        `original_address` will be the same as address if the original address
+        was all lower case.  Otherwise `original_address` will be the case
+        preserved address; `address` will always be lower case.
+        """)
+
     real_name = Attribute(
         """Optional real name associated with the email address.""")
 

=== modified file 'Mailman/testing/test_documentation.py'
--- a/Mailman/testing/test_documentation.py     2007-06-28 03:01:56 +0000
+++ b/Mailman/testing/test_documentation.py     2007-07-01 15:51:09 +0000
@@ -67,7 +67,8 @@
                 package=Mailman,
                 optionflags=(doctest.ELLIPSIS
                              | doctest.NORMALIZE_WHITESPACE
-                             | doctest.REPORT_NDIFF),
+                             | doctest.REPORT_NDIFF
+                             | doctest.REPORT_ONLY_FIRST_FAILURE),
                 tearDown=cleaning_teardown)
             suite.addTest(test)
     return suite



--
(no title)
https://code.launchpad.net/~mailman-coders/mailman/3.0

You are receiving this branch notification because you are subscribed to it.
To unsubscribe from this branch go to 
https://code.launchpad.net/~mailman-coders/mailman/3.0/+subscription/mailman-checkins.
_______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to