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