Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / PLC / Persons.py
index 541c608..d2bb510 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.31 2007/01/09 16:22:49 mlhuang Exp $
+# $Id: Persons.py 5652 2007-11-06 03:42:57Z tmack $
 #
 
 from types import StringTypes
@@ -17,7 +17,7 @@ import crypt
 
 from PLC.Faults import *
 from PLC.Debug import log
-from PLC.Parameter import Parameter
+from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Table import Row, Table
 from PLC.Roles import Role, Roles
@@ -45,8 +45,8 @@ class Person(Row):
         'bio': Parameter(str, "Biography", max = 254, nullok = True),
         'enabled': Parameter(bool, "Has been enabled"),
         'password': Parameter(str, "Account password in crypt() form", max = 254),
-        'verification_key': Parameter(str, "Reset password key", max = 254),
-       'verification_expires': Parameter(int, "Date and time when verification_key expires"),
+        'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
+       'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = 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"),
@@ -57,6 +57,18 @@ class Person(Row):
         'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True),
         'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True),
         }
+    related_fields = {
+       'roles': [Mixed(Parameter(int, "Role identifier"),
+                       Parameter(str, "Role name"))],
+       'sites': [Mixed(Parameter(int, "Site identifier"),
+                       Parameter(str, "Site name"))],
+       'keys': [Mixed(Parameter(int, "Key identifier"),
+                      Filter(Key.fields))],
+       'slices': [Mixed(Parameter(int, "Slice identifier"),
+                        Parameter(str, "Slice name"))]
+       }       
+
+       
 
     # for Cache
     class_key = 'email'
@@ -100,8 +112,15 @@ class Person(Row):
         if len(domain) < 2:
             raise invalid_email
 
