r2lab is migrating from a previous depl. based on omf_sfa
[plcapi.git] / PLC / Sites.py
index d05b285..2075409 100644 (file)
@@ -2,16 +2,15 @@ from types import StringTypes
 import string
 
 from PLC.Faults import *
-from PLC.Parameter import Parameter
+from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Debug import profile
 from PLC.Table import Row, Table
 from PLC.Slices import Slice, Slices
 from PLC.PCUs import PCU, PCUs
 from PLC.Nodes import Node, Nodes
-from PLC.NodeGroups import NodeGroup, NodeGroups
 from PLC.Addresses import Address, Addresses
-import PLC.Persons
+from PLC.Persons import Person, Persons
 
 class Site(Row):
     """
@@ -22,12 +21,14 @@ class Site(Row):
 
     table_name = 'sites'
     primary_key = 'site_id'
+    join_tables = ['person_site', 'site_address', 'peer_site']
     fields = {
         'site_id': Parameter(int, "Site identifier"),
         'name': Parameter(str, "Full site name", max = 254),
         'abbreviated_name': Parameter(str, "Abbreviated site name", max = 50),
-        'login_base': Parameter(str, "Site slice prefix", max = 20),
+        'login_base': Parameter(str, "Site slice prefix", max = 32),
         'is_public': Parameter(bool, "Publicly viewable site"),
+        'enabled': Parameter(bool, "Has been enabled"),
         'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
         'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
         'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
@@ -40,15 +41,21 @@ class Site(Row):
         'address_ids': Parameter([int], "List of address identifiers"),
         'pcu_ids': Parameter([int], "List of PCU identifiers"),
         'node_ids': Parameter([int], "List of site node identifiers"),
-        'peer_id': Parameter(int, "Peer at which this slice was created", nullok = True),
+        'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
+        'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
+        'site_tag_ids' : Parameter ([int], "List of tags attached to this site"),
+        'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
         }
-
-    # for Cache
-    class_key = 'login_base'
-    foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
-                     'url', 'date_created', 'last_updated', 'max_slices', 'max_slivers',
-                     ]
-    foreign_xrefs = {}
+    related_fields = {
+        'persons': [Mixed(Parameter(int, "Person identifier"),
+                          Parameter(str, "Email address"))],
+        'addresses': [Mixed(Parameter(int, "Address identifer"),
+                            Filter(Address.fields))]
+        }
+    view_tags_name = "view_site_tags"
+    # tags are used by the Add/Get/Update methods to expose tags
+    # this is initialized here and updated by the accessors factory
+    tags = { }
 
     def validate_name(self, name):
         if not len(name):
@@ -62,8 +69,8 @@ class Site(Row):
         if not len(login_base):
             raise PLCInvalidArgument, "Login base must be specified"
 
-        if not set(login_base).issubset(string.ascii_letters.lower()):
-            raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters"
+        if not set(login_base).issubset(string.lowercase + string.digits + '.'):
+            raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers or dots"
 
         conflicts = Sites(self.api, [login_base])
         for site in conflicts:
@@ -86,107 +93,94 @@ class Site(Row):
 
         return longitude
 
-    # timestamps
-    def validate_date_created (self, timestamp):
-       return self.validate_timestamp (timestamp)
-    def validate_last_updated (self, timestamp):
-       return self.validate_timestamp (timestamp)
+    validate_date_created = Row.validate_timestamp
+    validate_last_updated = Row.validate_timestamp
+
+    add_person = Row.add_object(Person, 'person_site')
+    remove_person = Row.remove_object(Person, 'person_site')
 
-    def add_person(self, person, commit = True):
+    add_address = Row.add_object(Address, 'site_address')
+    remove_address = Row.remove_object(Address, 'site_address')
+
+    def update_last_updated(self, commit = True):
         """
-        Add person to existing site.
+        Update last_updated field with current time
         """
 
         assert 'site_id' in self
-        assert isinstance(person, PLC.Persons.Person)
-        assert 'person_id' in person
-
-        site_id = self['site_id']
-        person_id = person['person_id']
+        assert self.table_name
 
-        if person_id not in self['person_ids']:
-            assert site_id not in person['site_ids']
-
-            self.api.db.do("INSERT INTO person_site (person_id, site_id)" \
-                           " VALUES(%(person_id)d, %(site_id)d)",
-                           locals())
-
-            if commit:
-                self.api.db.commit()
+        self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
+                       " where site_id = %d" % (self['site_id']) )
+        self.sync(commit)
 
-            self['person_ids'].append(person_id)
-            person['site_ids'].append(site_id)
 
-    def remove_person(self, person, commit = True):
+    def associate_persons(self, auth, field, value):
         """
-        Remove person from existing site.
+        Adds persons found in value list to this site (using AddPersonToSite).
+        Deletes persons not found in value list from this site (using DeletePersonFromSite).
         """
 
+        assert 'person_ids' in self
         assert 'site_id' in self
-        assert isinstance(person, PLC.Persons.Person)
-        assert 'person_id' in person
-
-        site_id = self['site_id']
-        person_id = person['person_id']
+        assert isinstance(value, list)
 
-        if person_id in self['person_ids']:
-            assert site_id in person['site_ids']
+        (person_ids, emails) = self.separate_types(value)[0:2]
 
-            self.api.db.do("DELETE FROM person_site" \
-                           " WHERE person_id = %(person_id)d" \
-                           " AND site_id = %(site_id)d",
-                           locals())
+        # Translate emails into person_ids
+        if emails:
+            persons = Persons(self.api, emails, ['person_id']).dict('person_id')
+            person_ids += persons.keys()
 
-            if commit:
-                self.api.db.commit()
+        # Add new ids, remove stale ids
+        if self['person_ids'] != person_ids:
+            from PLC.Methods.AddPersonToSite import AddPersonToSite
+            from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
+            new_persons = set(person_ids).difference(self['person_ids'])
+            stale_persons = set(self['person_ids']).difference(person_ids)
 
-            self['person_ids'].remove(person_id)
-            person['site_ids'].remove(site_id)
+            for new_person in new_persons:
+                AddPersonToSite.__call__(AddPersonToSite(self.api), auth, new_person, self['site_id'])
+            for stale_person in stale_persons:
+                DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, stale_person, self['site_id'])
 
-    def add_address(self, address, commit = True):
+    def associate_addresses(self, auth, field, value):
         """
