- remove unnecessary import of NodeGroups
[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.Addresses import Address, Addresses
13 from PLC.Persons import Person, Persons
14
15 class Site(Row):
16     """
17     Representation of a row in the sites table. To use, optionally
18     instantiate with a dict of values. Update as you would a
19     dict. Commit to the database with sync().
20     """
21
22     table_name = 'sites'
23     primary_key = 'site_id'
24     join_tables = ['person_site', 'site_address', 'peer_site']
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 to which this site belongs", nullok = True),
44         'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
45         }
46
47     # for Cache
48     class_key = 'login_base'
49     foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
50                       'url', 'max_slices', 'max_slivers',
51                       ]
52     # forget about these ones, they are read-only anyway
53     # handling them causes Cache to re-sync all over again 
54     # 'last_updated', 'date_created'
55     foreign_xrefs = []
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.ascii_letters.lower()):
70             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters"
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 delete(self, commit = True):
103         """
104         Delete existing site.
105         """
106
107         assert 'site_id' in self
108
109         # Delete accounts of all people at the site who are not
110         # members of at least one other non-deleted site.
111         persons = Persons(self.api, self['person_ids'])
112         for person in persons:
113             delete = True
114
115             person_sites = Sites(self.api, person['site_ids'])
116             for person_site in person_sites:
117                 if person_site['site_id'] != self['site_id']:
118                     delete = False
119                     break
120
121             if delete:
122                 person.delete(commit = False)
123
124         # Delete all site addresses
125         addresses = Addresses(self.api, self['address_ids'])
126         for address in addresses:
127             address.delete(commit = False)
128
129         # Delete all site slices
130         slices = Slices(self.api, self['slice_ids'])
131         for slice in slices:
132             slice.delete(commit = False)
133
134         # Delete all site PCUs
135         pcus = PCUs(self.api, self['pcu_ids'])
136         for pcu in pcus:
137             pcu.delete(commit = False)
138
139         # Delete all site nodes
140         nodes = Nodes(self.api, self['node_ids'])
141         for node in nodes:
142             node.delete(commit = False)
143
144         # Clean up miscellaneous join tables
145         for table in self.join_tables:
146             self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
147                            (table, self['site_id']))
148
149         # Mark as deleted
150         self['deleted'] = True
151         self.sync(commit)
152
153 class Sites(Table):
154     """
155     Representation of row(s) from the sites table in the
156     database.
157     """
158
159     def __init__(self, api, site_filter = None, columns = None):
160         Table.__init__(self, api, Site, columns)
161
162         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
163               ", ".join(self.columns)
164
165         if site_filter is not None:
166             if isinstance(site_filter, (list, tuple, set)):
167                 # Separate the list into integers and strings
168                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
169                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
170                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
171                 sql += " AND (%s)" % site_filter.sql(api, "OR")
172             elif isinstance(site_filter, dict):
173                 site_filter = Filter(Site.fields, site_filter)
174                 sql += " AND (%s)" % site_filter.sql(api, "AND")
175
176         self.selectall(sql)