|
1 # Copyright 2009 the Melange authors. |
|
2 # |
|
3 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 # you may not use this file except in compliance with the License. |
|
5 # You may obtain a copy of the License at |
|
6 # |
|
7 # http://www.apache.org/licenses/LICENSE-2.0 |
|
8 # |
|
9 # Unless required by applicable law or agreed to in writing, software |
|
10 # distributed under the License is distributed on an "AS IS" BASIS, |
|
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 # See the License for the specific language governing permissions and |
|
13 # limitations under the License. |
|
14 |
|
15 """The Melange Core module. |
|
16 """ |
|
17 |
|
18 __authors__ = [ |
|
19 '"Sverre Rabbelier" <sverre@rabbelier.nl>', |
|
20 '"Lennard de Rijk" <ljvderijk@gmail.com>', |
|
21 ] |
|
22 |
|
23 |
|
24 from django.conf.urls import defaults |
|
25 |
|
26 import settings |
|
27 import soc.cache.sidebar |
|
28 |
|
29 |
|
30 class Error(Exception): |
|
31 """Error class for the callback module. |
|
32 """ |
|
33 |
|
34 pass |
|
35 |
|
36 |
|
37 class APIVersionMismatch(Error): |
|
38 """Error raised when API version mismatches. |
|
39 """ |
|
40 |
|
41 MISMATCH_MSG_FMT = "API mismatch, expected '%d', got '%d'." |
|
42 |
|
43 def __init__(self, expected, actual): |
|
44 """Instantiates a new exception with a customized message. |
|
45 """ |
|
46 |
|
47 msg = self.MISMATCH_MSG_FMT % (expected, actual) |
|
48 super(APIVersionMismatch, self).__init__(msg) |
|
49 |
|
50 |
|
51 class MissingService(Error): |
|
52 """Error raised when a required service is missing. |
|
53 """ |
|
54 |
|
55 MISSING_SERVICE_FMT = "Required service '%s' is not registered, known: %s" |
|
56 |
|
57 def __init__(self, service, services): |
|
58 """Instantiates a new exception with a customized message. |
|
59 """ |
|
60 |
|
61 msg = self.MISSING_SERVICE_FMT % (service, services) |
|
62 super(MissingService, self).__init__(msg) |
|
63 |
|
64 |
|
65 class NonUniqueService(Error): |
|
66 """Error raised when a required service is missing. |
|
67 """ |
|
68 |
|
69 NON_UNIQUE_SERVICE_FMT = "Unique service '%s' called a second time, known: %s." |
|
70 |
|
71 def __init__(self, service, services): |
|
72 """Instantiates a new exception with a customized message. |
|
73 """ |
|
74 |
|
75 msg = self.NON_UNIQUE_SERVICE_FMT % (service, services) |
|
76 super(NonUniqueService, self).__init__(msg) |
|
77 |
|
78 |
|
79 class Core(object): |
|
80 """The core handler that controls the Melange API. |
|
81 """ |
|
82 |
|
83 def __init__(self): |
|
84 """Creates a new instance of the Core. |
|
85 """ |
|
86 |
|
87 self.API_VERSION = 1 |
|
88 |
|
89 self.registered_callbacks = [] |
|
90 self.capability = [] |
|
91 self.services = [] |
|
92 |
|
93 self.sitemap = [] |
|
94 self.sidebar = [] |
|
95 |
|
96 ## |
|
97 ## internal |
|
98 ## |
|
99 |
|
100 def getService(self, callback, service): |
|
101 """Retrieves the specified service from the callback if supported. |
|
102 |
|
103 Args: |
|
104 callback: the callback to retrieve the capability from |
|
105 service: the service to retrieve |
|
106 """ |
|
107 |
|
108 if not hasattr(callback, service): |
|
109 return False |
|
110 |
|
111 func = getattr(callback, service) |
|
112 |
|
113 if not callable(func): |
|
114 return False |
|
115 |
|
116 return func |
|
117 |
|
118 ## |
|
119 ## Core code |
|
120 ## |
|
121 |
|
122 def getPatterns(self): |
|
123 """Returns the Django patterns for this site. |
|
124 """ |
|
125 |
|
126 self.callService('registerWithSitemap', True) |
|
127 return defaults.patterns(None, *self.sitemap) |
|
128 |
|
129 @soc.cache.sidebar.cache |
|
130 def getSidebar(self, id, user): |
|
131 """Constructs a sidebar for the current user. |
|
132 """ |
|
133 |
|
134 self.callService('registerWithSidebar', True) |
|
135 |
|
136 sidebar = [] |
|
137 |
|
138 for i in self.sidebar: |
|
139 menus = i(id, user) |
|
140 |
|
141 for menu in (menus if menus else []): |
|
142 sidebar.append(menu) |
|
143 |
|
144 return sorted(sidebar, key=lambda x: x.get('group')) |
|
145 |
|
146 def callService(self, service, unique, *args, **kwargs): |
|
147 """Calls the specified service on all callbacks. |
|
148 """ |
|
149 |
|
150 if unique and (service in self.services): |
|
151 return |
|
152 |
|
153 results = [] |
|
154 |
|
155 for callback in self.registered_callbacks: |
|
156 func = self.getService(callback, service) |
|
157 if not func: |
|
158 continue |
|
159 |
|
160 result = func(*args, **kwargs) |
|
161 results.append(result) |
|
162 |
|
163 self.services.append(service) |
|
164 return results |
|
165 |
|
166 def registerModuleCallbacks(self): |
|
167 """Retrieves all callbacks for the modules of this site. |
|
168 |
|
169 Callbacks for modules without a version number or the wrong API_VERSION |
|
170 number are dropped. They won't be called. |
|
171 """ |
|
172 |
|
173 fmt = settings.MODULE_FMT |
|
174 modules = ['soc_core'] + settings.MODULES |
|
175 modules = [__import__(fmt % i, fromlist=['']) for i in modules] |
|
176 |
|
177 for callback_class in [i.getCallback() for i in modules]: |
|
178 if callback_class.API_VERSION != self.API_VERSION: |
|
179 raise callback.APIVersionMismatch(self.API_VERSION, |
|
180 callback_class.API_VERSION) |
|
181 |
|
182 |
|
183 callback = callback_class(self) |
|
184 self.registered_callbacks.append(callback) |
|
185 |
|
186 return True |
|
187 |
|
188 ## |
|
189 ## Module code |
|
190 ## |
|
191 |
|
192 def registerCapability(self, capability): |
|
193 """Registers the specified capability. |
|
194 """ |
|
195 |
|
196 self.capabilities.append(capability) |
|
197 |
|
198 def requireCapability(self, capability): |
|
199 """Requires that the specified capability is present. |
|
200 """ |
|
201 |
|
202 if capability in self.capabilities: |
|
203 return True |
|
204 |
|
205 raise MissingCapability(capability, self.capability) |
|
206 |
|
207 def requireService(self, service): |
|
208 """Requires that the specified service has been called. |
|
209 """ |
|
210 |
|
211 if service in self.services: |
|
212 return True |
|
213 |
|
214 raise MissingService(service, self.services) |
|
215 |
|
216 def requireUniqueService(self, service): |
|
217 """Requires that the specified service is called exactly once. |
|
218 """ |
|
219 |
|
220 if service not in self.services: |
|
221 return True |
|
222 |
|
223 raise NonUniqueService(service, self.services) |
|
224 |
|
225 def registerSitemapEntry(self, entries): |
|
226 """Registers the specified entries with the sitemap. |
|
227 """ |
|
228 |
|
229 self.sitemap.extend(entries) |
|
230 |
|
231 def registerSidebarEntry(self, entry): |
|
232 """Registers the specified entry with the sidebar. |
|
233 """ |
|
234 |
|
235 self.sidebar.append(entry) |