- added update_last_updated(). Records when a record was last updated
[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         'enabled': Parameter(bool, "Has been enabled"),
32         'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
33         'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
34         'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
35         'date_created': Parameter(int, "Date and time when site entry was created, in seconds since UNIX epoch", ro = True),
36         'last_updated': Parameter(int, "Date and time when site entry was last updated, in seconds since UNIX epoch", ro = True),
37         'max_slices': Parameter(int, "Maximum number of slices that the site is able to create"),
38         'max_slivers': Parameter(int, "Maximum number of slivers that the site is able to create"),
39         'person_ids': Parameter([int], "List of account identifiers"),
40         'slice_ids': Parameter([int], "List of slice identifiers"),
41         'address_ids': Parameter([int], "List of address identifiers"),
42         'pcu_ids': Parameter([int], "List of PCU identifiers"),
43         'node_ids': Parameter([int], "List of site node identifiers"),
44         'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
45         'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
46         'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
47         }
48
49     # for Cache
50     class_key = 'login_base'
51     foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
52                       'url', 'max_slices', 'max_slivers',
53                       ]
54     # forget about these ones, they are read-only anyway
55     # handling them causes Cache to re-sync all over again 
56     # 'last_updated', 'date_created'
57     foreign_xrefs = []
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.lowercase + string.digits):
72             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers"
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     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     def delete(self, commit = True):
117         """
118         Delete existing site.
119         """
120
121         assert 'site_id' in self
122
123         # Delete accounts of all people at the site who are not
124         # members of at least one other non-deleted site.
125         persons = Persons(self.api, self['person_ids'])
126         for person in persons:
127             delete = True
128
129             person_sites = Sites(self.api, person['site_ids'])
130             for person_site in person_sites:
131                 if person_site['site_id'] != self['site_id']:
132                     delete = False
133                     break
134
135             if delete:
136                 person.delete(commit = False)
137
138         # Delete all site addresses
139         addresses = Addresses(self.api, self['address_ids'])
140         for address in addresses:
141             address.delete(commit = False)
142
143         # Delete all site slices
144         slices = Slices(self.api, self['slice_ids'])
145         for slice in slices:
146             slice.delete(commit = False)
147
148         # Delete all site PCUs
149         pcus = PCUs(self.api, self['pcu_ids'])
150         for pcu in pcus:
151             pcu.delete(commit = False)
152
153         # Delete all site nodes
154         nodes = Nodes(self.api, self['node_ids'])
155         for node in nodes:
156             node.delete(commit = False)
157
158         # Clean up miscellaneous join tables
159         for table in self.join_tables:
160             self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
161                            (table, self['site_id']))
162
163         # Mark as deleted
164         self['deleted'] = True
165         self.sync(commit)
166
167 class Sites(Table):
168     """
169     Representation of row(s) from the sites table in the
170     database.
171     """
172
173     def __init__(self, api, site_filter = None, columns = None):
174         Table.__init__(self, api, Site, columns)
175
176         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
177               ", ".join(self.columns)
178
179         if site_filter is not None:
180             if isinstance(site_filter, (list, tuple, set)):
181                 # Separate the list into integers and strings
182                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
183                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
184                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
185                 sql += " AND (%s)" % site_filter.sql(api, "OR")
186             elif isinstance(site_filter, dict):
187                 site_filter = Filter(Site.fields, site_filter)
188                 sql += " AND (%s)" % site_filter.sql(api, "AND")
189
190         self.selectall(sql)