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