caching sites, connected to persons, and nodes
[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
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.NodeGroups import NodeGroup, NodeGroups
13 from PLC.Addresses import Address, Addresses
14 import PLC.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     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         '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 at which this slice was created", nullok = True),
44         }
45
46     # for Cache
47     class_key = 'login_base'
48     foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
49                       'url', 'date_created', 'last_updated', 'max_slices', 'max_slivers',
50                       ]
51     foreign_xrefs = {
52 #'person_ids',
53 #'slice_ids',
54 #'node_ids',
55 #'address_ids',
56 #'pcu_ids',
57 }
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_letters.lower()):
72             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters"
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 not self.has_key('longitude') 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 not self.has_key('latitude') or \
90            self['latitude'] is None:
91             raise PLCInvalidArgument, "Latitude must also be specified"
92
93         return longitude
94
95     def add_person(self, person, commit = True):
96         """
97         Add person to existing site.
98         """
99
100         assert 'site_id' in self
101         assert isinstance(person, PLC.Persons.Person)
102         assert 'person_id' in person
103
104         site_id = self['site_id']
105         person_id = person['person_id']
106
107         if person_id not in self['person_ids']:
108             assert site_id not in person['site_ids']
109
110             self.api.db.do("INSERT INTO person_site (person_id, site_id)" \
111                            " VALUES(%(person_id)d, %(site_id)d)",
112                            locals())
113
114             if commit:
115                 self.api.db.commit()
116
117             self['person_ids'].append(person_id)
118             person['site_ids'].append(site_id)
119
120     def remove_person(self, person, commit = True):
121         """
122         Remove person from existing site.
123         """
124
125         assert 'site_id' in self
126         assert isinstance(person, PLC.Persons.Person)
127         assert 'person_id' in person
128
129         site_id = self['site_id']
130         person_id = person['person_id']
131
132         if person_id in self['person_ids']:
133             assert site_id in person['site_ids']
134
135             self.api.db.do("DELETE FROM person_site" \
136                            " WHERE person_id = %(person_id)d" \
137                            " AND site_id = %(site_id)d",
138                            locals())
139
140             if commit:
141                 self.api.db.commit()
142
143             self['person_ids'].remove(person_id)
144             person['site_ids'].remove(site_id)
145
146     def add_address(self, address, commit = True):
147         """
148         Add address to existing site.
149         """
150
151         assert 'site_id' in self
152         assert isinstance(address, Address)
153         assert 'address_id' in address
154
155         site_id = self['site_id']
156         address_id = address['address_id']
157
158         if address_id not in self['address_ids']:
159             self.api.db.do("INSERT INTO site_address (address_id, site_id)" \
160                            " VALUES(%(address_id)d, %(site_id)d)",
161                            locals())
162
163             if commit:
164                 self.api.db.commit()
165
166             self['address_ids'].append(address_id)
167
168     def remove_address(self, address, commit = True):
169         """
170         Remove address from existing site.
171         """
172
173         assert 'site_id' in self
174         assert isinstance(address, Address)
175         assert 'address_id' in address
176
177         site_id = self['site_id']
178         address_id = address['address_id']
179
180         if address_id in self['address_ids']:
181             self.api.db.do("DELETE FROM site_address" \
182                            " WHERE address_id = %(address_id)d" \
183                            " AND site_id = %(site_id)d",
184                            locals())
185
186             if commit:
187                 self.api.db.commit()
188
189             self['address_ids'].remove(address_id)
190
191     def delete(self, commit = True):
192         """
193         Delete existing site.
194         """
195
196         assert 'site_id' in self
197
198         # Delete accounts of all people at the site who are not
199         # members of at least one other non-deleted site.
200         persons = PLC.Persons.Persons(self.api, self['person_ids'])
201         for person in persons:
202             delete = True
203
204             person_sites = Sites(self.api, person['site_ids'])
205             for person_site in person_sites:
206                 if person_site['site_id'] != self['site_id']:
207                     delete = False
208                     break
209
210             if delete:
211                 person.delete(commit = False)
212
213         # Delete all site addresses
214         addresses = Addresses(self.api, self['address_ids'])
215         for address in addresses:
216             address.delete(commit = False)
217
218         # Delete all site slices
219         slices = Slices(self.api, self['slice_ids'])
220         for slice in slices:
221             slice.delete(commit = False)
222
223         # Delete all site PCUs
224         pcus = PCUs(self.api, self['pcu_ids'])
225         for pcu in pcus:
226             pcu.delete(commit = False)
227
228         # Delete all site nodes
229         nodes = Nodes(self.api, self['node_ids'])
230         for node in nodes:
231             node.delete(commit = False)
232
233         # Clean up miscellaneous join tables
234         for table in ['person_site']:
235             self.api.db.do("DELETE FROM %s" \
236                            " WHERE site_id = %d" % \
237                            (table, self['site_id']), self)
238
239         # Mark as deleted
240         self['deleted'] = True
241         self.sync(commit)
242
243 class Sites(Table):
244     """
245     Representation of row(s) from the sites table in the
246     database.
247     """
248
249     def __init__(self, api, site_filter = None, columns = None):
250         Table.__init__(self, api, Site, columns)
251
252         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
253               ", ".join(self.columns)
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)" % site_filter.sql(api, "OR")
262             elif isinstance(site_filter, dict):
263                 site_filter = Filter(Site.fields, site_filter)
264                 sql += " AND (%s)" % site_filter.sql(api, "AND")
265
266         self.selectall(sql)