(*) direct cross refs redefined as NOT NULL in the database
[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
53     def validate_name(self, name):
54         if not len(name):
55             raise PLCInvalidArgument, "Name must be specified"
56
57         return name
58
59     validate_abbreviated_name = validate_name
60
61     def validate_login_base(self, login_base):
62         if not len(login_base):
63             raise PLCInvalidArgument, "Login base must be specified"
64
65         if not set(login_base).issubset(string.ascii_letters.lower()):
66             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters"
67
68         conflicts = Sites(self.api, [login_base])
69         for site in conflicts:
70             if 'site_id' not in self or self['site_id'] != site['site_id']:
71                 raise PLCInvalidArgument, "login_base already in use"
72
73         return login_base
74
75     def validate_latitude(self, latitude):
76         if not self.has_key('longitude') or \
77            self['longitude'] is None:
78             raise PLCInvalidArgument, "Longitude must also be specified"
79
80         return latitude
81
82     def validate_longitude(self, longitude):
83         if not self.has_key('latitude') or \
84            self['latitude'] is None:
85             raise PLCInvalidArgument, "Latitude must also be specified"
86
87         return longitude
88
89     def add_person(self, person, commit = True):
90         """
91         Add person to existing site.
92         """
93
94         assert 'site_id' in self
95         assert isinstance(person, PLC.Persons.Person)
96         assert 'person_id' in person
97
98         site_id = self['site_id']
99         person_id = person['person_id']
100
101         if person_id not in self['person_ids']:
102             assert site_id not in person['site_ids']
103
104             self.api.db.do("INSERT INTO person_site (person_id, site_id)" \
105                            " VALUES(%(person_id)d, %(site_id)d)",
106                            locals())
107
108             if commit:
109                 self.api.db.commit()
110
111             self['person_ids'].append(person_id)
112             person['site_ids'].append(site_id)
113
114     def remove_person(self, person, commit = True):
115         """
116         Remove person from existing site.
117         """
118
119         assert 'site_id' in self
120         assert isinstance(person, PLC.Persons.Person)
121         assert 'person_id' in person
122
123         site_id = self['site_id']
124         person_id = person['person_id']
125
126         if person_id in self['person_ids']:
127             assert site_id in person['site_ids']
128
129             self.api.db.do("DELETE FROM person_site" \
130                            " WHERE person_id = %(person_id)d" \
131                            " AND site_id = %(site_id)d",
132                            locals())
133
134             if commit:
135                 self.api.db.commit()
136
137             self['person_ids'].remove(person_id)
138             person['site_ids'].remove(site_id)
139
140     def add_address(self, address, commit = True):
141         """
142         Add address to existing site.
143         """
144
145         assert 'site_id' in self
146         assert isinstance(address, Address)
147         assert 'address_id' in address
148
149         site_id = self['site_id']
150         address_id = address['address_id']
151
152         if address_id not in self['address_ids']:
153             self.api.db.do("INSERT INTO site_address (address_id, site_id)" \
154                            " VALUES(%(address_id)d, %(site_id)d)",
155                            locals())
156
157             if commit:
158                 self.api.db.commit()
159
160             self['address_ids'].append(address_id)
161
162     def remove_address(self, address, commit = True):
163         """
164         Remove address from existing site.
165         """
166
167         assert 'site_id' in self
168         assert isinstance(address, Address)
169         assert 'address_id' in address
170
171         site_id = self['site_id']
172         address_id = address['address_id']
173
174         if address_id in self['address_ids']:
175             self.api.db.do("DELETE FROM site_address" \
176                            " WHERE address_id = %(address_id)d" \
177                            " AND site_id = %(site_id)d",
178                            locals())
179
180             if commit:
181                 self.api.db.commit()
182
183             self['address_ids'].remove(address_id)
184
185     def delete(self, commit = True):
186         """
187         Delete existing site.
188         """
189
190         assert 'site_id' in self
191
192         # Delete accounts of all people at the site who are not
193         # members of at least one other non-deleted site.
194         persons = PLC.Persons.Persons(self.api, self['person_ids'])
195         for person in persons:
196             delete = True
197
198             person_sites = Sites(self.api, person['site_ids'])
199             for person_site in person_sites:
200                 if person_site['site_id'] != self['site_id']:
201                     delete = False
202                     break
203
204             if delete:
205                 person.delete(commit = False)
206
207         # Delete all site addresses
208         addresses = Addresses(self.api, self['address_ids'])
209         for address in addresses:
210             address.delete(commit = False)
211
212         # Delete all site slices
213         slices = Slices(self.api, self['slice_ids'])
214         for slice in slices:
215             slice.delete(commit = False)
216
217         # Delete all site PCUs
218         pcus = PCUs(self.api, self['pcu_ids'])
219         for pcu in pcus:
220             pcu.delete(commit = False)
221
222         # Delete all site nodes
223         nodes = Nodes(self.api, self['node_ids'])
224         for node in nodes:
225             node.delete(commit = False)
226
227         # Clean up miscellaneous join tables
228         for table in ['person_site']:
229             self.api.db.do("DELETE FROM %s" \
230                            " WHERE site_id = %d" % \
231                            (table, self['site_id']), self)
232
233         # Mark as deleted
234         self['deleted'] = True
235         self.sync(commit)
236
237 class Sites(Table):
238     """
239     Representation of row(s) from the sites table in the
240     database.
241     """
242
243     def __init__(self, api, site_filter = None, columns = None):
244         Table.__init__(self, api, Site, columns)
245
246         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
247               ", ".join(self.columns)
248
249         if site_filter is not None:
250             if isinstance(site_filter, (list, tuple, set)):
251                 # Separate the list into integers and strings
252                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
253                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
254                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
255                 sql += " AND (%s)" % site_filter.sql(api, "OR")
256             elif isinstance(site_filter, dict):
257                 site_filter = Filter(Site.fields, site_filter)
258                 sql += " AND (%s)" % site_filter.sql(api, "AND")
259
260         self.selectall(sql)