Cache:
[plcapi.git] / PLC / Persons.py
index a3c38b4..2f77d90 100644 (file)
@@ -4,7 +4,7 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: Persons.py,v 1.7 2006/10/02 18:32:31 mlhuang Exp $
+# $Id: Persons.py,v 1.23 2006/11/30 10:12:01 thierry Exp $
 #
 
 from types import StringTypes
@@ -17,10 +17,8 @@ import crypt
 
 from PLC.Faults import *
 from PLC.Parameter import Parameter
-from PLC.Debug import profile
+from PLC.Filter import Filter
 from PLC.Table import Row, Table
-from PLC.Roles import Roles
-from PLC.Addresses import Address, Addresses
 from PLC.Keys import Key, Keys
 import PLC.Sites
 
@@ -33,29 +31,41 @@ class Person(Row):
 
     table_name = 'persons'
     primary_key = 'person_id'
+    join_tables = ['person_role', 'person_site', 'slice_person', 'person_session']
     fields = {
         'person_id': Parameter(int, "Account identifier"),
         'first_name': Parameter(str, "Given name", max = 128),
         'last_name': Parameter(str, "Surname", max = 128),
-        'title': Parameter(str, "Title", max = 128),
+        'title': Parameter(str, "Title", max = 128, nullok = True),
         'email': Parameter(str, "Primary e-mail address", max = 254),
-        'phone': Parameter(str, "Telephone number", max = 64),
-        'url': Parameter(str, "Home page", max = 254),
-        'bio': Parameter(str, "Biography", max = 254),
+        'phone': Parameter(str, "Telephone number", max = 64, nullok = True),
+        'url': Parameter(str, "Home page", max = 254, nullok = True),
+        'bio': Parameter(str, "Biography", max = 254, nullok = True),
         'enabled': Parameter(bool, "Has been enabled"),
         'password': Parameter(str, "Account password in crypt() form", max = 254),
-        'last_updated': Parameter(str, "Date and time of last update", ro = True),
-        'date_created': Parameter(str, "Date and time when account was created", ro = True),
-        'role_ids': Parameter([int], "List of role identifiers", ro = True),
-        'roles': Parameter([str], "List of roles", ro = True),
-        'site_ids': Parameter([int], "List of site identifiers", ro = True),
-        'key_ids': Parameter([int], "List of key identifiers", ro = True),
-        'slice_ids': Parameter([int], "List of slice identifiers", ro = True),
+        'last_updated': Parameter(int, "Date and time of last update", ro = True),
+        'date_created': Parameter(int, "Date and time when account was created", ro = True),
+        'role_ids': Parameter([int], "List of role identifiers"),
+        'roles': Parameter([str], "List of roles"),
+        'site_ids': Parameter([int], "List of site identifiers"),
+        'key_ids': Parameter([int], "List of key identifiers"),
+        'slice_ids': Parameter([int], "List of slice identifiers"),
+        'peer_id': Parameter(int, "Peer at which this slice was created", nullok = True),
         }
 
-    def __init__(self, api, fields = {}):
-        Row.__init__(self, fields)
-        self.api = api
+    # for Cache
+    class_key = 'email'
+    foreign_fields = ['first_name', 'last_name', 'title', 'email', 'phone', 'url',
+                     'bio', 'enabled', 'password', ]
+    # forget about these ones, they are read-only anyway
+    # handling them causes Cache to re-sync all over again 
+    # 'last_updated', 'date_created'
+    foreign_xrefs = [
+        {'field' : 'key_ids',  'class': 'Key',  'table' : 'person_key' } ,
+        {'field' : 'site_ids', 'class': 'Site', 'table' : 'person_site'},
+#       xxx this is not handled by Cache yet
+#        'role_ids': Parameter([int], "List of role identifiers"),
+]
 
     def validate_email(self, email):
         """
@@ -86,8 +96,8 @@ class Person(Row):
             raise invalid_email
 
         conflicts = Persons(self.api, [email])
-        for person_id, person in conflicts.iteritems():
-            if 'person_id' not in self or self['person_id'] != person_id:
+        for person in conflicts:
+            if 'person_id' not in self or self['person_id'] != person['person_id']:
                 raise PLCInvalidArgument, "E-mail address already in use"
 
         return email
@@ -109,6 +119,13 @@ class Person(Row):
             salt = md5.md5(salt).hexdigest()[:8] 
             return crypt.crypt(password.encode(self.api.encoding), magic + salt + "$")
 
+    # timestamps
+    # verification_expires in the DB but not exposed here
+    def validate_date_created (self, timestamp):
+       return self.validate_timestamp (timestamp)
+    def validate_last_updated (self, timestamp):
+       return self.validate_timestamp (timestamp)
+
     def can_update(self, person):
         """
         Returns true if we can update the specified person. We can
@@ -165,15 +182,15 @@ class Person(Row):
         assert 'person_id' in self
 
         person_id = self['person_id']
-        self.api.db.do("INSERT INTO person_role (person_id, role_id)" \
-                       " VALUES(%(person_id)d, %(role_id)d)",
-                       locals())
-
-        if commit:
-            self.api.db.commit()
 
-        assert 'role_ids' in self
         if role_id not in self['role_ids']:
