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