embed svn Id keyword
[plcapi.git] / PLC / Sites.py
1 # $Id#
2 from types import StringTypes
3 import string
4
5 from PLC.Faults import *
6 from PLC.Parameter import Parameter, Mixed
7 from PLC.Filter import Filter
8 from PLC.Debug import profile
9 from PLC.Table import Row, Table
10 from PLC.Slices import Slice, Slices
11 from PLC.PCUs import PCU, PCUs
12 from PLC.Nodes import Node, Nodes
13 from PLC.Addresses import Address, Addresses
14 from PLC.Persons import Person, Persons
15
16 class Site(Row):
17     """
18     Representation of a row in the sites table. To use, optionally
19     instantiate with a dict of values. Update as you would a
20     dict. Commit to the database with sync().
21     """
22
23     table_name = 'sites'
24     primary_key = 'site_id'
25     join_tables = ['person_site', 'site_address', 'peer_site']
26     fields = {
27         'site_id': Parameter(int, "Site identifier"),
28         'name': Parameter(str, "Full site name", max = 254),
29         'abbreviated_name': Parameter(str, "Abbreviated site name", max = 50),
30         'login_base': Parameter(str, "Site slice prefix", max = 20),
31         'is_public': Parameter(bool, "Publicly viewable site"),
32         'enabled': Parameter(bool, "Has been enabled"),
33         'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
34         'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
35         'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
36         'date_created': Parameter(int, "Date and time when site entry was created, in seconds since UNIX epoch", ro = True),
37         'last_updated': Parameter(int, "Date and time when site entry was last updated, in seconds since UNIX epoch", ro = True),
38         'max_slices': Parameter(int, "Maximum number of slices that the site is able to create"),
39         'max_slivers': Parameter(int, "Maximum number of slivers that the site is able to create"),
40         'person_ids': Parameter([int], "List of account identifiers"),
41         'slice_ids': Parameter([int], "List of slice identifiers"),
42         'address_ids': Parameter([int], "List of address identifiers"),
43         'pcu_ids': Parameter([int], "List of PCU identifiers"),
44         'node_ids': Parameter([int], "List of site node identifiers"),
45         'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
46         'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
47         'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
48         }
49     related_fields = {
50         'persons': [Mixed(Parameter(int, "Person identifier"),
51                           Parameter(str, "Email address"))],
52         'addresses': [Mixed(Parameter(int, "Address identifer"),
53                             Filter(Address.fields))]
54         }
55
56     def validate_name(self, name):
57         if not len(name):
58             raise PLCInvalidArgument, "Name must be specified"
59
60         return name
61
62     validate_abbreviated_name = validate_name
63
64     def validate_login_base(self, login_base):
65         if not len(login_base):
66             raise PLCInvalidArgument, "Login base must be specified"
67
68         if not set(login_base).issubset(string.lowercase + string.digits):
69             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers"
70
71         conflicts = Sites(self.api, [login_base])
72         for site in conflicts:
73             if 'site_id' not in self or self['site_id'] != site['site_id']:
74                 raise PLCInvalidArgument, "login_base already in use"
75
76         return login_base
77
78     def validate_latitude(self, latitude):
79         if not self.has_key('longitude') or \
80            self['longitude'] is None:
81             raise PLCInvalidArgument, "Longitude must also be specified"
82
83         return latitude
84
85     def validate_longitude(self, longitude):
86         if not self.has_key('latitude') or \
87            self['latitude'] is None:
88             raise PLCInvalidArgument, "Latitude must also be specified"
89
90         return longitude
91
92     validate_date_created = Row.validate_timestamp
93     validate_last_updated = Row.validate_timestamp
94
95     add_person = Row.add_object(Person, 'person_site')
96     remove_person = Row.remove_object(Person, 'person_site')
97
98     add_address = Row.add_object(Address, 'site_address')
99     remove_address = Row.remove_object(Address, 'site_address')
100
101     def update_last_updated(self, commit = True):
102         """
103         Update last_updated field with current time
104         """
105
106         assert 'site_id' in self
107         assert self.table_name
108
109         self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
110                        " where site_id = %d" % (self['site_id']) )
111         self.sync(commit)    
112
113
114     def associate_persons(self, auth, field, value):
115         """
116         Adds persons found in value list to this site (using AddPersonToSite).
117         Deletes persons not found in value list from this site (using DeletePersonFromSite).
118         """
119         
120         assert 'person_ids' in self
121         assert 'site_id' in self
122         assert isinstance(value, list)
123
124         (person_ids, emails) = self.separate_types(value)[0:2]
125
126         # Translate emails into person_ids
127         if emails:
128             persons = Persons(self.api, emails, ['person_id']).dict('person_id')
129             person_ids += persons.keys()
130
131         # Add new ids, remove stale ids
132         if self['person_ids'] != person_ids:
133             from PLC.Methods.AddPersonToSite import AddPersonToSite
134             from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
135             new_persons = set(person_ids).difference(self['person_ids'])
136             stale_persons = set(self['person_ids']).difference(person_ids)
137          
138             for new_person in new_persons:
139                 AddPersonToSite.__call__(AddPersonToSite(self.api), auth, new_person, self['site_id'])
140             for stale_person in stale_persons:
141                 DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, stale_person, self['site_id'])              
142
143     def associate_addresses(self, auth, field, value):
144         """
145         Deletes addresses_ids not found in value list (using DeleteAddress).  
146         Adds address if slice_fields w/o address_id found in value list (using AddSiteAddress).
147         Update address if slice_fields w/ address_id found in value list (using UpdateAddress).
148         """
149         
150         assert 'address_ids' in self
151         assert 'site_id' in self
152         assert isinstance(value, list)
153
154         (address_ids, blank, addresses) = self.separate_types(value)
155
156         for address in addresses:
157             if 'address_id' in address:
158                 address_ids.append(address['address_id'])               
159
160         # Add new ids, remove stale ids
161         if self['address_ids'] != address_ids:
162             from PLC.Methods.DeleteAddress import DeleteAddress
163             stale_addresses = set(self['address_ids']).difference(address_ids)
164
165             for stale_address in stale_addresses:
166                 DeleteAddress.__call__(DeleteAddress(self.api), auth, stale_address)    
167         
168         if addresses:
169             from PLC.Methods.AddSiteAddress import AddSiteAddress
170             from PLC.Methods.UpdateAddress import UpdateAddress
171                 
172             updated_addresses = filter(lambda address: 'address_id' in address, addresses)
173             added_addresses = filter(lambda address: 'address_id' not in address, addresses)
174                 
175             for address in added_addresses:
176                 AddSiteAddress.__call__(AddSiteAddress(self.api), auth, self['site_id'], address)       
177             for address in updated_addresses:
178                 address_id = address.pop('address_id')
179                 UpdateAddress.__call__(UpdateAddress(self.api), auth, address_id, address)
180
181     def delete(self, commit = True):
182         """
183         Delete existing site.
184         """
185
186         assert 'site_id' in self
187
188         # Delete accounts of all people at the site who are not
189         # members of at least one other non-deleted site.
190         persons = Persons(self.api, self['person_ids'])
191         for person in persons:
192             delete = True
193
194             person_sites = Sites(self.api, person['site_ids'])
195             for person_site in person_sites:
196                 if person_site['site_id'] != self['site_id']:
197                     delete = False
198                     break
199
200             if delete:
201                 person.delete(commit = False)
202
203         # Delete all site addresses
204         addresses = Addresses(self.api, self['address_ids'])
205         for address in addresses:
206             address.delete(commit = False)
207
208         # Delete all site slices
209         slices = Slices(self.api, self['slice_ids'])
210         for slice in slices:
211             slice.delete(commit = False)
212
213         # Delete all site PCUs
214         pcus = PCUs(self.api, self['pcu_ids'])
215         for pcu in pcus:
216             pcu.delete(commit = False)
217
218         # Delete all site nodes
219         nodes = Nodes(self.api, self['node_ids'])
220         for node in nodes:
221             node.delete(commit = False)
222
223         # Clean up miscellaneous join tables
224         for table in self.join_tables:
225             self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
226                            (table, self['site_id']))
227
228         # Mark as deleted
229         self['deleted'] = True
230         self.sync(commit)
231
232 class Sites(Table):
233     """
234     Representation of row(s) from the sites table in the
235     database.
236     """
237
238     def __init__(self, api, site_filter = None, columns = None):
239         Table.__init__(self, api, Site, columns)
240
241         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
242               ", ".join(self.columns)
243
244         if site_filter is not None:
245             if isinstance(site_filter, (list, tuple, set)):
246                 # Separate the list into integers and strings
247                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
248                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
249                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
250                 sql += " AND (%s) %s" % site_filter.sql(api, "OR")
251             elif isinstance(site_filter, dict):
252                 site_filter = Filter(Site.fields, site_filter)
253                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
254             elif isinstance (site_filter, StringTypes):
255                 site_filter = Filter(Site.fields, {'login_base':[site_filter]})
256                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
257             elif isinstance (site_filter, int):
258                 site_filter = Filter(Site.fields, {'site_id':[site_filter]})
259                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
260             else:
261                 raise PLCInvalidArgument, "Wrong site filter %r"%site_filter
262
263         self.selectall(sql)