-        conflicts = Persons(self.api, [email])
-        for person in conflicts:
+               # check only against users on the same peer  
+       if 'peer_id' in self:
+            namespace_peer_id = self['peer_id']
+        else:
+            namespace_peer_id = None
+         
+       conflicts = Persons(self.api, {'email':email,'peer_id':namespace_peer_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"
 
@@ -210,69 +229,143 @@ class Person(Row):
         self['site_ids'].remove(site_id)
         self['site_ids'].insert(0, site_id)
 
-    def send_initiate_password_reset_email(self):
-       # email user next step instructions
-        to_addr = {}
-        to_addr[self['email']] = "%s %s" % \
-            (self['first_name'], self['last_name'])
-        from_addr = {}
-        from_addr[self.api.config.PLC_MAIL_SUPPORT_ADDRESS] = \
-        "%s %s" % ('Planetlab', 'Support')
-
-       # fill in template
-        messages = Messages(self.api, ['ASSWORD_RESET_INITIATE'])
-        if not messages:
-            print >> log, "No such message template"
-           return 1
-
-        message = messages[0]
-        subject = message['subject']
-        template = message['template'] % \
-            (self.api.config.PLC_WWW_HOST,
-             self['verification_key'], self['person_id'],
-             self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
-             self.api.config.PLC_WWW_HOST)
-
-        self.api.mailer.mail(to_addr, None, from_addr, subject, template)
-    
-    def send_account_registered_email(self, site):
-       to_addr = {}
-       cc_addr = {}
-       from_addr = {}
-       from_addr[self.api.config.PLC_MAIL_SUPPORT_ADDRESS] = \
-        "%s %s" % ('Planetlab', 'Support')
-
-       # email user
-       user_full_name = "%s %s" % (self['first_name'], self['last_name'])
-       to_addr[self['email']] = "%s" % user_full_name
-
-       # if the account had a admin role or a pi role, email support.
-        if set(['admin', 'pi']).intersection(self['roles']):
-            to_addr[self.api.config.PLC_MAIL_SUPPORT_ADDRESS] = \
-                "%s %s" % ('Planetlab', 'Support')
+    def update_last_updated(self, commit = True):
+        """
+        Update last_updated field with current time
+        """
+       
+       assert 'person_id' in self
+       assert self.table_name
+       
+       self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
+                       " where person_id = %d" % (self['person_id']) )
+        self.sync(commit)
+
+    def associate_roles(self, auth, field, value):
+       """
+       Adds roles found in value list to this person (using AddRoleToPerson).
+       Deletes roles not found in value list from this person (using DeleteRoleFromPerson).
+       """
        
-       # cc site pi's
-       site_persons = Persons(self.api, site['person_ids'])
-        for person in site_persons:
-            if 'pi' in person['roles'] and not person['email'] in to_addr.keys():
-                cc_addr[person['email']] = "%s %s" % \
-               (person['first_name'], person['last_name'])
-
-       # fill in template
-       messages = Messages(self.api, ['ACCOUNT_REGISTERED'])
-        if not messages:
-           print >> log, "No such message template"
-            return 1
-
-        message = messages[0]
-        subject = message['subject'] % (user_full_name, site['name'])
-        template = message['template'] % \
-           (user_full_name, site['name'], ", ".join(self['roles']),
-            self.api.config.PLC_WWW_HOST, self['person_id'],
-             self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
-             self.api.config.PLC_WWW_HOST)
-                               
-       self.api.mailer.mail(to_addr, cc_addr, from_addr, subject, template)
+       assert 'role_ids' in self
+       assert 'person_id' in self
+       assert isinstance(value, list)
+       
+       (role_ids, roles_names) = self.separate_types(value)[0:2]
+       
+       # Translate roles into role_ids
+       if roles_names:
+           roles = Roles(self.api, role_names, ['role_id']).dict('role_id')
+           role_ids += roles.keys()
+       
+       # Add new ids, remove stale ids
+       if self['role_ids'] != role_ids:
+           from PLC.Methods.AddRoleToPerson import AddRoleToPerson
+           from PLC.Methods.DeleteRoleFromPerson import DeleteRoleFromPerson
+           new_roles = set(role_ids).difference(self['role_ids'])
+           stale_roles = set(self['role_ids']).difference(role_ids)
+
+           for new_role in new_roles:
+               AddRoleToPerson.__call__(AddRoleToPerson(self.api), auth, new_role, self['person_id'])
+           for stale_role in stale_roles:
+               DeleteRoleFromPerson.__call__(DeleteRoleFromPerson(self.api), auth, stale_role, self['person_id'])
+
+
+    def associate_sites(self, auth, field, value):
+        """
+        Adds person to sites found in value list (using AddPersonToSite).
+        Deletes person from site not found in value list (using DeletePersonFromSite).
+        """
+
+       from PLC.Sites import Sites
+
+        assert 'site_ids' in self
+        assert 'person_id' in self
+        assert isinstance(value, list)
+
+        (site_ids, site_names) = self.separate_types(value)[0:2]
+
+        # Translate roles into role_ids
+        if site_names:
+            sites = Sites(self.api, site_names, ['site_id']).dict('site_id')
+            site_ids += sites.keys()
+
+        # Add new ids, remove stale ids
+        if self['site_ids'] != site_ids:
+            from PLC.Methods.AddPersonToSite import AddPersonToSite
+            from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
+            new_sites = set(site_ids).difference(self['site_ids'])
+            stale_sites = set(self['site_ids']).difference(site_ids)
+
+            for new_site in new_sites:
+                AddPersonToSite.__call__(AddPersonToSite(self.api), auth, self['person_id'], new_site)
+            for stale_site in stale_sites:
+                DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, self['person_id'], stale_site)
+
+
+    def associate_keys(self, auth, field, value):
+       """
+        Deletes key_ids not found in value list (using DeleteKey).
+        Adds key if key_fields w/o key_id is found (using AddPersonKey).
+        Updates key if key_fields w/ key_id is found (using UpdateKey).
+        """
+       assert 'key_ids' in self
+       assert 'person_id' in self
+       assert isinstance(value, list)
+       
+       (key_ids, blank, keys) = self.separate_types(value)
+       
+       if self['key_ids'] != key_ids:
+           from PLC.Methods.DeleteKey import DeleteKey
+           stale_keys = set(self['key_ids']).difference(key_ids)
+       
+           for stale_key in stale_keys:
+               DeleteKey.__call__(DeleteKey(self.api), auth, stale_key) 
+
+       if keys:
+           from PLC.Methods.AddPersonKey import AddPersonKey
+           from PLC.Methods.UpdateKey import UpdateKey         
+           updated_keys = filter(lambda key: 'key_id' in key, keys)
+           added_keys = filter(lambda key: 'key_id' not in key, keys)
+               
+           for key in added_keys:
+               AddPersonKey.__call__(AddPersonKey(self.api), auth, self['person_id'], key)
+           for key in updated_keys:
+               key_id = key.pop('key_id')
+               UpdateKey.__call__(UpdateKey(self.api), auth, key_id, key)
+                 
+       
+    def associate_slices(self, auth, field, value):
+        """
+        Adds person to slices found in value list (using AddPersonToSlice).
+        Deletes person from slices found in value list (using DeletePersonFromSlice).
+        """
+
+       from PLC.Slices import Slices
+
+        assert 'slice_ids' in self
+        assert 'person_id' in self
+        assert isinstance(value, list)
+
+        (slice_ids, slice_names) = self.separate_types(value)[0:2]
+
+        # Translate roles into role_ids
+        if slice_names:
+            slices = Slices(self.api, slice_names, ['slice_id']).dict('slice_id')
+            slice_ids += slices.keys()
+
+        # Add new ids, remove stale ids
+        if self['slice_ids'] != slice_ids:
+            from PLC.Methods.AddPersonToSlice import AddPersonToSlice
+            from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
+            new_slices = set(slice_ids).difference(self['slice_ids'])
+            stale_slices = set(self['slice_ids']).difference(slice_ids)
+
+            for new_slice in new_slices:
+                AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, self['person_id'], new_slice)
+            for stale_slice in stale_slices:
+                DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, self['person_id'], stale_slice)
+    
 
     def delete(self, commit = True):
         """
@@ -301,19 +394,108 @@ class Persons(Table):
 
     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(self.columns)
-
+        #sql = "SELECT %s FROM view_persons WHERE deleted IS False" % \
+        #      ", ".join(self.columns)
+       foreign_fields = {'role_ids': ('role_id', 'person_role'),
+                         'roles': ('name', 'roles'),
+                          'site_ids': ('site_id', 'person_site'),
+                          'key_ids': ('key_id', 'person_key'),
+                          'slice_ids': ('slice_id', 'slice_person')
+                          }
+       foreign_keys = {}
+       db_fields = filter(lambda field: field not in foreign_fields.keys(), Person.fields.keys())
+       all_fields = db_fields + [value[0] for value in foreign_fields.values()]
+       fields = []
+       _select = "SELECT "
+       _from = " FROM persons "
+       _join = " LEFT JOIN peer_person USING (person_id) "  
+       _where = " WHERE deleted IS False "
+
+       if not columns:
+           # include all columns       
+           fields = all_fields
+           tables = [value[1] for value in foreign_fields.values()]
+           tables.sort()
+           for key in foreign_fields.keys():
+               foreign_keys[foreign_fields[key][0]] = key  
+           for table in tables:
+               if table in ['roles']:
+                   _join += " LEFT JOIN roles USING(role_id) "
+               else:   
+                   _join += " LEFT JOIN %s USING (person_id) " % (table)
+       else: 
+           tables = set()
+           columns = filter(lambda column: column in db_fields+foreign_fields.keys(), columns)
+           columns.sort()
+           for column in columns: 
+               if column in foreign_fields.keys():
+                   (field, table) = foreign_fields[column]
+                   foreign_keys[field] = column
+                   fields += [field]
+                   tables.add(table)
+                   if column in ['roles']:
+                       _join += " LEFT JOIN roles USING(role_id) "
+                   else:
+                       _join += " LEFT JOIN %s USING (person_id)" % \
+                               (foreign_fields[column][1])
+               
+               else:
+                   fields += [column]  
+       
+       # postgres will return timestamps as datetime objects. 
+       # XMLPRC cannot marshal datetime so convert to int
+       timestamps = ['date_created', 'last_updated', 'verification_expires']
+       for field in fields:
+           if field in timestamps:
+               fields[fields.index(field)] = \
+                "CAST(date_part('epoch', %s) AS bigint) AS %s" % (field, field)
+
+       _select += ", ".join(fields)
+       sql = _select + _from + _join + _where
+
+       # deal with filter                      
         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")
+                sql += " AND (%s) %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")
+                sql += " AND (%s) %s" % person_filter.sql(api, "AND")
+            elif isinstance (person_filter, StringTypes):
+                person_filter = Filter(Person.fields, {'email':[person_filter]})
+                sql += " AND (%s) %s" % person_filter.sql(api, "AND")
+            elif isinstance (person_filter, int):
+                person_filter = Filter(Person.fields, {'person_id':[person_filter]})
+                sql += " AND (%s) %s" % person_filter.sql(api, "AND")
+            else:
+                raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
+
+       # aggregate data
+       all_persons = {}
+       for row in self.api.db.selectall(sql):
+           person_id = row['person_id']
+
+           if all_persons.has_key(person_id):
+               for (key, key_list) in foreign_keys.items():
+                   data = row.pop(key)
+                   row[key_list] = [data]
+                   if data and data not in all_persons[person_id][key_list]:
+                       all_persons[person_id][key_list].append(data)
+            else:
+               for key in foreign_keys.keys():
+                    value = row.pop(key)
+                   if value:   
+                       row[foreign_keys[key]] = [value]
+                   else:
+                       row[foreign_keys[key]] = []
+               if row: 
+                   all_persons[person_id] = row
+               
+       # populate self
+       for row in all_persons.values():
+           obj = self.classobj(self.api, row)
+            self.append(obj)
 
-        self.selectall(sql)