+            self.api.db.do("INSERT INTO person_role (person_id, role_id)" \
+                           " VALUES(%(person_id)d, %(role_id)d)",
+                           locals())
+
+            if commit:
+                self.api.db.commit()
+
             self['role_ids'].append(role_id)
 
     def remove_role(self, role_id, commit = True):
@@ -184,17 +201,62 @@ class Person(Row):
         assert 'person_id' in self
 
         person_id = self['person_id']
-        self.api.db.do("DELETE FROM person_role" \
-                       " WHERE person_id = %(person_id)d" \
-                       " AND role_id = %(role_id)d",
-                       locals())
 
-        if commit:
-            self.api.db.commit()
-
-        assert 'role_ids' in self
         if role_id in self['role_ids']:
+            self.api.db.do("DELETE FROM person_role" \
+                           " WHERE person_id = %(person_id)d" \
+                           " AND role_id = %(role_id)d",
+                           locals())
+
+            if commit:
+                self.api.db.commit()
+
             self['role_ids'].remove(role_id)
+    def add_key(self, key, commit = True):
+        """
+        Add key to existing account.
+        """
+
+        assert 'person_id' in self
+        assert isinstance(key, Key)
+        assert 'key_id' in key
+
+        person_id = self['person_id']
+        key_id = key['key_id']
+
+        if key_id not in self['key_ids']:
+            self.api.db.do("INSERT INTO person_key (person_id, key_id)" \
+                           " VALUES(%(person_id)d, %(key_id)d)",
+                           locals())
+
+            if commit:
+                self.api.db.commit()
+
+            self['key_ids'].append(key_id)
+
+    def remove_key(self, key, commit = True):
+        """
+        Remove key from existing account.
+        """
+
+        assert 'person_id' in self
+        assert isinstance(key, Key)
+        assert 'key_id' in key
+
+        person_id = self['person_id']
+        key_id = key['key_id']
+
+        if key_id in self['key_ids']:
+            self.api.db.do("DELETE FROM person_key" \
+                           " WHERE person_id = %(person_id)d" \
+                           " AND key_id = %(key_id)d",
+                           locals())
+
+            if commit:
+                self.api.db.commit()
+
+            self['key_ids'].remove(key_id)
 
     def set_primary_site(self, site, commit = True):
         """
@@ -232,13 +294,12 @@ class Person(Row):
 
         # Delete all keys
         keys = Keys(self.api, self['key_ids'])
-        for key in keys.values():
+        for key in keys:
             key.delete(commit = False)
 
         # Clean up miscellaneous join tables
-        for table in ['person_role', 'person_site', 'slice_person']:
-            self.api.db.do("DELETE FROM %s" \
-                           " WHERE person_id = %d" % \
+        for table in self.join_tables:
+            self.api.db.do("DELETE FROM %s WHERE person_id = %d" % \
                            (table, self['person_id']))
 
         # Mark as deleted
@@ -248,44 +309,24 @@ class Person(Row):
 class Persons(Table):
     """
     Representation of row(s) from the persons table in the
-    database. Specify deleted and/or enabled to force a match on
-    whether a person is deleted and/or enabled. Default is to match on
-    non-deleted accounts.
+    database.
     """
 
-    def __init__(self, api, person_id_or_email_list = None, enabled = None):
-        self.api = api
+    def __init__(self, api, person_filter = None, columns = None):
+        Table.__init__(self, api, Person, columns)
 
         sql = "SELECT %s FROM view_persons WHERE deleted IS False" % \
-              ", ".join(Person.fields)
-
-        if enabled is not None:
-            sql += " AND enabled IS %(enabled)s"
-
-        if person_id_or_email_list:
-            # Separate the list into integers and strings
-            person_ids = filter(lambda person_id: isinstance(person_id, (int, long)),
-                                person_id_or_email_list)
-            emails = filter(lambda email: isinstance(email, StringTypes),
-                            person_id_or_email_list)
-            sql += " AND (False"
-            if person_ids:
-                sql += " OR person_id IN (%s)" % ", ".join(map(str, person_ids))
-            if emails:
-                # Case insensitive e-mail address comparison
-                sql += " OR email IN (%s)" % ", ".join(api.db.quote(emails)).lower()
-            sql += ")"
-
-        rows = self.api.db.selectall(sql, locals())
-
-        for row in rows:
-            self[row['person_id']] = person = Person(api, row)
-            for aggregate in 'role_ids', 'roles', 'site_ids', 'key_ids', 'slice_ids':
-                if not person.has_key(aggregate) or person[aggregate] is None:
-                    person[aggregate] = []
-                else:
-                    elements = person[aggregate].split(',')
-                    try:
-                        person[aggregate] = map(int, elements)
-                    except ValueError:
-                        person[aggregate] = elements
+              ", ".join(self.columns)
+
+        if person_filter is not None:
+            if isinstance(person_filter, (list, tuple, set)):
+                # Separate the list into integers and strings
+                ints = filter(lambda x: isinstance(x, (int, long)), person_filter)
+                strs = filter(lambda x: isinstance(x, StringTypes), person_filter)
+                person_filter = Filter(Person.fields, {'person_id': ints, 'email': strs})
+                sql += " AND (%s)" % person_filter.sql(api, "OR")
+            elif isinstance(person_filter, dict):
+                person_filter = Filter(Person.fields, person_filter)
+                sql += " AND (%s)" % person_filter.sql(api, "AND")
+
+        self.selectall(sql)