add site_tags : GetSites() now returns 'site_tag_ids' attribute.
[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         'site_tag_ids' : Parameter ([int], "List of tags attached to this site"),
49         'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
50         }
51     related_fields = {
52         'persons': [Mixed(Parameter(int, "Person identifier"),
53                           Parameter(str, "Email address"))],
54         'addresses': [Mixed(Parameter(int, "Address identifer"),
55                             Filter(Address.fields))]
56         }
57     view_tags_name = "view_site_tags"
58     # tags are used by the Add/Get/Update methods to expose tags
59     # this is initialized here and updated by the accessors factory
60     tags = { }
61
62     def validate_name(self, name):
63         if not len(name):
64             raise PLCInvalidArgument, "Name must be specified"
65
66         return name
67
68     validate_abbreviated_name = validate_name
69
70     def validate_login_base(self, login_base):
71         if not len(login_base):
72             raise PLCInvalidArgument, "Login base must be specified"
73
74         if not set(login_base).issubset(string.lowercase + string.digits):
75             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers"
76
77         conflicts = Sites(self.api, [login_base])
78         for site in conflicts:
79             if 'site_id' not in self or self['site_id'] != site['site_id']:
80                 raise PLCInvalidArgument, "login_base already in use"
81
82         return login_base
83
84     def validate_latitude(self, latitude):
85         if not self.has_key('longitude') or \
86            self['longitude'] is None:
87             raise PLCInvalidArgument, "Longitude must also be specified"
88
89         return latitude
90
91     def validate_longitude(self, longitude):
92         if not self.has_key('latitude') or \
93            self['latitude'] is None:
94             raise PLCInvalidArgument, "Latitude must also be specified"
95
96         return longitude
97
98     validate_date_created = Row.validate_timestamp
99     validate_last_updated = Row.validate_timestamp
100
101     add_person = Row.add_object(Person, 'person_site')
102     remove_person = Row.remove_object(Person, 'person_site')
103
104     add_address = Row.add_object(Address, 'site_address')
105     remove_address = Row.remove_object(Address, 'site_address')
106
107     def update_last_updated(self, commit = True):
108         """
109         Update last_updated field with current time
110         """
111
112         assert 'site_id' in self
113         assert self.table_name
114
115         self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
116                        " where site_id = %d" % (self['site_id']) )
117         self.sync(commit)    
118
119
120     def associate_persons(self, auth, field, value):
121         """
122         Adds persons found in value list to this site (using AddPersonToSite).
123         Deletes persons not found in value list from this site (using DeletePersonFromSite).
124         """
125         
126         assert 'person_ids' in self
127         assert 'site_id' in self
128         assert isinstance(value, list)
129
130         (person_ids, emails) = self.separate_types(value)[0:2]
131
132         # Translate emails into person_ids
133         if emails:
134             persons = Persons(self.api, emails, ['person_id']).dict('person_id')
135             person_ids += persons.keys()
136
137         # Add new ids, remove stale ids
138         if self['person_ids'] != person_ids:
139             from PLC.Methods.AddPersonToSite import AddPersonToSite
140             from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
141             new_persons = set(person_ids).difference(self['person_ids'])
142             stale_persons = set(self['person_ids']).difference(person_ids)
143          
144             for new_person in new_persons:
145                 AddPersonToSite.__call__(AddPersonToSite(self.api), auth, new_person, self['site_id'])
146             for stale_person in stale_persons:
147                 DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, stale_person, self['site_id'])              
148
149     def associate_addresses(self, auth, field, value):
150         """
151         Deletes addresses_ids not found in value list (using DeleteAddress).  
152         Adds address if slice_fields w/o address_id found in value list (using AddSiteAddress).
153         Update address if slice_fields w/ address_id found in value list (using UpdateAddress).
154         """
155         
156         assert 'address_ids' in self
157         assert 'site_id' in self
158         assert isinstance(value, list)
159
160         (address_ids, blank, addresses) = self.separate_types(value)
161
162         for address in addresses:
163             if 'address_id' in address:
164                 address_ids.append(address['address_id'])               
165
166         # Add new ids, remove stale ids
167         if self['address_ids'] != address_ids:
168             from PLC.Methods.DeleteAddress import DeleteAddress
169             stale_addresses = set(self['address_ids']).difference(address_ids)
170
171             for stale_address in stale_addresses:
172                 DeleteAddress.__call__(DeleteAddress(self.api), auth, stale_address)    
173         
174         if addresses:
175             from PLC.Methods.AddSiteAddress import AddSiteAddress
176             from PLC.Methods.UpdateAddress import UpdateAddress
177                 
178             updated_addresses = filter(lambda address: 'address_id' in address, addresses)
179             added_addresses = filter(lambda address: 'address_id' not in address, addresses)
180                 
181             for address in added_addresses:
182                 AddSiteAddress.__call__(AddSiteAddress(self.api), auth, self['site_id'], address)       
183             for address in updated_addresses:
184                 address_id = address.pop('address_id')
185                 UpdateAddress.__call__(UpdateAddress(self.api), auth, address_id, address)
186
187     def delete(self, commit = True):
188         """
189         Delete existing site.
190         """
191
192         assert 'site_id' in self
193
194         # Delete accounts of all people at the site who are not
195         # members of at least one other non-deleted site.
196         persons = Persons(self.api, self['person_ids'])
197         for person in persons:
198             delete = True
199
200             person_sites = Sites(self.api, person['site_ids'])
201             for person_site in person_sites:
202                 if person_site['site_id'] != self['site_id']:
203                     delete = False
204                     break
205
206             if delete:
207                 person.delete(commit = False)
208
209         # Delete all site addresses
210         addresses = Addresses(self.api, self['address_ids'])
211         for address in addresses:
212             address.delete(commit = False)
213
214         # Delete all site slices
215         slices = Slices(self.api, self['slice_ids'])
216         for slice in slices:
217             slice.delete(commit = False)
218
219         # Delete all site PCUs
220         pcus = PCUs(self.api, self['pcu_ids'])
221         for pcu in pcus:
222             pcu.delete(commit = False)
223
224         # Delete all site nodes
225         nodes = Nodes(self.api, self['node_ids'])
226         for node in nodes:
227             node.delete(commit = False)
228
229         # Clean up miscellaneous join tables
230         for table in self.join_tables:
231             self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
232                            (table, self['site_id']))
233
234         # Mark as deleted
235         self['deleted'] = True
236         self.sync(commit)
237
238 class Sites(Table):
239     """
240     Representation of row(s) from the sites table in the
241     database.
242     """
243
244     def __init__(self, api, site_filter = None, columns = None):
245         Table.__init__(self, api, Site, columns)
246         
247         view = "view_sites"
248         for tagname in self.tag_columns:
249             view= "%s left join %s using (%s)"%(view,Site.tagvalue_view_name(tagname),
250                                                 Site.primary_key)
251             
252         sql = "SELECT %s FROM %s WHERE deleted IS False" % \
253             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
254
255         if site_filter is not None:
256             if isinstance(site_filter, (list, tuple, set)):
257                 # Separate the list into integers and strings
258                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
259                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
260                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
261                 sql += " AND (%s) %s" % site_filter.sql(api, "OR")
262             elif isinstance(site_filter, dict):
263                 site_filter = Filter(Site.fields, site_filter)
264                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
265             elif isinstance (site_filter, StringTypes):
266                 site_filter = Filter(Site.fields, {'login_base':[site_filter]})
267                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
268             elif isinstance (site_filter, int):
269                 site_filter = Filter(Site.fields, {'site_id':[site_filter]})
270                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
271             else:
272                 raise PLCInvalidArgument, "Wrong site filter %r"%site_filter
273
274         self.selectall(sql)