-        Add address to existing site.
+        Deletes addresses_ids not found in value list (using DeleteAddress).
+        Adds address if slice_fields w/o address_id found in value list (using AddSiteAddress).
+        Update address if slice_fields w/ address_id found in value list (using UpdateAddress).
         """
 
+        assert 'address_ids' in self
         assert 'site_id' in self
-        assert isinstance(address, Address)
-        assert 'address_id' in address
+        assert isinstance(value, list)
 
-        site_id = self['site_id']
-        address_id = address['address_id']
+        (address_ids, blank, addresses) = self.separate_types(value)
 
-        if address_id not in self['address_ids']:
-            self.api.db.do("INSERT INTO site_address (address_id, site_id)" \
-                           " VALUES(%(address_id)d, %(site_id)d)",
-                           locals())
-
-            if commit:
-                self.api.db.commit()
-
-            self['address_ids'].append(address_id)
-
-    def remove_address(self, address, commit = True):
-        """
-        Remove address from existing site.
-        """
+        for address in addresses:
+            if 'address_id' in address:
+                address_ids.append(address['address_id'])
 
-        assert 'site_id' in self
-        assert isinstance(address, Address)
-        assert 'address_id' in address
+        # Add new ids, remove stale ids
+        if self['address_ids'] != address_ids:
+            from PLC.Methods.DeleteAddress import DeleteAddress
+            stale_addresses = set(self['address_ids']).difference(address_ids)
 
-        site_id = self['site_id']
-        address_id = address['address_id']
+            for stale_address in stale_addresses:
+                DeleteAddress.__call__(DeleteAddress(self.api), auth, stale_address)
 
-        if address_id in self['address_ids']:
-            self.api.db.do("DELETE FROM site_address" \
-                           " WHERE address_id = %(address_id)d" \
-                           " AND site_id = %(site_id)d",
-                           locals())
+        if addresses:
+            from PLC.Methods.AddSiteAddress import AddSiteAddress
+            from PLC.Methods.UpdateAddress import UpdateAddress
 
-            if commit:
-                self.api.db.commit()
+            updated_addresses = filter(lambda address: 'address_id' in address, addresses)
+            added_addresses = filter(lambda address: 'address_id' not in address, addresses)
 
-            self['address_ids'].remove(address_id)
+            for address in added_addresses:
+                AddSiteAddress.__call__(AddSiteAddress(self.api), auth, self['site_id'], address)
+            for address in updated_addresses:
+                address_id = address.pop('address_id')
+                UpdateAddress.__call__(UpdateAddress(self.api), auth, address_id, address)
 
     def delete(self, commit = True):
         """
@@ -197,7 +191,7 @@ class Site(Row):
 
         # Delete accounts of all people at the site who are not
         # members of at least one other non-deleted site.
-        persons = PLC.Persons.Persons(self.api, self['person_ids'])
+        persons = Persons(self.api, self['person_ids'])
         for person in persons:
             delete = True
 
@@ -231,10 +225,9 @@ class Site(Row):
             node.delete(commit = False)
 
         # Clean up miscellaneous join tables
-        for table in ['person_site']:
-            self.api.db.do("DELETE FROM %s" \
-                           " WHERE site_id = %d" % \
-                           (table, self['site_id']), self)
+        for table in self.join_tables:
+            self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
+                           (table, self['site_id']))
 
         # Mark as deleted
         self['deleted'] = True
@@ -249,8 +242,13 @@ class Sites(Table):
     def __init__(self, api, site_filter = None, columns = None):
         Table.__init__(self, api, Site, columns)
 
-        sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
-              ", ".join(self.columns)
+        view = "view_sites"
+        for tagname in self.tag_columns:
+            view= "%s left join %s using (%s)"%(view,Site.tagvalue_view_name(tagname),
+                                                Site.primary_key)
+
+        sql = "SELECT %s FROM %s WHERE deleted IS False" % \
+            (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         if site_filter is not None:
             if isinstance(site_filter, (list, tuple, set)):
@@ -258,9 +256,18 @@ class Sites(Table):
                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
-                sql += " AND (%s)" % site_filter.sql(api, "OR")
+                sql += " AND (%s) %s" % site_filter.sql(api, "OR")
             elif isinstance(site_filter, dict):
-                site_filter = Filter(Site.fields, site_filter)
-                sql += " AND (%s)" % site_filter.sql(api, "AND")
+                allowed_fields=dict(Site.fields.items()+Site.tags.items())
+                site_filter = Filter(allowed_fields, site_filter)
+                sql += " AND (%s) %s" % site_filter.sql(api, "AND")
+            elif isinstance (site_filter, StringTypes):
+                site_filter = Filter(Site.fields, {'login_base':site_filter})
+                sql += " AND (%s) %s" % site_filter.sql(api, "AND")
+            elif isinstance (site_filter, (int, long)):
+                site_filter = Filter(Site.fields, {'site_id':site_filter})
+                sql += " AND (%s) %s" % site_filter.sql(api, "AND")
+            else:
+                raise PLCInvalidArgument, "Wrong site filter %r"%site_filter
 
         self.selectall(sql)