Merge lp:~aelkner/schooltool/demo_fields into lp:schooltool/1.7
- demo_fields
- Merge into 1.7
Proposed by
Alan Elkner
Status: | Merged |
---|---|
Merged at revision: | 2749 |
Proposed branch: | lp:~aelkner/schooltool/demo_fields |
Merge into: | lp:schooltool/1.7 |
Diff against target: |
2683 lines (+1765/-248) (has conflicts) 27 files modified
CHANGES.txt (+6/-0) src/schooltool/basicperson/browser/configure.zcml (+90/-8) src/schooltool/basicperson/browser/demographics.py (+22/-19) src/schooltool/basicperson/browser/ftests/basicperson.txt (+2/-2) src/schooltool/basicperson/browser/ftests/demographics.txt (+87/-4) src/schooltool/basicperson/browser/ftests/group_aware.txt (+123/-0) src/schooltool/basicperson/browser/person.py (+124/-1) src/schooltool/basicperson/browser/templates/demographics-view.pt (+2/-2) src/schooltool/basicperson/browser/templates/person_form.pt (+138/-0) src/schooltool/basicperson/configure.zcml (+15/-0) src/schooltool/basicperson/demographics.py (+31/-3) src/schooltool/basicperson/interfaces.py (+35/-2) src/schooltool/basicperson/tests/test_demographics.py (+87/-0) src/schooltool/basicperson/vocabularies.py (+30/-0) src/schooltool/resource/browser/configure.zcml (+159/-133) src/schooltool/resource/browser/demographics.txt (+50/-0) src/schooltool/resource/browser/resource-booking.txt (+1/-1) src/schooltool/resource/browser/resource.py (+339/-51) src/schooltool/resource/browser/resources.txt (+4/-9) src/schooltool/resource/browser/subtype_widget.pt (+8/-5) src/schooltool/resource/browser/templates/resource_form.pt (+138/-0) src/schooltool/resource/browser/templates/resource_view.pt (+36/-0) src/schooltool/resource/configure.zcml (+21/-1) src/schooltool/resource/ftesting.zcml (+1/-0) src/schooltool/resource/interfaces.py (+23/-3) src/schooltool/resource/resource.py (+109/-4) src/schooltool/resource/tests/test_resource.py (+84/-0) Text conflict in CHANGES.txt |
To merge this branch: | bzr merge lp:~aelkner/schooltool/demo_fields |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
SchoolTool Owners | Pending | ||
Review via email: mp+45818@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 2757. By Alan Elkner <aelkner@ubuntu>
-
updated CHANGES.txt
- 2758. By Alan Elkner <aelkner@ubuntu>
-
moved resource limit_keys vocab to resource package
- 2759. By Alan Elkner <aelkner@ubuntu>
-
added functional tests for the limit_keys vocanulary
- 2760. By Alan Elkner <aelkner@ubuntu>
-
merged from Justus branch
- 2761. By Alan Elkner <aelkner@ubuntu>
-
added filter_keys method to field description container
used it in generateExtraFields for add/edit person views - 2762. By Alan Elkner <aelkner@ubuntu>
-
created group-aware (teacher, student, adinistrator) person add views
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'CHANGES.txt' |
2 | --- CHANGES.txt 2010-11-23 22:58:23 +0000 |
3 | +++ CHANGES.txt 2011-01-17 05:56:49 +0000 |
4 | @@ -12,7 +12,13 @@ |
5 | ------------------ |
6 | |
7 | - Fixed Section meeting time misalignment in calendar (https://launchpad.net/bugs/611797) |
8 | +<<<<<<< TREE |
9 | - Added Bengali and Faroese translations, updated many others |
10 | +======= |
11 | +- Added BooFieldDescription for boolean demographic fields |
12 | +- Added limit_keys list to IFieldDescription and filter_key method to IDemographicsFields |
13 | +- Created resource demographics and changed resource views to have demos |
14 | +>>>>>>> MERGE-SOURCE |
15 | |
16 | |
17 | 1.5.2 (2010-10-04) |
18 | |
19 | === modified file 'src/schooltool/basicperson/browser/configure.zcml' |
20 | --- src/schooltool/basicperson/browser/configure.zcml 2010-09-22 09:40:18 +0000 |
21 | +++ src/schooltool/basicperson/browser/configure.zcml 2011-01-17 05:56:49 +0000 |
22 | @@ -3,13 +3,6 @@ |
23 | xmlns:zope="http://namespaces.zope.org/zope" |
24 | i18n_domain="schooltool"> |
25 | |
26 | - <zope:class class=".demographics.CustomEnumTextWidget"> |
27 | - <require |
28 | - permission="zope.Public" |
29 | - interface=".demographics.IEnumTextWidget" |
30 | - /> |
31 | - </zope:class> |
32 | - |
33 | <page |
34 | name="index.html" |
35 | for="schooltool.basicperson.interfaces.IBasicPerson" |
36 | @@ -171,6 +164,13 @@ |
37 | menu="schooltool_actions" title="New Date Field" /> |
38 | |
39 | <page |
40 | + name="addBool.html" |
41 | + for="schooltool.basicperson.interfaces.IDemographicsFields" |
42 | + class=".demographics.BoolFieldDescriptionAddView" |
43 | + permission="schooltool.edit" |
44 | + menu="schooltool_actions" title="New Yes/No Field" /> |
45 | + |
46 | + <page |
47 | name="addEnum.html" |
48 | for="schooltool.basicperson.interfaces.IDemographicsFields" |
49 | class=".demographics.EnumFieldDescriptionAddView" |
50 | @@ -193,6 +193,13 @@ |
51 | |
52 | <page |
53 | name="edit.html" |
54 | + for="schooltool.basicperson.demographics.BoolFieldDescription" |
55 | + class=".demographics.FieldDescriptionEditView" |
56 | + permission="schooltool.edit" |
57 | + menu="schooltool_actions" title="Edit" /> |
58 | + |
59 | + <page |
60 | + name="edit.html" |
61 | for="schooltool.basicperson.demographics.EnumFieldDescription" |
62 | class=".demographics.EnumFieldDescriptionEditView" |
63 | permission="schooltool.edit" |
64 | @@ -214,6 +221,13 @@ |
65 | |
66 | <page |
67 | name="index.html" |
68 | + for="schooltool.basicperson.demographics.BoolFieldDescription" |
69 | + class=".demographics.BoolFieldDescriptionView" |
70 | + permission="schooltool.view" |
71 | + menu="schooltool_actions" title="View" /> |
72 | + |
73 | + <page |
74 | + name="index.html" |
75 | for="schooltool.basicperson.demographics.EnumFieldDescription" |
76 | class=".demographics.EnumFieldDescriptionView" |
77 | permission="schooltool.view" |
78 | @@ -229,7 +243,22 @@ |
79 | name="absolute_url"/> |
80 | |
81 | <zope:adapter |
82 | - factory=".demographics.CustomEnumDataConverter" /> |
83 | + factory="z3c.form.browser.checkbox.CheckBoxFieldWidget" |
84 | + for="schooltool.basicperson.interfaces.FilterKeyList |
85 | + z3c.form.interfaces.IFormLayer" |
86 | + provides="z3c.form.interfaces.IFieldWidget" /> |
87 | + |
88 | + <zope:adapter |
89 | + factory=".demographics.CustomEnumDataConverter" |
90 | + for="schooltool.basicperson.interfaces.EnumValueList |
91 | + z3c.form.interfaces.ITextAreaWidget" |
92 | + /> |
93 | + |
94 | + <zope:adapter |
95 | + factory="z3c.form.browser.textarea.TextAreaFieldWidget" |
96 | + for="schooltool.basicperson.interfaces.EnumValueList |
97 | + z3c.form.interfaces.IFormLayer" |
98 | + /> |
99 | |
100 | <resourceDirectory |
101 | name="jstree" |
102 | @@ -241,4 +270,57 @@ |
103 | file="resources/jstree/jquery.min.js" |
104 | layer=".skin.IBasicPersonLayer" /> |
105 | |
106 | + <!-- Group-aware add views --> |
107 | + <page |
108 | + name="addTeacher.html" |
109 | + for="schooltool.person.interfaces.IPersonContainer" |
110 | + class=".person.TeacherAddView" |
111 | + permission="schooltool.edit" |
112 | + /> |
113 | + <page |
114 | + name="addStudent.html" |
115 | + for="schooltool.person.interfaces.IPersonContainer" |
116 | + class=".person.StudentAddView" |
117 | + permission="schooltool.edit" |
118 | + /> |
119 | + <page |
120 | + name="addAdministrator.html" |
121 | + for="schooltool.person.interfaces.IPersonContainer" |
122 | + class=".person.AdministratorAddView" |
123 | + permission="schooltool.edit" |
124 | + /> |
125 | + |
126 | + <configure package="schooltool.skin"> |
127 | + <navigationViewlet |
128 | + name="add_teacher" |
129 | + for="schooltool.person.interfaces.IPersonContainer" |
130 | + manager="schooltool.skin.IActionMenuManager" |
131 | + template="templates/actionsViewlet.pt" |
132 | + class="schooltool.skin.skin.ActionMenuViewlet" |
133 | + permission="schooltool.edit" |
134 | + link="addTeacher.html" |
135 | + title="Add Teacher" |
136 | + /> |
137 | + <navigationViewlet |
138 | + name="add_student" |
139 | + for="schooltool.person.interfaces.IPersonContainer" |
140 | + manager="schooltool.skin.IActionMenuManager" |
141 | + template="templates/actionsViewlet.pt" |
142 | + class="schooltool.skin.skin.ActionMenuViewlet" |
143 | + permission="schooltool.edit" |
144 | + link="addStudent.html" |
145 | + title="Add Student" |
146 | + /> |
147 | + <navigationViewlet |
148 | + name="add_administrator" |
149 | + for="schooltool.person.interfaces.IPersonContainer" |
150 | + manager="schooltool.skin.IActionMenuManager" |
151 | + template="templates/actionsViewlet.pt" |
152 | + class="schooltool.skin.skin.ActionMenuViewlet" |
153 | + permission="schooltool.edit" |
154 | + link="addAdministrator.html" |
155 | + title="Add Administrator" |
156 | + /> |
157 | + </configure> |
158 | + |
159 | </configure> |
160 | |
161 | === modified file 'src/schooltool/basicperson/browser/demographics.py' |
162 | --- src/schooltool/basicperson/browser/demographics.py 2010-08-30 16:09:34 +0000 |
163 | +++ src/schooltool/basicperson/browser/demographics.py 2011-01-17 05:56:49 +0000 |
164 | @@ -29,41 +29,28 @@ |
165 | from zope.container.interfaces import INameChooser |
166 | from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile |
167 | |
168 | -from z3c.form.browser.textarea import TextAreaWidget |
169 | from z3c.form.interfaces import ITextAreaWidget |
170 | from z3c.form import form, field, button |
171 | from z3c.form.converter import BaseDataConverter, FormatterValidationError |
172 | -from z3c.form.widget import FieldWidget |
173 | |
174 | from schooltool.app.interfaces import ISchoolToolApplication |
175 | from schooltool.basicperson.demographics import TextFieldDescription |
176 | from schooltool.basicperson.demographics import DateFieldDescription |
177 | +from schooltool.basicperson.demographics import BoolFieldDescription |
178 | from schooltool.basicperson.demographics import EnumFieldDescription |
179 | from schooltool.basicperson.interfaces import IEnumFieldDescription |
180 | from schooltool.basicperson.interfaces import IDemographicsFields |
181 | from schooltool.basicperson.interfaces import IFieldDescription |
182 | +from schooltool.basicperson.interfaces import EnumValueList |
183 | |
184 | from schooltool.common import format_message |
185 | from schooltool.common import SchoolToolMessage as _ |
186 | |
187 | |
188 | -class IEnumTextWidget(ITextAreaWidget): |
189 | - """Marker interface for custom enum widget.""" |
190 | - |
191 | - |
192 | -class CustomEnumTextWidget(TextAreaWidget): |
193 | - implements(IEnumTextWidget) |
194 | - |
195 | - |
196 | -def CustomEnumFieldTextWidget(field, request): |
197 | - """IFieldWidget factory for CustomEnumTextWidget.""" |
198 | - return FieldWidget(field, CustomEnumTextWidget(request)) |
199 | - |
200 | - |
201 | class CustomEnumDataConverter(BaseDataConverter): |
202 | """A special data converter for iso enums.""" |
203 | |
204 | - adapts(IList, CustomEnumTextWidget) |
205 | + adapts(EnumValueList, ITextAreaWidget) |
206 | |
207 | def toWidgetValue(self, value): |
208 | """See interfaces.IDataConverter""" |
209 | @@ -110,6 +97,8 @@ |
210 | class DemographicsView(BrowserView): |
211 | """A Demographics List view.""" |
212 | |
213 | + title = _('Demographics Container') |
214 | + |
215 | def demographics(self): |
216 | pos = 0 |
217 | for demo in list(self.context.values()): |
218 | @@ -195,10 +184,21 @@ |
219 | self._fd = fd |
220 | return fd |
221 | |
222 | + |
223 | +class BoolFieldDescriptionAddView(FieldDescriptionAddView): |
224 | + |
225 | + def create(self, data): |
226 | + fd = BoolFieldDescription(data['title'], |
227 | + str(data['name']), |
228 | + data['required']) |
229 | + form.applyChanges(self, fd, data) |
230 | + self._fd = fd |
231 | + return fd |
232 | + |
233 | + |
234 | class EnumFieldDescriptionAddView(FieldDescriptionAddView): |
235 | |
236 | fields = field.Fields(IEnumFieldDescription) |
237 | - fields['items'].widgetFactory = CustomEnumFieldTextWidget |
238 | |
239 | def create(self, data): |
240 | fd = EnumFieldDescription(data['title'], |
241 | @@ -234,7 +234,6 @@ |
242 | class EnumFieldDescriptionEditView(FieldDescriptionEditView): |
243 | |
244 | fields = field.Fields(IEnumFieldDescription).omit('name') |
245 | - fields['items'].widgetFactory = CustomEnumFieldTextWidget |
246 | |
247 | |
248 | class FieldDescriptionView(form.DisplayForm): |
249 | @@ -255,8 +254,12 @@ |
250 | """Display form for a date field description.""" |
251 | |
252 | |
253 | +class BoolFieldDescriptionView(FieldDescriptionView): |
254 | + """Display form for a bool field description.""" |
255 | + |
256 | + |
257 | class EnumFieldDescriptionView(FieldDescriptionView): |
258 | """Display form for an enum field description.""" |
259 | |
260 | fields = field.Fields(IEnumFieldDescription) |
261 | - fields['items'].widgetFactory = CustomEnumFieldTextWidget |
262 | + |
263 | |
264 | === modified file 'src/schooltool/basicperson/browser/ftests/basicperson.txt' |
265 | --- src/schooltool/basicperson/browser/ftests/basicperson.txt 2010-11-15 16:05:34 +0000 |
266 | +++ src/schooltool/basicperson/browser/ftests/basicperson.txt 2011-01-17 05:56:49 +0000 |
267 | @@ -36,7 +36,7 @@ |
268 | >>> manager.getLink('John') |
269 | <Link text='John' url='http://localhost/persons/john'> |
270 | |
271 | - >>> manager.getLink('Administrator', index=1) |
272 | + >>> manager.getLink('Administrator', index=2) |
273 | <Link text='Administrator' url='http://localhost/persons/manager'> |
274 | |
275 | You can filter the list: |
276 | @@ -49,7 +49,7 @@ |
277 | >>> manager.getLink('John') |
278 | <Link text='John' url='http://localhost/persons/john'> |
279 | |
280 | - >>> manager.getLink('Administrator', index=1) |
281 | + >>> manager.getLink('Administrator', index=2) |
282 | Traceback (most recent call last): |
283 | ... |
284 | LinkNotFoundError |
285 | |
286 | === modified file 'src/schooltool/basicperson/browser/ftests/demographics.txt' |
287 | --- src/schooltool/basicperson/browser/ftests/demographics.txt 2010-05-21 15:45:10 +0000 |
288 | +++ src/schooltool/basicperson/browser/ftests/demographics.txt 2011-01-17 05:56:49 +0000 |
289 | @@ -147,6 +147,8 @@ |
290 | >>> manager.getLink('Demographics').click() |
291 | >>> manager.getLink('ethnicity').click() |
292 | >>> manager.getLink('Edit').click() |
293 | + >>> print manager.getControl(name='form.widgets.limit_keys:list').value |
294 | + [] |
295 | >>> print manager.getControl('List of values').value |
296 | American Indian or Alaska Native |
297 | Asian |
298 | @@ -154,15 +156,33 @@ |
299 | Native Hawaiian or Other Pacific Islander |
300 | White |
301 | |
302 | -Let's make the field required, and add Martians to it: |
303 | +Let's make the field required, and add Martians to it. Also, we'll put limit |
304 | +keys into the empty list so that we can test that they get saved: |
305 | |
306 | + >>> manager.getControl(name='form.widgets.limit_keys:list').value = ['students', 'teachers'] |
307 | >>> values = manager.getControl('List of values').value.splitlines() |
308 | >>> manager.getControl('List of values').value = "\n".join(values + ['Martians']) |
309 | >>> manager.getControl('yes').click() |
310 | >>> manager.getControl('Apply').click() |
311 | |
312 | -Now if we go to the add person form, we should see the additional |
313 | -value in the select box, and have the field required: |
314 | +Calling the edit form back up, we see that our changes took: |
315 | + |
316 | + >>> manager.getLink('Manage').click() |
317 | + >>> manager.getLink('Demographics').click() |
318 | + >>> manager.getLink('ethnicity').click() |
319 | + >>> manager.getLink('Edit').click() |
320 | + >>> print manager.getControl(name='form.widgets.limit_keys:list').value |
321 | + ['students', 'teachers'] |
322 | + >>> print manager.getControl('List of values').value |
323 | + American Indian or Alaska Native |
324 | + Asian |
325 | + Black or African American |
326 | + Native Hawaiian or Other Pacific Islander |
327 | + White |
328 | + Martians |
329 | + |
330 | +When we go to the add person form, we should still not see the additional |
331 | +value in the select box as the group is not yet set: |
332 | |
333 | >>> manager.getLink('Manage').click() |
334 | >>> manager.getLink('Persons').click() |
335 | @@ -183,9 +203,42 @@ |
336 | Confirm password |
337 | ID |
338 | Language |
339 | - Ethnicity * |
340 | Citizenship |
341 | |
342 | +However, if we make john a teacher, it will show up when we edit him. |
343 | + |
344 | + >>> manager.getLink('Manage').click() |
345 | + >>> manager.getLink('Persons').click() |
346 | + >>> manager.getLink('John').click() |
347 | + >>> manager.getLink('Groups').click() |
348 | + >>> manager.getControl(name='add_item.teachers').value = 'checked' |
349 | + >>> manager.getControl('Add').click() |
350 | + |
351 | + >>> manager.getLink('Manage').click() |
352 | + >>> manager.getLink('Persons').click() |
353 | + >>> manager.getLink('John').click() |
354 | + >>> manager.getLink('Edit').click() |
355 | + >>> manager.getControl('Ethnicity').displayOptions |
356 | + ['American Indian or Alaska Native', |
357 | + 'Asian', |
358 | + 'Black or African American', |
359 | + 'Native Hawaiian or Other Pacific Islander', |
360 | + 'White', |
361 | + 'Martians'] |
362 | + |
363 | +When we change the demo to not have a limit_key, then it will show when we add |
364 | +a new person: |
365 | + |
366 | + >>> manager.getLink('Manage').click() |
367 | + >>> manager.getLink('Demographics').click() |
368 | + >>> manager.getLink('ethnicity').click() |
369 | + >>> manager.getLink('Edit').click() |
370 | + >>> manager.getControl(name='form.widgets.limit_keys:list').value = [] |
371 | + >>> manager.getControl('Apply').click() |
372 | + |
373 | + >>> manager.getLink('Manage').click() |
374 | + >>> manager.getLink('Persons').click() |
375 | + >>> manager.getLink('New Person').click() |
376 | >>> manager.getControl('Ethnicity').displayOptions |
377 | ['American Indian or Alaska Native', |
378 | 'Asian', |
379 | @@ -217,6 +270,36 @@ |
380 | 'Martians', |
381 | 'N\xc3\xb6rds'] |
382 | |
383 | +Boolean fields are available, so we'll create one called 'certified' and test |
384 | +the values in the display view: |
385 | + |
386 | + >>> manager.getLink('Manage').click() |
387 | + >>> manager.getLink('Demographics').click() |
388 | + >>> manager.getLink('New Yes/No Field').click() |
389 | + >>> manager.getControl('Title').value = "Certified" |
390 | + >>> manager.getControl('ID').value = "certified" |
391 | + >>> manager.getControl(name='form.widgets.required:list').value = ['true'] |
392 | + >>> manager.getControl('Add').click() |
393 | + |
394 | + >>> manager.printQuery('//span[@id="form-widgets-title"]') |
395 | + <span id="form-widgets-title" class="text-widget required textline-field">Certified</span> |
396 | + >>> manager.printQuery('//span[@id="form-widgets-name"]') |
397 | + <span id="form-widgets-name" class="text-widget required pythonidentifier-field">certified</span> |
398 | + >>> manager.printQuery('//span[@id="form-widgets-required"]//span') |
399 | + <span class="selected-option">yes</span> |
400 | + |
401 | +Now when we add a new person, the new 'Certified' field appears as a boolean |
402 | +radio field: |
403 | + |
404 | + >>> manager.getLink('Manage').click() |
405 | + >>> manager.getLink('Persons').click() |
406 | + >>> manager.getLink('New Person').click() |
407 | + |
408 | + >>> manager.printQuery('//input[@id="form-widgets-certified-0"]') |
409 | + <input id="form-widgets-certified-0" ... value="true" type="radio" /> |
410 | + >>> manager.printQuery('//input[@id="form-widgets-certified-1"]') |
411 | + <input id="form-widgets-certified-1" ... value="false" type="radio" /> |
412 | + |
413 | TODO |
414 | Test values being unset if you remove a field |
415 | Test export |
416 | |
417 | === added file 'src/schooltool/basicperson/browser/ftests/group_aware.txt' |
418 | --- src/schooltool/basicperson/browser/ftests/group_aware.txt 1970-01-01 00:00:00 +0000 |
419 | +++ src/schooltool/basicperson/browser/ftests/group_aware.txt 2011-01-17 05:56:49 +0000 |
420 | @@ -0,0 +1,123 @@ |
421 | +Group-aware add views |
422 | +===================== |
423 | + |
424 | +A manager logs in |
425 | + |
426 | + >>> manager = Browser('manager', 'schooltool') |
427 | + |
428 | + >>> from schooltool.app.browser.ftests import setup |
429 | + >>> setup.setUpBasicSchool() |
430 | + |
431 | +Let's change some of the default demographics fields to be group-specific: |
432 | + |
433 | + >>> manager.getLink('Manage').click() |
434 | + >>> manager.getLink('Demographics').click() |
435 | + >>> manager.getLink('ethnicity').click() |
436 | + >>> manager.getLink('Edit').click() |
437 | + >>> manager.getControl(name='form.widgets.limit_keys:list').value = ['students'] |
438 | + >>> manager.getControl('Apply').click() |
439 | + |
440 | + >>> manager.getLink('Manage').click() |
441 | + >>> manager.getLink('Demographics').click() |
442 | + >>> manager.getLink('language').click() |
443 | + >>> manager.getLink('Edit').click() |
444 | + >>> manager.getControl(name='form.widgets.limit_keys:list').value = ['teachers'] |
445 | + >>> manager.getControl('Apply').click() |
446 | + |
447 | + >>> manager.getLink('Manage').click() |
448 | + >>> manager.getLink('Demographics').click() |
449 | + >>> manager.getLink('citizenship').click() |
450 | + >>> manager.getLink('Edit').click() |
451 | + >>> manager.getControl(name='form.widgets.limit_keys:list').value = ['administrators'] |
452 | + >>> manager.getControl('Apply').click() |
453 | + |
454 | +Adding a student will have only student demos: |
455 | + |
456 | + >>> manager.getLink('Manage').click() |
457 | + >>> manager.getLink('Persons').click() |
458 | + >>> manager.getLink('Add Student').click() |
459 | + >>> manager.printQuery('//fieldset[@id="demo-data"]/div[@class="row"]/div[@class="label"]//span/text()') |
460 | + ID |
461 | + Ethnicity |
462 | + Place of birth |
463 | + |
464 | + >>> manager.getControl('First name').value = "John" |
465 | + >>> manager.getControl('Last name').value = "Petterson" |
466 | + >>> manager.getControl('Gender').value = ['male'] |
467 | + >>> manager.getControl('Birth date').value = '1980-01-01' |
468 | + >>> manager.getControl('Username').value = "john" |
469 | + >>> manager.getControl('Password').value = "pwd" |
470 | + >>> manager.getControl('Confirm password').value = "pwd" |
471 | + |
472 | + >>> manager.getControl('ID').value = "001122" |
473 | + >>> manager.getControl('Ethnicity').displayValue = ['Black or African American'] |
474 | + >>> manager.getControl('Place of birth').value = "South Hampton" |
475 | + >>> manager.getControl('Add').click() |
476 | + |
477 | + >>> manager.printQuery('//span[@id="form-widgets-ID"]/text()') |
478 | + 001122 |
479 | + >>> manager.printQuery('//span[@id="form-widgets-ethnicity"]/span/text()') |
480 | + Black or African American |
481 | + >>> manager.printQuery('//span[@id="form-widgets-placeofbirth"]/text()') |
482 | + South Hampton |
483 | + |
484 | +Adding a teacher will have only teacher demos: |
485 | + |
486 | + >>> manager.getLink('Manage').click() |
487 | + >>> manager.getLink('Persons').click() |
488 | + >>> manager.getLink('Add Teacher').click() |
489 | + >>> manager.printQuery('//fieldset[@id="demo-data"]/div[@class="row"]/div[@class="label"]//span/text()') |
490 | + ID |
491 | + Language |
492 | + Place of birth |
493 | + |
494 | + >>> manager.getControl('First name').value = "Bob" |
495 | + >>> manager.getControl('Last name').value = "Hall" |
496 | + >>> manager.getControl('Gender').value = ['male'] |
497 | + >>> manager.getControl('Birth date').value = '1980-01-01' |
498 | + >>> manager.getControl('Username').value = "bhall" |
499 | + >>> manager.getControl('Password').value = "pwd" |
500 | + >>> manager.getControl('Confirm password').value = "pwd" |
501 | + |
502 | + >>> manager.getControl('ID').value = "987654" |
503 | + >>> manager.getControl('Language').value = "French" |
504 | + >>> manager.getControl('Place of birth').value = "Boston" |
505 | + >>> manager.getControl('Add').click() |
506 | + |
507 | + >>> manager.printQuery('//span[@id="form-widgets-ID"]/text()') |
508 | + 987654 |
509 | + >>> manager.printQuery('//span[@id="form-widgets-language"]/text()') |
510 | + French |
511 | + >>> manager.printQuery('//span[@id="form-widgets-placeofbirth"]/text()') |
512 | + Boston |
513 | + |
514 | +Adding an administrator will have only administrator demos: |
515 | + |
516 | + >>> manager.getLink('Manage').click() |
517 | + >>> manager.getLink('Persons').click() |
518 | + >>> manager.getLink('Add Administrator').click() |
519 | + >>> manager.printQuery('//fieldset[@id="demo-data"]/div[@class="row"]/div[@class="label"]//span/text()') |
520 | + ID |
521 | + Place of birth |
522 | + Citizenship |
523 | + |
524 | + >>> manager.getControl('First name').value = "Jane" |
525 | + >>> manager.getControl('Last name').value = "Brown" |
526 | + >>> manager.getControl('Gender').value = ['male'] |
527 | + >>> manager.getControl('Birth date').value = '1980-01-01' |
528 | + >>> manager.getControl('Username').value = "jbrown" |
529 | + >>> manager.getControl('Password').value = "pwd" |
530 | + >>> manager.getControl('Confirm password').value = "pwd" |
531 | + |
532 | + >>> manager.getControl('ID').value = "123456" |
533 | + >>> manager.getControl('Place of birth').value = "Miami" |
534 | + >>> manager.getControl('Citizenship').value = 'Spain' |
535 | + >>> manager.getControl('Add').click() |
536 | + |
537 | + >>> manager.printQuery('//span[@id="form-widgets-ID"]/text()') |
538 | + 123456 |
539 | + >>> manager.printQuery('//span[@id="form-widgets-placeofbirth"]/text()') |
540 | + Miami |
541 | + >>> manager.printQuery('//span[@id="form-widgets-citizenship"]/text()') |
542 | + Spain |
543 | + |
544 | |
545 | === modified file 'src/schooltool/basicperson/browser/person.py' |
546 | --- src/schooltool/basicperson/browser/person.py 2010-11-15 16:06:20 +0000 |
547 | +++ src/schooltool/basicperson/browser/person.py 2011-01-17 05:56:49 +0000 |
548 | @@ -37,7 +37,9 @@ |
549 | |
550 | from schooltool.app.browser.app import RelationshipViewBase |
551 | from schooltool.app.interfaces import ISchoolToolApplication |
552 | +from schooltool.group.interfaces import IGroupContainer |
553 | from schooltool.person.interfaces import IPersonFactory |
554 | +from schooltool.schoolyear.interfaces import ISchoolYearContainer |
555 | |
556 | from schooltool.basicperson.interfaces import IDemographicsFields |
557 | from schooltool.basicperson.interfaces import IBasicPerson |
558 | @@ -135,7 +137,11 @@ |
559 | def generateExtraFields(self): |
560 | field_descriptions = IDemographicsFields(ISchoolToolApplication(None)) |
561 | fields = field.Fields() |
562 | - for field_desc in field_descriptions.values(): |
563 | + if IBasicPerson.providedBy(self.context): |
564 | + limit_keys = [group.__name__ for group in self.context.groups] |
565 | + else: |
566 | + limit_keys = [] |
567 | + for field_desc in field_descriptions.filter_keys(limit_keys): |
568 | fields += field_desc.makeField() |
569 | return fields |
570 | |
571 | @@ -458,3 +464,120 @@ |
572 | def getCollection(self): |
573 | return self.context.advisees |
574 | |
575 | + |
576 | +############### Base class of all group-aware add views ################ |
577 | +class PersonAddViewBase(PersonAddFormBase): |
578 | + |
579 | + id = 'person-form' |
580 | + template = ViewPageTemplateFile('templates/person_form.pt') |
581 | + |
582 | + def makeRows(self, fields, cols=1): |
583 | + rows = [] |
584 | + while fields: |
585 | + rows.append(fields[:cols]) |
586 | + fields = fields[cols:] |
587 | + return rows |
588 | + |
589 | + def makeFieldSet(self, fieldset_id, legend, fields, cols=1): |
590 | + result = { |
591 | + 'id': fieldset_id, |
592 | + 'legend': legend, |
593 | + } |
594 | + result['rows'] = self.makeRows(fields, cols) |
595 | + return result |
596 | + |
597 | + def fieldsets(self): |
598 | + result = [] |
599 | + sources = [ |
600 | + (self.base_id, self.base_legend, list(self.getBaseFields())), |
601 | + (self.demo_id, self.demo_legend, list(self.getDemoFields())), |
602 | + ] |
603 | + for fieldset_id, legend, fields in sources: |
604 | + result.append(self.makeFieldSet(fieldset_id, legend, fields, 2)) |
605 | + return result |
606 | + |
607 | + def getDemoFields(self): |
608 | + fields = field.Fields() |
609 | + dfs = IDemographicsFields(ISchoolToolApplication(None)) |
610 | + for field_desc in dfs.filter_key(self.group_id): |
611 | + fields += field_desc.makeField() |
612 | + return fields |
613 | + |
614 | + def getBaseFields(self): |
615 | + return field.Fields(IPersonAddForm).omit('group') |
616 | + |
617 | + def groupViewURL(self): |
618 | + return '%s/%s.html' % (absoluteURL(self.context, self.request), |
619 | + self.group_id) |
620 | + |
621 | + def updateActions(self): |
622 | + super(PersonAddViewBase, self).updateActions() |
623 | + self.actions['add'].addClass('button-ok') |
624 | + self.actions['cancel'].addClass('button-cancel') |
625 | + |
626 | + @button.buttonAndHandler(_('Add')) |
627 | + def handleAdd(self, action): |
628 | + super(PersonAddViewBase, self).handleAdd.func(self, action) |
629 | + |
630 | + @button.buttonAndHandler(_('Cancel')) |
631 | + def handle_cancel_action(self, action): |
632 | + self.request.response.redirect(self.groupViewURL()) |
633 | + |
634 | + def create(self, data): |
635 | + username = data.get('username') |
636 | + first_name = data.get('first_name') |
637 | + last_name = data.get('last_name') |
638 | + person = self._factory(username, first_name, last_name) |
639 | + data.pop('confirm') |
640 | + form.applyChanges(self, person, data) |
641 | + group = None |
642 | + syc = ISchoolYearContainer(ISchoolToolApplication(None)) |
643 | + active_schoolyear = syc.getActiveSchoolYear() |
644 | + if active_schoolyear is not None: |
645 | + group = IGroupContainer(active_schoolyear).get(self.group_id) |
646 | + if group is not None: |
647 | + person.groups.add(group) |
648 | + self._person = person |
649 | + return person |
650 | + |
651 | + def update(self): |
652 | + self.fields = self.getBaseFields() |
653 | + self.fields += self.getDemoFields() |
654 | + self.updateWidgets() |
655 | + self.updateActions() |
656 | + self.actions.execute() |
657 | + |
658 | + def updateWidgets(self): |
659 | + super(PersonAddViewBase, self).updateWidgets() |
660 | + |
661 | + |
662 | +############### Group-aware add views ################ |
663 | +class TeacherAddView(PersonAddViewBase): |
664 | + |
665 | + group_id = 'teachers' |
666 | + base_id = 'base-data' |
667 | + base_legend = _('Teacher identification') |
668 | + demo_id = 'demo-data' |
669 | + demo_legend = _('Teacher demographics') |
670 | + label = _('Add new teacher') |
671 | + |
672 | + |
673 | +class StudentAddView(PersonAddViewBase): |
674 | + |
675 | + group_id = 'students' |
676 | + base_id = 'base-data' |
677 | + base_legend = _('Student identification') |
678 | + demo_id = 'demo-data' |
679 | + demo_legend = _('Student demographics') |
680 | + label = _('Add new student') |
681 | + |
682 | + |
683 | +class AdministratorAddView(PersonAddViewBase): |
684 | + |
685 | + group_id = 'administrators' |
686 | + base_id = 'base-data' |
687 | + base_legend = _('Administrator identification') |
688 | + demo_id = 'demo-data' |
689 | + demo_legend = _('Administrator demographics') |
690 | + label = _('Add new administrator') |
691 | + |
692 | |
693 | === modified file 'src/schooltool/basicperson/browser/templates/demographics-view.pt' |
694 | --- src/schooltool/basicperson/browser/templates/demographics-view.pt 2009-10-25 01:13:33 +0000 |
695 | +++ src/schooltool/basicperson/browser/templates/demographics-view.pt 2011-01-17 05:56:49 +0000 |
696 | @@ -1,10 +1,10 @@ |
697 | <tal:dummy condition="view/update"> </tal:dummy> |
698 | <html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
699 | <head> |
700 | - <title metal:fill-slot="title" i18n:translate="">Demographics Container</title> |
701 | + <title metal:fill-slot="title" tal:content="view/title" /> |
702 | </head> |
703 | <body> |
704 | -<h1 metal:fill-slot="content-header" i18n:translate="">Demographics Container</h1> |
705 | +<h1 metal:fill-slot="content-header" tal:content="view/title" /> |
706 | |
707 | <metal:block metal:fill-slot="body"> |
708 | |
709 | |
710 | === added file 'src/schooltool/basicperson/browser/templates/person_form.pt' |
711 | --- src/schooltool/basicperson/browser/templates/person_form.pt 1970-01-01 00:00:00 +0000 |
712 | +++ src/schooltool/basicperson/browser/templates/person_form.pt 2011-01-17 05:56:49 +0000 |
713 | @@ -0,0 +1,138 @@ |
714 | +<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
715 | + <body> |
716 | + <metal:nothing metal:fill-slot="content-header" /> |
717 | + <metal:block metal:fill-slot="body"> |
718 | + <form action="." method="post" enctype="multipart/form-data" class="standalone" |
719 | + metal:define-macro="form" |
720 | + tal:attributes="method view/method; |
721 | + enctype view/enctype; |
722 | + acceptCharset view/acceptCharset; |
723 | + accept view/accept; |
724 | + action view/action; |
725 | + name view/name; |
726 | + id view/id"> |
727 | + <metal:subform define-macro="subform"> |
728 | + <div class="viewspace" metal:define-slot="viewspace"> |
729 | + <metal:label define-slot="label"> |
730 | + <h3 metal:define-macro="form-label" |
731 | + tal:condition="view/label|nothing" |
732 | + tal:content="view/label"> |
733 | + Form Label |
734 | + </h3> |
735 | + </metal:label> |
736 | + <metal:info define-slot="info"> |
737 | + <div class="required-info" |
738 | + metal:define-macro="required-info"> |
739 | + <!--<span class="required">*</span> |
740 | + – required --> |
741 | + </div> |
742 | + </metal:info> |
743 | + <metal:header define-slot="header"> |
744 | + <div class="status" |
745 | + tal:condition="view/status" |
746 | + metal:define-macro="form-header"> |
747 | + <div class="summary" |
748 | + i18n:translate="" |
749 | + tal:content="view/status"> |
750 | + Form status summary |
751 | + </div> |
752 | + <ul class="errors" |
753 | + tal:define="errors view/errors|view/widgets/errors" |
754 | + tal:condition="errors" |
755 | + metal:define-macro="form-errors"> |
756 | + <li tal:repeat="error errors"> |
757 | + <tal:block condition="error/widget"> |
758 | + <span tal:replace="error/widget/label" />: |
759 | + </tal:block> |
760 | + <span tal:replace="structure error/render">Error Type</span> |
761 | + </li> |
762 | + </ul> |
763 | + </div> |
764 | + </metal:header> |
765 | + <div metal:define-slot="extra-info" tal:replace="nothing"> |
766 | + </div> |
767 | + <div metal:define-slot="main"> |
768 | + <metal:widget-rows define-macro="widget-rows"> |
769 | + <tal:loop repeat="fieldset view/fieldsets"> |
770 | + <fieldset tal:attributes="id fieldset/id"> |
771 | + <legend tal:content="fieldset/legend">legend</legend> |
772 | + <tal:loop repeat="row fieldset/rows"> |
773 | + <tal:block repeat="widget_id row"> |
774 | + <metal:block tal:define="widget python:view.widgets[widget_id]"> |
775 | + <div id="" class="row" |
776 | + tal:attributes="id string:${widget/id}-row" |
777 | + tal:condition="python:widget.mode != 'hidden'"> |
778 | + <metal:widget-row define-macro="widget-row"> |
779 | + <div class="label"> |
780 | + <label tal:attributes="for widget/id"> |
781 | + <span i18n:translate="" |
782 | + tal:content="widget/label">label</span> |
783 | + <span class="required" |
784 | + tal:condition="widget/required">*</span> |
785 | + </label> |
786 | + </div> |
787 | + <p class="hint" tal:content="widget/field/description">Description of this field.</p> |
788 | + <div class="widget" tal:content="structure widget/render"> |
789 | + <input type="text" size="24" value="" /> |
790 | + </div> |
791 | + <div class="error" |
792 | + tal:condition="widget/error"> |
793 | + <span tal:replace="structure widget/error/render">error</span> |
794 | + </div> |
795 | + </metal:widget-row> |
796 | + </div> |
797 | + <input type="hidden" value="" |
798 | + tal:condition="python:widget.mode == 'hidden'" |
799 | + tal:replace="structure widget/render" /> |
800 | + </metal:block> |
801 | + </tal:block> |
802 | + <br class="clearboth" /> |
803 | + </tal:loop> |
804 | + <div metal:define-slot="extra-widgets" tal:replace="nothing"> |
805 | + </div> |
806 | + </fieldset> |
807 | + </tal:loop> |
808 | + </metal:widget-rows> |
809 | + <metal:groups define-macro="groups"> |
810 | + <fieldset tal:condition="view/groups|nothing" |
811 | + tal:repeat="view view/groups"> |
812 | + <legend tal:condition="view/label" |
813 | + tal:content="view/label">Label</legend> |
814 | + <metal:group-header define-slot="group-header"> |
815 | + <div class="status" |
816 | + tal:condition="view/widgets/errors"> |
817 | + <div metal:use-macro="template/macros/errors" /> |
818 | + </div> |
819 | + </metal:group-header> |
820 | + <metal:group-rows define-slot="group-rows"> |
821 | + <div metal:use-macro="template/macros/widget-rows" /> |
822 | + </metal:group-rows> |
823 | + </fieldset> |
824 | + </metal:groups> |
825 | + </div> |
826 | + <metal:above-buttons define-slot="above-buttons"> |
827 | + <tal:block |
828 | + repeat="form view/subforms" |
829 | + content="structure form/render" |
830 | + condition="view/subforms|nothing" /> |
831 | + </metal:above-buttons> |
832 | + </div> |
833 | + <metal:buttons define-slot="buttons"> |
834 | + <div metal:define-macro="form-buttons"> |
835 | + <div class="buttons controls" |
836 | + metal:define-slot="bottom-buttons"> |
837 | + <tal:block condition="view/actions|nothing"> |
838 | + <input tal:repeat="action view/actions/values" |
839 | + tal:replace="structure action/render" |
840 | + /> |
841 | + </tal:block> |
842 | + </div> |
843 | + </div> |
844 | + </metal:buttons> |
845 | + <metal:bottom define-slot="bottom"> |
846 | + </metal:bottom> |
847 | + </metal:subform> |
848 | + </form> |
849 | + </metal:block> |
850 | + </body> |
851 | +</html> |
852 | |
853 | === modified file 'src/schooltool/basicperson/configure.zcml' |
854 | --- src/schooltool/basicperson/configure.zcml 2010-08-08 12:06:54 +0000 |
855 | +++ src/schooltool/basicperson/configure.zcml 2011-01-17 05:56:49 +0000 |
856 | @@ -52,6 +52,13 @@ |
857 | set_schema="schooltool.basicperson.interfaces.IFieldDescription" /> |
858 | </class> |
859 | |
860 | + <class class=".demographics.BoolFieldDescription"> |
861 | + <allow interface="zope.location.interfaces.ILocation" /> |
862 | + <allow interface="schooltool.basicperson.interfaces.IFieldDescription" /> |
863 | + <require permission="schooltool.edit" |
864 | + set_schema="schooltool.basicperson.interfaces.IFieldDescription" /> |
865 | + </class> |
866 | + |
867 | <class class=".demographics.EnumFieldDescription"> |
868 | <allow interface="zope.location.interfaces.ILocation" /> |
869 | <allow interface="schooltool.basicperson.interfaces.IEnumFieldDescription" /> |
870 | @@ -74,6 +81,14 @@ |
871 | provides="zope.schema.interfaces.IVocabularyFactory" |
872 | name="schooltool.basicperson.advisor_vocabulary" /> |
873 | |
874 | + <utility |
875 | + factory=".vocabularies.LimitKeyVocabFactory" |
876 | + provides="zope.schema.interfaces.IVocabularyFactory" |
877 | + name="schooltool.basicperson.limit_keys_vocabulary" /> |
878 | + |
879 | + <adapter factory=".vocabularies.getLimitKeyVocabularyForFieldDescription" /> |
880 | + <adapter factory=".vocabularies.getLimitKeyVocabularyForPersonFields" /> |
881 | + |
882 | <include package="schooltool.basicperson.browser" /> |
883 | |
884 | <adapter factory=".demographics.getPersonDemographics" /> |
885 | |
886 | === modified file 'src/schooltool/basicperson/demographics.py' |
887 | --- src/schooltool/basicperson/demographics.py 2010-08-30 09:15:43 +0000 |
888 | +++ src/schooltool/basicperson/demographics.py 2011-01-17 05:56:49 +0000 |
889 | @@ -24,7 +24,7 @@ |
890 | |
891 | from zope.schema._field import Choice |
892 | from zope.schema._field import Date |
893 | -from zope.schema import TextLine |
894 | +from zope.schema import TextLine, Bool |
895 | from zope.location.location import Location |
896 | from zope.location.location import locate |
897 | from zope.interface import Interface |
898 | @@ -113,6 +113,25 @@ |
899 | class DemographicsFields(OrderedContainer): |
900 | implements(IDemographicsFields) |
901 | |
902 | + def filter_key(self, key): |
903 | + """Return the subset of fields whose limited_keys list is either |
904 | + empty, or it contains the key passed.""" |
905 | + result = [] |
906 | + for field in self.values(): |
907 | + if not field.limit_keys or key in field.limit_keys: |
908 | + result.append(field) |
909 | + return result |
910 | + |
911 | + def filter_keys(self, keys): |
912 | + """Return the subset of fields whose limited_keys list is either |
913 | + empty, or it contains one of the keys passed.""" |
914 | + result = [] |
915 | + for field in self.values(): |
916 | + limit_keys = field.limit_keys |
917 | + if not limit_keys or [key for key in keys if key in limit_keys]: |
918 | + result.append(field) |
919 | + return result |
920 | + |
921 | |
922 | def setUpDefaultDemographics(app): |
923 | dfs = DemographicsFields() |
924 | @@ -152,9 +171,11 @@ |
925 | |
926 | class FieldDescription(Persistent, Location): |
927 | implements(IFieldDescription) |
928 | + limit_keys = [] |
929 | |
930 | - def __init__(self, name, title, required=False): |
931 | - self.name, self.title,self.required = name, title, required |
932 | + def __init__(self, name, title, required=False, limit_keys=[]): |
933 | + self.name, self.title, self.required, self.limit_keys = (name, |
934 | + title, required, limit_keys) |
935 | |
936 | def setUpField(self, form_field): |
937 | form_field.required = self.required |
938 | @@ -202,3 +223,10 @@ |
939 | |
940 | def makeField(self): |
941 | return self.setUpField(TextLine(title=unicode(self.title))) |
942 | + |
943 | + |
944 | +class BoolFieldDescription(FieldDescription): |
945 | + |
946 | + def makeField(self): |
947 | + return self.setUpField(Bool(title=unicode(self.title))) |
948 | + |
949 | |
950 | === modified file 'src/schooltool/basicperson/interfaces.py' |
951 | --- src/schooltool/basicperson/interfaces.py 2010-08-08 12:06:54 +0000 |
952 | +++ src/schooltool/basicperson/interfaces.py 2011-01-17 05:56:49 +0000 |
953 | @@ -21,7 +21,7 @@ |
954 | """ |
955 | from zope.container.interfaces import IContainer |
956 | from zope.container.interfaces import IOrderedContainer |
957 | -from zope.schema import Date, Choice, TextLine, Bool |
958 | +from zope.schema import Date, Choice, TextLine, Bool, List, Set |
959 | from zope.configuration.fields import PythonIdentifier |
960 | from zope.interface import Interface, Attribute |
961 | |
962 | @@ -113,6 +113,18 @@ |
963 | class IDemographicsFields(IOrderedContainer): |
964 | """Demographics field storage.""" |
965 | |
966 | + def filter_key(key): |
967 | + """Return the subset of fields whose limited_keys list is either |
968 | + empty, or it contains the key passed""" |
969 | + |
970 | + def filter_keys(keys): |
971 | + """Return the subset of fields whose limited_keys list is either |
972 | + empty, or it contains one of the keys passed""" |
973 | + |
974 | + |
975 | +class FilterKeyList(List): |
976 | + """Marker field to pin widgets on.""" |
977 | + |
978 | |
979 | class IFieldDescription(Interface): |
980 | """Demographics field.""" |
981 | @@ -129,9 +141,30 @@ |
982 | title = _(u"Required"), |
983 | description = _(u"Whether this Field is required or not")) |
984 | |
985 | + limit_keys = FilterKeyList( |
986 | + title = _(u"Limit keys"), |
987 | + description = _(u"An optional list of limit keys for this field"), |
988 | + value_type=Choice( |
989 | + title=_(u"Limit key"), |
990 | + source="schooltool.basicperson.limit_keys_vocabulary", |
991 | + required=True, |
992 | + ), |
993 | + required=False) |
994 | + |
995 | + |
996 | +class EnumValueList(List): |
997 | + """Marker field to pin custom validation on.""" |
998 | + |
999 | |
1000 | class IEnumFieldDescription(IFieldDescription): |
1001 | """Enumeration demographics field.""" |
1002 | |
1003 | - items = List( |
1004 | + items = EnumValueList( |
1005 | title = _('List of values')) |
1006 | + |
1007 | + |
1008 | +class IFieldFilterVocabulary(IVocabularyTokenized): |
1009 | + """Marker interface for vocabularies that give keys that are used |
1010 | + to filder demographics fields for the context. |
1011 | + """ |
1012 | + |
1013 | |
1014 | === modified file 'src/schooltool/basicperson/tests/test_demographics.py' |
1015 | --- src/schooltool/basicperson/tests/test_demographics.py 2010-01-10 17:31:54 +0000 |
1016 | +++ src/schooltool/basicperson/tests/test_demographics.py 2011-01-17 05:56:49 +0000 |
1017 | @@ -31,6 +31,7 @@ |
1018 | from schooltool.basicperson.demographics import setUpDefaultDemographics |
1019 | from schooltool.basicperson.demographics import IDemographicsForm |
1020 | from schooltool.basicperson.demographics import TextFieldDescription |
1021 | +from schooltool.basicperson.demographics import BoolFieldDescription |
1022 | from schooltool.basicperson.demographics import PersonDemographicsData |
1023 | from schooltool.schoolyear.testing import (setUp, tearDown, |
1024 | provideStubUtility, |
1025 | @@ -140,6 +141,65 @@ |
1026 | """ |
1027 | |
1028 | |
1029 | +def doctest_DemographicsFields(): |
1030 | + """Tests for DemographicsFields |
1031 | + |
1032 | + DemographicsFields is a class that contains demo fields that themselves |
1033 | + may or may not be limited to a group or groups. |
1034 | + |
1035 | + >>> dfs = IDemographicsFields(ISchoolToolApplication(None)) |
1036 | + >>> dfs['email'] = TextFieldDescription("email", "Email") |
1037 | + >>> dfs['supervisor'] = TextFieldDescription("supervisor", "Supervisor", |
1038 | + ... limit_keys=['teachers']) |
1039 | + >>> dfs['advisor'] = TextFieldDescription("advisor", "Advisor", |
1040 | + ... limit_keys=['students']) |
1041 | + >>> dfs['phone'] = TextFieldDescription("phone", "Phone", |
1042 | + ... limit_keys=['teachers', 'students']) |
1043 | + |
1044 | + |
1045 | + When we pass the filter_key method a key that does not belong |
1046 | + to any of the limit_keys lists, then it will only return those |
1047 | + fields that have empty limit_keys lists. |
1048 | + |
1049 | + >>> [f.__name__ for f in dfs.filter_key('anything')] |
1050 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1051 | + u'email'] |
1052 | + |
1053 | + When we pass 'teachers', it picks up the additional fields that are for |
1054 | + teachers. |
1055 | + |
1056 | + >>> [f.__name__ for f in dfs.filter_key('teachers')] |
1057 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1058 | + u'email', u'supervisor', u'phone'] |
1059 | + |
1060 | + When we pass 'students', it picks up the additional fields that are for |
1061 | + students. |
1062 | + |
1063 | + >>> [f.__name__ for f in dfs.filter_key('students')] |
1064 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1065 | + u'email', u'advisor', u'phone'] |
1066 | + |
1067 | + We also have a filter_keys method to return fields whose keys are in the |
1068 | + list passed. |
1069 | + |
1070 | + >>> [f.__name__ for f in dfs.filter_keys([])] |
1071 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1072 | + u'email'] |
1073 | + |
1074 | + >>> [f.__name__ for f in dfs.filter_keys(['students'])] |
1075 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1076 | + u'email', u'advisor', u'phone'] |
1077 | + |
1078 | + >>> [f.__name__ for f in dfs.filter_keys(['teachers'])] |
1079 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1080 | + u'email', u'supervisor', u'phone'] |
1081 | + |
1082 | + >>> [f.__name__ for f in dfs.filter_keys(['students', 'teachers'])] |
1083 | + [u'ID', u'ethnicity', u'language', u'placeofbirth', u'citizenship', |
1084 | + u'email', u'supervisor', u'advisor', u'phone'] |
1085 | + """ |
1086 | + |
1087 | + |
1088 | def doctest_EnumFieldDescription(): |
1089 | """Tests for EnumFieldDescription |
1090 | |
1091 | @@ -194,6 +254,33 @@ |
1092 | """ |
1093 | |
1094 | |
1095 | +def doctest_BoolFieldDescription(): |
1096 | + """Tests for BoolFieldDescription |
1097 | + |
1098 | + Boolean field description is a class that defines a boolean field shown |
1099 | + in person add/edit form. |
1100 | + |
1101 | + >>> fd = BoolFieldDescription("ID", "ID") |
1102 | + >>> fields = fd.makeField() |
1103 | + >>> len(fields) |
1104 | + 1 |
1105 | + |
1106 | + >>> field = fields['ID'] |
1107 | + >>> field |
1108 | + <Field 'ID'> |
1109 | + |
1110 | + >>> field.interface |
1111 | + <InterfaceClass schooltool.basicperson.demographics.IDemographicsForm> |
1112 | + |
1113 | + >>> field.field |
1114 | + <zope.schema._bootstrapfields.Bool object at ...> |
1115 | + |
1116 | + >>> field.__name__ |
1117 | + 'ID' |
1118 | + |
1119 | + """ |
1120 | + |
1121 | + |
1122 | def doctest_DateFieldDescription(): |
1123 | """Tests for DateFieldDescription |
1124 | |
1125 | |
1126 | === modified file 'src/schooltool/basicperson/vocabularies.py' |
1127 | --- src/schooltool/basicperson/vocabularies.py 2010-08-08 12:06:54 +0000 |
1128 | +++ src/schooltool/basicperson/vocabularies.py 2011-01-17 05:56:49 +0000 |
1129 | @@ -20,9 +20,12 @@ |
1130 | Sources and vocabularies for form fields. |
1131 | """ |
1132 | from zope.interface import implements |
1133 | +from zope.interface import implementer |
1134 | +from zope.component import adapter |
1135 | from zope.schema.interfaces import ITitledTokenizedTerm |
1136 | |
1137 | from schooltool.app.interfaces import ISchoolToolApplication |
1138 | +from schooltool.app.utils import vocabulary |
1139 | from schooltool.person.interfaces import IPerson |
1140 | from schooltool.group.interfaces import IGroupContainer |
1141 | from schooltool.group.interfaces import IGroup |
1142 | @@ -31,6 +34,11 @@ |
1143 | from schooltool.basicperson.browser.person import GroupTerm |
1144 | from schooltool.basicperson.interfaces import IGroupVocabulary |
1145 | from schooltool.basicperson.interfaces import IBasicPersonVocabulary |
1146 | +from schooltool.basicperson.interfaces import IDemographicsFields |
1147 | +from schooltool.basicperson.interfaces import IFieldDescription |
1148 | +from schooltool.basicperson.interfaces import IFieldFilterVocabulary |
1149 | + |
1150 | +from schooltool.common import SchoolToolMessage as _ |
1151 | |
1152 | |
1153 | class Term(object): |
1154 | @@ -115,3 +123,25 @@ |
1155 | |
1156 | def advisorVocabularyFactory(): |
1157 | return AdvisorVocabulary |
1158 | + |
1159 | + |
1160 | +def LimitKeyVocabFactory(): |
1161 | + return IFieldFilterVocabulary |
1162 | + |
1163 | + |
1164 | +@adapter(IFieldDescription) |
1165 | +@implementer(IFieldFilterVocabulary) |
1166 | +def getLimitKeyVocabularyForFieldDescription(field_description): |
1167 | + field_container = field_description.__parent__ |
1168 | + return IFieldFilterVocabulary(field_container) |
1169 | + |
1170 | + |
1171 | +@adapter(IDemographicsFields) |
1172 | +@implementer(IFieldFilterVocabulary) |
1173 | +def getLimitKeyVocabularyForPersonFields(person_field_description_container): |
1174 | + return vocabulary([ |
1175 | + ('students', _('Students')), |
1176 | + ('teachers', _('Teachers')), |
1177 | + ('administrators', _('Administrators')), |
1178 | + ]) |
1179 | + |
1180 | |
1181 | === modified file 'src/schooltool/resource/browser/configure.zcml' |
1182 | --- src/schooltool/resource/browser/configure.zcml 2009-11-23 14:48:29 +0000 |
1183 | +++ src/schooltool/resource/browser/configure.zcml 2011-01-17 05:56:49 +0000 |
1184 | @@ -1,8 +1,31 @@ |
1185 | <?xml version="1.0"?> |
1186 | <configure xmlns="http://namespaces.zope.org/browser" |
1187 | + xmlns:z3c="http://namespaces.zope.org/z3c" |
1188 | xmlns:zope="http://namespaces.zope.org/zope" |
1189 | i18n_domain="schooltool"> |
1190 | |
1191 | + <zope:class class=".resource.ResourceSubTypeWidget"> |
1192 | + <require |
1193 | + permission="zope.Public" |
1194 | + interface="z3c.form.interfaces.ITextWidget" |
1195 | + /> |
1196 | + </zope:class> |
1197 | + |
1198 | + <zope:adapter |
1199 | + factory=".resource.ResourceSubTypeFieldWidget" |
1200 | + provides="z3c.form.interfaces.IFieldWidget" |
1201 | + for="schooltool.resource.interfaces.ResourceSubType |
1202 | + z3c.form.interfaces.IFormLayer" |
1203 | + /> |
1204 | + |
1205 | + <z3c:widgetTemplate |
1206 | + mode="input" |
1207 | + widget=".resource.IResourceSubTypeWidget" |
1208 | + layer="z3c.form.interfaces.IFormLayer" |
1209 | + template="subtype_widget.pt" |
1210 | + /> |
1211 | + |
1212 | + |
1213 | <!-- Resource Traverser --> |
1214 | <zope:view |
1215 | for="..interfaces.IBaseResource" |
1216 | @@ -58,6 +81,29 @@ |
1217 | /> |
1218 | |
1219 | <configure package="schooltool.skin"> |
1220 | + <navigationViewlet |
1221 | + name="resources" |
1222 | + for="*" |
1223 | + manager="schooltool.app.browser.interfaces.IManageMenuViewletManager" |
1224 | + template="templates/navigationViewlet.pt" |
1225 | + class="schooltool.skin.TopLevelContainerNavigationViewlet" |
1226 | + permission="schooltool.view" |
1227 | + link="resources" |
1228 | + title="Resources" |
1229 | + order="60" |
1230 | + /> |
1231 | + |
1232 | + <navigationViewlet |
1233 | + name="resource_demographics" |
1234 | + for="*" |
1235 | + manager="schooltool.app.browser.interfaces.IManageMenuViewletManager" |
1236 | + template="templates/navigationViewlet.pt" |
1237 | + class="schooltool.skin.skin.NavigationViewlet" |
1238 | + permission="schooltool.edit" |
1239 | + link="resource_demographics" |
1240 | + title="Resource Demographics" |
1241 | + order="65" |
1242 | + /> |
1243 | |
1244 | <page |
1245 | name="index.html" |
1246 | @@ -67,147 +113,14 @@ |
1247 | menu="zmi_views" |
1248 | title="View" |
1249 | /> |
1250 | - |
1251 | - <addform |
1252 | - label="Add a Resource" |
1253 | - name="addSchoolToolResource.html" |
1254 | - schema="schooltool.resource.interfaces.IResource" |
1255 | - fields="title description notes" |
1256 | - set_before_add="title description notes" |
1257 | - content_factory="schooltool.resource.resource.Resource" |
1258 | - template="templates/simple_add.pt" |
1259 | - class="schooltool.app.browser.app.BaseAddView" |
1260 | - permission="schooltool.edit"> |
1261 | - <widget field="description" height="5" /> |
1262 | - </addform> |
1263 | - |
1264 | - <editform |
1265 | - for="schooltool.resource.interfaces.IResource" |
1266 | - name="edit.html" |
1267 | - label="Edit Resource Information" |
1268 | - schema="schooltool.resource.interfaces.IResource" |
1269 | - fields="title description notes" |
1270 | - permission="schooltool.edit" |
1271 | - template="templates/simple_edit.pt" |
1272 | - class="schooltool.resource.browser.resource.ResourceEditView" |
1273 | - menu="schooltool_actions" |
1274 | - title="Edit Info"> |
1275 | - <widget field="description" height="5" /> |
1276 | - </editform> |
1277 | - |
1278 | - <!-- Location forms --> |
1279 | - <addform |
1280 | - label="Add a Location" |
1281 | - name="addSchoolToolLocation.html" |
1282 | - schema="schooltool.resource.interfaces.ILocation" |
1283 | - fields="title type description capacity notes" |
1284 | - set_before_add="title type description capacity notes" |
1285 | - content_factory="schooltool.resource.resource.Location" |
1286 | - template="templates/simple_add.pt" |
1287 | - class="schooltool.app.browser.app.BaseAddView" |
1288 | - permission="schooltool.edit"> |
1289 | - <widget field="description" height="5" /> |
1290 | - <widget field="type" |
1291 | - class="schooltool.resource.browser.resource.ResourceSubTypeWidget" |
1292 | - utility="location"/> |
1293 | - </addform> |
1294 | - |
1295 | - <editform |
1296 | - for="schooltool.resource.interfaces.ILocation" |
1297 | - name="edit.html" |
1298 | - label="Edit Location Information" |
1299 | - schema="schooltool.resource.interfaces.ILocation" |
1300 | - fields="title type description capacity notes" |
1301 | - permission="schooltool.edit" |
1302 | - template="templates/simple_edit.pt" |
1303 | - class="schooltool.resource.browser.resource.ResourceEditView" |
1304 | - menu="schooltool_actions" |
1305 | - title="Edit Info"> |
1306 | - <widget field="description" height="5" /> |
1307 | - <widget field="type" |
1308 | - class="schooltool.resource.browser.resource.ResourceSubTypeWidget" |
1309 | - utility="location"/> |
1310 | - </editform> |
1311 | - |
1312 | - <!-- Equipment forms --> |
1313 | - <addform |
1314 | - label="Add an Equipment" |
1315 | - name="addSchoolToolEquipment.html" |
1316 | - schema="schooltool.resource.interfaces.IEquipment" |
1317 | - fields="title description type manufacturer model serialNumber |
1318 | - purchaseDate notes" |
1319 | - set_before_add="title description notes type manufacturer model |
1320 | - serialNumber purchaseDate" |
1321 | - content_factory="schooltool.resource.resource.Equipment" |
1322 | - template="templates/simple_add.pt" |
1323 | - class="schooltool.app.browser.app.BaseAddView" |
1324 | - permission="schooltool.edit"> |
1325 | - <widget field="description" height="5" /> |
1326 | - <widget field="type" |
1327 | - class="schooltool.resource.browser.resource.ResourceSubTypeWidget" |
1328 | - utility="equipment"/> |
1329 | - </addform> |
1330 | - |
1331 | - <editform |
1332 | - for="schooltool.resource.interfaces.IEquipment" |
1333 | - name="edit.html" |
1334 | - label="Edit Equipment Information" |
1335 | - schema="schooltool.resource.interfaces.IEquipment" |
1336 | - fields="title description type manufacturer model serialNumber |
1337 | - purchaseDate notes" |
1338 | - permission="schooltool.edit" |
1339 | - template="templates/simple_edit.pt" |
1340 | - class="schooltool.resource.browser.resource.ResourceEditView" |
1341 | - menu="schooltool_actions" |
1342 | - title="Edit Info"> |
1343 | - <widget field="description" height="5" /> |
1344 | - <widget field="type" |
1345 | - class="schooltool.resource.browser.resource.ResourceSubTypeWidget" |
1346 | - utility="equipment"/> |
1347 | - </editform> |
1348 | - |
1349 | - <navigationViewlet |
1350 | - name="resources" |
1351 | - for="*" |
1352 | - manager="schooltool.app.browser.interfaces.IManageMenuViewletManager" |
1353 | - template="templates/navigationViewlet.pt" |
1354 | - class="schooltool.skin.TopLevelContainerNavigationViewlet" |
1355 | - permission="schooltool.view" |
1356 | - link="resources" |
1357 | - title="Resources" |
1358 | - order="60" |
1359 | - /> |
1360 | - |
1361 | </configure> |
1362 | |
1363 | - |
1364 | <containerViews |
1365 | for="..interfaces.IResourceContainer" |
1366 | contents="schooltool.view" |
1367 | add="schooltool.edit" |
1368 | /> |
1369 | |
1370 | - <menuItem |
1371 | - menu="schooltool_actions" |
1372 | - title="New Resource" |
1373 | - for="..interfaces.IResourceContainer" |
1374 | - action="+/addSchoolToolResource.html" |
1375 | - /> |
1376 | - |
1377 | - <menuItem |
1378 | - menu="schooltool_actions" |
1379 | - title="New Location" |
1380 | - for="..interfaces.IResourceContainer" |
1381 | - action="+/addSchoolToolLocation.html" |
1382 | - /> |
1383 | - |
1384 | - <menuItem |
1385 | - menu="schooltool_actions" |
1386 | - title="New Equipment" |
1387 | - for="..interfaces.IResourceContainer" |
1388 | - action="+/addSchoolToolEquipment.html" |
1389 | - /> |
1390 | - |
1391 | <page |
1392 | name="index.html" |
1393 | for="..interfaces.IResource" |
1394 | @@ -302,4 +215,117 @@ |
1395 | order="5" |
1396 | /> |
1397 | |
1398 | + <zope:adapterTraverserPlugin |
1399 | + for="schooltool.app.interfaces.ISchoolToolApplication" |
1400 | + layer="zope.publisher.interfaces.http.IHTTPRequest" |
1401 | + name="resource_demographics" |
1402 | + adapter="schooltool.resource.interfaces.IResourceDemographicsFields" |
1403 | + permission="zope.Public" |
1404 | + /> |
1405 | + |
1406 | + <zope:configure package="schooltool.basicperson.browser"> |
1407 | + <page |
1408 | + name="index.html" |
1409 | + for="schooltool.resource.interfaces.IResourceDemographicsFields" |
1410 | + class="schooltool.resource.browser.resource.ResourceDemographicsView" |
1411 | + template="templates/demographics-view.pt" |
1412 | + permission="schooltool.edit" |
1413 | + /> |
1414 | + </zope:configure> |
1415 | + |
1416 | + <zope:adapter |
1417 | + factory=".resource.ResourceDemographicsFieldsAbsoluteURLAdapter" |
1418 | + provides="zope.traversing.browser.interfaces.IAbsoluteURL" /> |
1419 | + |
1420 | + <zope:adapter |
1421 | + factory=".resource.ResourceDemographicsFieldsAbsoluteURLAdapter" |
1422 | + provides="zope.interface.Interface" |
1423 | + name="absolute_url"/> |
1424 | + |
1425 | + <menuItem |
1426 | + menu="schooltool_actions" |
1427 | + title="Edit Info" |
1428 | + for="schooltool.resource.interfaces.IResource" |
1429 | + action="edit.html" |
1430 | + permission="schooltool.edit" |
1431 | + /> |
1432 | + <page |
1433 | + for="schooltool.resource.interfaces.IResource" |
1434 | + name="edit.html" |
1435 | + class=".resource.ResourceEditView" |
1436 | + permission="schooltool.edit" |
1437 | + layer="schooltool.skin.ISchoolToolLayer" |
1438 | + /> |
1439 | + <menuItem |
1440 | + menu="schooltool_actions" |
1441 | + title="Edit Info" |
1442 | + for="schooltool.resource.interfaces.ILocation" |
1443 | + action="edit.html" |
1444 | + permission="schooltool.edit" |
1445 | + /> |
1446 | + <page |
1447 | + for="schooltool.resource.interfaces.ILocation" |
1448 | + name="edit.html" |
1449 | + class=".resource.LocationEditView" |
1450 | + permission="schooltool.edit" |
1451 | + layer="schooltool.skin.ISchoolToolLayer" |
1452 | + /> |
1453 | + <menuItem |
1454 | + menu="schooltool_actions" |
1455 | + title="Edit Info" |
1456 | + for="schooltool.resource.interfaces.IEquipment" |
1457 | + action="edit.html" |
1458 | + permission="schooltool.edit" |
1459 | + /> |
1460 | + <page |
1461 | + for="schooltool.resource.interfaces.IEquipment" |
1462 | + name="edit.html" |
1463 | + class=".resource.EquipmentEditView" |
1464 | + permission="schooltool.edit" |
1465 | + layer="schooltool.skin.ISchoolToolLayer" |
1466 | + /> |
1467 | + |
1468 | + <menuItem |
1469 | + menu="schooltool_actions" |
1470 | + title="New Resource" |
1471 | + for="schooltool.resource.interfaces.IResourceContainer" |
1472 | + action="addResource.html" |
1473 | + permission="schooltool.edit" |
1474 | + /> |
1475 | + <page |
1476 | + name="addResource.html" |
1477 | + for="schooltool.resource.interfaces.IResourceContainer" |
1478 | + class=".resource.ResourceAddView" |
1479 | + permission="schooltool.edit" |
1480 | + layer="schooltool.skin.ISchoolToolLayer" |
1481 | + /> |
1482 | + <menuItem |
1483 | + menu="schooltool_actions" |
1484 | + title="New Location" |
1485 | + for="schooltool.resource.interfaces.IResourceContainer" |
1486 | + action="addLocation.html" |
1487 | + permission="schooltool.edit" |
1488 | + /> |
1489 | + <page |
1490 | + name="addLocation.html" |
1491 | + for="schooltool.resource.interfaces.IResourceContainer" |
1492 | + class=".resource.LocationAddView" |
1493 | + permission="schooltool.edit" |
1494 | + layer="schooltool.skin.ISchoolToolLayer" |
1495 | + /> |
1496 | + <menuItem |
1497 | + menu="schooltool_actions" |
1498 | + title="New Equipment" |
1499 | + for="schooltool.resource.interfaces.IResourceContainer" |
1500 | + action="addEquipment.html" |
1501 | + permission="schooltool.edit" |
1502 | + /> |
1503 | + <page |
1504 | + name="addEquipment.html" |
1505 | + for="schooltool.resource.interfaces.IResourceContainer" |
1506 | + class=".resource.EquipmentAddView" |
1507 | + permission="schooltool.edit" |
1508 | + layer="schooltool.skin.ISchoolToolLayer" |
1509 | + /> |
1510 | + |
1511 | </configure> |
1512 | |
1513 | === added file 'src/schooltool/resource/browser/demographics.txt' |
1514 | --- src/schooltool/resource/browser/demographics.txt 1970-01-01 00:00:00 +0000 |
1515 | +++ src/schooltool/resource/browser/demographics.txt 2011-01-17 05:56:49 +0000 |
1516 | @@ -0,0 +1,50 @@ |
1517 | +Resource demographics |
1518 | +===================== |
1519 | + |
1520 | +A manager logs in. |
1521 | + |
1522 | + >>> manager = Browser('manager', 'schooltool') |
1523 | + |
1524 | +There's a link in the manage tab that brings up the Resource Demographics |
1525 | +container view. |
1526 | + |
1527 | + >>> manager.getLink('Manage').click() |
1528 | + >>> manager.getLink('Resource Demographics').click() |
1529 | + >>> manager.printQuery("id('content-header')//h1") |
1530 | + <h1>Resource Demographics Container</h1> |
1531 | + |
1532 | +We'll create some demographics fields. |
1533 | + |
1534 | + >>> manager.getLink('New Text Field').click() |
1535 | + |
1536 | + >>> manager.printQuery("id('column-center')//input[@type='checkbox']") |
1537 | + <input ... value="resource" type="checkbox" /> |
1538 | + <input ... value="location" type="checkbox" /> |
1539 | + <input ... value="equipment" type="checkbox" /> |
1540 | + |
1541 | + >>> manager.getControl('Title').value = "Room Number" |
1542 | + >>> manager.getControl('ID').value = "room" |
1543 | + >>> manager.getControl(name='form.widgets.required:list').value = ['true'] |
1544 | + >>> manager.getControl(name='form.widgets.limit_keys:list').value = ['location'] |
1545 | + >>> manager.getControl('Add').click() |
1546 | + |
1547 | + >>> manager.getLink('Edit').click() |
1548 | + >>> print manager.getControl(name='form.widgets.limit_keys:list').value |
1549 | + ['location'] |
1550 | + |
1551 | + >>> manager.getLink('Manage').click() |
1552 | + >>> manager.getLink('Resource Demographics').click() |
1553 | + >>> manager.getLink('New Yes/No Field').click() |
1554 | + >>> manager.getControl('Title').value = "Certified" |
1555 | + >>> manager.getControl('ID').value = "certified" |
1556 | + >>> manager.getControl(name='form.widgets.required:list').value = ['true'] |
1557 | + >>> manager.getControl('Add').click() |
1558 | + |
1559 | +Now we see them in the container view. |
1560 | + |
1561 | + >>> manager.getLink('Manage').click() |
1562 | + >>> manager.getLink('Resource Demographics').click() |
1563 | + >>> manager.printQuery("id('column-center')//a") |
1564 | + <a href="http://localhost/resource_demographics/room">room</a> |
1565 | + <a href="http://localhost/resource_demographics/certified">certified</a> |
1566 | + |
1567 | |
1568 | === modified file 'src/schooltool/resource/browser/resource-booking.txt' |
1569 | --- src/schooltool/resource/browser/resource-booking.txt 2008-09-05 12:13:50 +0000 |
1570 | +++ src/schooltool/resource/browser/resource-booking.txt 2011-01-17 05:56:49 +0000 |
1571 | @@ -15,7 +15,7 @@ |
1572 | >>> manager.getLink('Resources').click() |
1573 | >>> manager.getLink('New Location').click() |
1574 | >>> manager.getControl('Title').value = 'mud' |
1575 | - >>> manager.getControl(name='field.type.newSubType').value = 'Computer Lab' |
1576 | + >>> manager.getControl(name='form.widgets.type.newSubType').value = 'Computer Lab' |
1577 | >>> manager.getControl('Add').click() |
1578 | >>> manager.getLink('Computer Lab').click() |
1579 | >>> 'mud' in manager.contents |
1580 | |
1581 | === modified file 'src/schooltool/resource/browser/resource.py' |
1582 | --- src/schooltool/resource/browser/resource.py 2010-08-30 16:09:34 +0000 |
1583 | +++ src/schooltool/resource/browser/resource.py 2011-01-17 05:56:49 +0000 |
1584 | @@ -22,22 +22,38 @@ |
1585 | $Id$ |
1586 | """ |
1587 | |
1588 | +import z3c.form |
1589 | +import z3c.form.browser.text |
1590 | +from z3c.form import form, field, button, subform, validator, widget |
1591 | +from z3c.form.interfaces import DISPLAY_MODE, HIDDEN_MODE, NO_VALUE, IActionHandler |
1592 | from zc.table import table |
1593 | + |
1594 | from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile |
1595 | +from zope.component import adapter, adapts |
1596 | from zope.component import getUtilitiesFor |
1597 | from zope.component import queryAdapter |
1598 | from zope.component import queryMultiAdapter |
1599 | from zope.component import queryUtility |
1600 | -from zope.formlib import form |
1601 | +from zope.component import getMultiAdapter |
1602 | +from zope.container.interfaces import INameChooser |
1603 | +from zope.event import notify |
1604 | +from zope.formlib import form as oldform |
1605 | +from zope.interface import implementer, implements, implementsOnly |
1606 | +from zope.lifecycleevent import ObjectCreatedEvent |
1607 | +from zope.publisher.browser import BrowserView |
1608 | +from zope.publisher.interfaces.browser import IBrowserRequest |
1609 | from zope.session.interfaces import ISession |
1610 | -from zope.app.form.browser import widget |
1611 | from zope.traversing.browser.absoluteurl import absoluteURL |
1612 | +from zope.traversing.browser.interfaces import IAbsoluteURL |
1613 | |
1614 | from schooltool.app.browser.app import BaseEditView |
1615 | +from schooltool.app.interfaces import ISchoolToolApplication |
1616 | +from schooltool.basicperson.browser.demographics import DemographicsView |
1617 | from schooltool.resource.interfaces import IBookingCalendar |
1618 | from schooltool.resource.interfaces import (IBaseResourceContained, |
1619 | IResourceContainer, IResourceTypeInformation, IResourceSubTypes, |
1620 | - IResource, IEquipment, ILocation) |
1621 | + IResource, IEquipment, ILocation, IResourceDemographicsFields) |
1622 | +from schooltool.resource.resource import Resource, Location, Equipment |
1623 | from schooltool.table.interfaces import IFilterWidget |
1624 | from schooltool.table.table import url_cell_formatter |
1625 | from schooltool.table.table import CheckboxColumn |
1626 | @@ -48,7 +64,7 @@ |
1627 | from schooltool.common import SchoolToolMessage as _ |
1628 | |
1629 | |
1630 | -class ResourceContainerView(form.FormBase): |
1631 | +class ResourceContainerView(oldform.FormBase): |
1632 | """A Resource Container view.""" |
1633 | |
1634 | __used_for__ = IResourceContainer |
1635 | @@ -56,20 +72,20 @@ |
1636 | index_title = _("Resource index") |
1637 | |
1638 | prefix = "resources" |
1639 | - form_fields = form.Fields() |
1640 | - searchActions = form.Actions( |
1641 | - form.Action('Search', success='handle_search_action'),) |
1642 | + form_fields = oldform.Fields() |
1643 | + searchActions = oldform.Actions( |
1644 | + oldform.Action('Search', success='handle_search_action'),) |
1645 | |
1646 | - actions = form.Actions( |
1647 | - form.Action('Delete', success='handle_delete_action'), |
1648 | - form.Action('Book', success='handle_book_action')) |
1649 | + actions = oldform.Actions( |
1650 | + oldform.Action('Delete', success='handle_delete_action'), |
1651 | + oldform.Action('Book', success='handle_book_action')) |
1652 | template = ViewPageTemplateFile("resourcecontainer.pt") |
1653 | delete_template = ViewPageTemplateFile('container_delete.pt') |
1654 | |
1655 | resourceType = None |
1656 | |
1657 | def __init__(self, context, request): |
1658 | - form.FormBase.__init__(self, context, request) |
1659 | + oldform.FormBase.__init__(self, context, request) |
1660 | self.resourceType = self.request.get('SEARCH_TYPE','|').split('|')[0] |
1661 | self.filter_widget = queryMultiAdapter((self.getResourceUtility(), |
1662 | self.request), IFilterWidget) |
1663 | @@ -185,12 +201,28 @@ |
1664 | """Resource Type Filter""" |
1665 | |
1666 | |
1667 | -class ResourceSubTypeWidget(widget.SimpleInputWidget): |
1668 | - |
1669 | - __call__ = ViewPageTemplateFile('subtype_widget.pt') |
1670 | +class IResourceSubTypeWidget(z3c.form.interfaces.ITextWidget): |
1671 | + pass |
1672 | + |
1673 | + |
1674 | +class ResourceSubTypeWidget(z3c.form.browser.text.TextWidget): |
1675 | + implementsOnly(IResourceSubTypeWidget) |
1676 | + |
1677 | + utility = None |
1678 | + |
1679 | + def __init__(self, request, utility=None): |
1680 | + super(ResourceSubTypeWidget, self).__init__(request) |
1681 | + self.utility = utility |
1682 | + |
1683 | + def freeTextValue(self): |
1684 | + if self.value in self.subTypes(): |
1685 | + return '' |
1686 | + return self.value |
1687 | |
1688 | def subTypes(self): |
1689 | - util = queryUtility(IResourceFactoryUtility, name=self.utility, default=None) |
1690 | + util = queryUtility(IResourceFactoryUtility, |
1691 | + name=self.utility, |
1692 | + default=None) |
1693 | if IResourceSubTypes.providedBy(util): |
1694 | subtypes = util |
1695 | else: |
1696 | @@ -202,42 +234,26 @@ |
1697 | def hasInput(self): |
1698 | return self.request.get(self.name,None) != '' or self.request.get(self.name+'.newSubType',None) |
1699 | |
1700 | - def getInputValue(self): |
1701 | - subType = self.request.get(self.name) |
1702 | - newSubType = self.request.get(self.name+'.newSubType') |
1703 | - return newSubType or subType |
1704 | - |
1705 | - |
1706 | -class ResourceView(form.DisplayFormBase): |
1707 | - """A Resource info view.""" |
1708 | - |
1709 | - __used_for__ = IResource |
1710 | - |
1711 | - form_fields = form.Fields(IResource) |
1712 | - |
1713 | - template = ViewPageTemplateFile("resource.pt") |
1714 | - |
1715 | - def __init__(self, context, request): |
1716 | - self.context = context |
1717 | - self.request = request |
1718 | - |
1719 | - |
1720 | -class LocationView(ResourceView): |
1721 | - """A location info view.""" |
1722 | - __used_for__ = ILocation |
1723 | - form_fields = form.Fields(ILocation) |
1724 | - |
1725 | - |
1726 | -class EquipmentView(ResourceView): |
1727 | - """A equipment info view.""" |
1728 | - __used_for__ = IEquipment |
1729 | - form_fields = form.Fields(IEquipment) |
1730 | - |
1731 | - |
1732 | -class ResourceEditView(BaseEditView): |
1733 | - """A view for editing resource info.""" |
1734 | - |
1735 | - __used_for__ = IBaseResourceContained |
1736 | + def extract(self, default=NO_VALUE): |
1737 | + subType = self.request.get(self.name, default) |
1738 | + newSubType = self.request.get(self.name+'.newSubType', default) |
1739 | + if subType and subType != default: |
1740 | + return subType |
1741 | + return newSubType or default |
1742 | + |
1743 | + |
1744 | +def ResourceSubTypeFieldWidget(field, request): |
1745 | + utility_name = 'resource' |
1746 | + if not field.interface is None: |
1747 | + # XXX: this is what we get for using named utilities |
1748 | + if issubclass(field.interface, IEquipment): |
1749 | + utility_name = 'equipment' |
1750 | + elif issubclass(field.interface, ILocation): |
1751 | + utility_name = 'location' |
1752 | + return widget.FieldWidget( |
1753 | + field, |
1754 | + ResourceSubTypeWidget(request, utility=utility_name) |
1755 | + ) |
1756 | |
1757 | |
1758 | class ResourceContainerFilterWidget(PersonFilterWidget): |
1759 | @@ -291,3 +307,275 @@ |
1760 | results = filter_widget.filter(results) |
1761 | |
1762 | return results |
1763 | + |
1764 | + |
1765 | +class ResourceDemographicsFieldsAbsoluteURLAdapter(BrowserView): |
1766 | + |
1767 | + adapts(IResourceDemographicsFields, IBrowserRequest) |
1768 | + implements(IAbsoluteURL) |
1769 | + |
1770 | + def __str__(self): |
1771 | + app = ISchoolToolApplication(None) |
1772 | + url = str(getMultiAdapter((app, self.request), name='absolute_url')) |
1773 | + return url + '/resource_demographics' |
1774 | + |
1775 | + __call__ = __str__ |
1776 | + |
1777 | + |
1778 | +class ResourceDemographicsView(DemographicsView): |
1779 | + |
1780 | + title = _('Resource Demographics Container') |
1781 | + |
1782 | + |
1783 | +########## Base class of all resource views (uses self.resource_type) ######### |
1784 | +class ResourceFieldGenerator(object): |
1785 | + |
1786 | + def makeRows(self, fields, cols=1): |
1787 | + rows = [] |
1788 | + while fields: |
1789 | + rows.append(fields[:cols]) |
1790 | + fields = fields[cols:] |
1791 | + return rows |
1792 | + |
1793 | + def makeFieldSet(self, fieldset_id, legend, fields, cols=1): |
1794 | + result = { |
1795 | + 'id': fieldset_id, |
1796 | + 'legend': legend, |
1797 | + } |
1798 | + result['rows'] = self.makeRows(fields, cols) |
1799 | + return result |
1800 | + |
1801 | + def fieldsets(self): |
1802 | + result = [] |
1803 | + sources = [ |
1804 | + (self.base_id, self.base_legend, list(self.getBaseFields())), |
1805 | + (self.demo_id, self.demo_legend, list(self.getDemoFields())), |
1806 | + ] |
1807 | + for fieldset_id, legend, fields in sources: |
1808 | + result.append(self.makeFieldSet(fieldset_id, legend, fields, 2)) |
1809 | + return result |
1810 | + |
1811 | + def getDemoFields(self): |
1812 | + fields = field.Fields() |
1813 | + dfs = IResourceDemographicsFields(ISchoolToolApplication(None)) |
1814 | + for field_desc in dfs.filter_key(self.resource_type): |
1815 | + fields += field_desc.makeField() |
1816 | + return fields |
1817 | + |
1818 | + |
1819 | +############### Resource view (group determined by base class) ################# |
1820 | +class BaseResourceView(form.Form, ResourceFieldGenerator): |
1821 | + |
1822 | + template = ViewPageTemplateFile('templates/resource_view.pt') |
1823 | + mode = DISPLAY_MODE |
1824 | + id = 'resource-view' |
1825 | + |
1826 | + @property |
1827 | + def label(self): |
1828 | + return self.context.title |
1829 | + |
1830 | + def update(self): |
1831 | + self.fields = self.getBaseFields() |
1832 | + self.fields += self.getDemoFields() |
1833 | + self.subforms = [] |
1834 | + super(BaseResourceView, self).update() |
1835 | + |
1836 | + def __call__(self): |
1837 | + self.update() |
1838 | + return self.render() |
1839 | + |
1840 | + def updateWidgets(self): |
1841 | + super(BaseResourceView, self).updateWidgets() |
1842 | + for widget in self.widgets: |
1843 | + if not self.widgets[widget].value: |
1844 | + self.widgets[widget].mode = HIDDEN_MODE |
1845 | + |
1846 | + |
1847 | +class ResourceView(BaseResourceView): |
1848 | + """A location info view.""" |
1849 | + |
1850 | + resource_type = 'resource' |
1851 | + |
1852 | + def getBaseFields(self): |
1853 | + return field.Fields(IResource) |
1854 | + |
1855 | + |
1856 | +class LocationView(BaseResourceView): |
1857 | + """A location info view.""" |
1858 | + |
1859 | + resource_type = 'location' |
1860 | + |
1861 | + def getBaseFields(self): |
1862 | + return field.Fields(ILocation) |
1863 | + |
1864 | + |
1865 | +class EquipmentView(BaseResourceView): |
1866 | + """A equipment info view.""" |
1867 | + |
1868 | + resource_type = 'equipment' |
1869 | + |
1870 | + def getBaseFields(self): |
1871 | + return field.Fields(IEquipment) |
1872 | + |
1873 | + |
1874 | +############### Base classes of all resource add/edit views ################ |
1875 | +class BaseResourceAddView(form.AddForm, ResourceFieldGenerator): |
1876 | + |
1877 | + id = 'resource-form' |
1878 | + template = ViewPageTemplateFile('templates/resource_form.pt') |
1879 | + |
1880 | + def getBaseFields(self): |
1881 | + return field.Fields(IResource) |
1882 | + |
1883 | + def groupViewURL(self): |
1884 | + return '%s/%s.html' % (absoluteURL(self.context, self.request), |
1885 | + self.group_id) |
1886 | + |
1887 | + def updateActions(self): |
1888 | + super(BaseResourceAddView, self).updateActions() |
1889 | + self.actions['add'].addClass('button-ok') |
1890 | + self.actions['cancel'].addClass('button-cancel') |
1891 | + |
1892 | + @button.buttonAndHandler(_('Add')) |
1893 | + def handleAdd(self, action): |
1894 | + super(BaseResourceAddView, self).handleAdd.func(self, action) |
1895 | + |
1896 | + @button.buttonAndHandler(_('Cancel')) |
1897 | + def handle_cancel_action(self, action): |
1898 | + self.request.response.redirect(self.groupViewURL()) |
1899 | + |
1900 | + def createAndAdd(self, data): |
1901 | + resource = self._factory() |
1902 | + resource.title = data.get('title') |
1903 | + chooser = INameChooser(self.context) |
1904 | + resource.__name__ = chooser.chooseName('', resource) |
1905 | + form.applyChanges(self, resource, data) |
1906 | + notify(ObjectCreatedEvent(resource)) |
1907 | + self.context[resource.__name__] = resource |
1908 | + return resource |
1909 | + |
1910 | + def update(self): |
1911 | + self.fields = self.getBaseFields() |
1912 | + self.fields += self.getDemoFields() |
1913 | + self.updateWidgets() |
1914 | + self.updateActions() |
1915 | + self.actions.execute() |
1916 | + |
1917 | + def updateWidgets(self): |
1918 | + super(BaseResourceAddView, self).updateWidgets() |
1919 | + |
1920 | + def nextURL(self): |
1921 | + return absoluteURL(self.context, self.request) |
1922 | + |
1923 | + |
1924 | +class BaseResourceEditView(form.EditForm, ResourceFieldGenerator): |
1925 | + |
1926 | + id = 'resource-form' |
1927 | + template = ViewPageTemplateFile('templates/resource_form.pt') |
1928 | + |
1929 | + def update(self): |
1930 | + self.fields = self.getBaseFields() |
1931 | + self.fields += self.getDemoFields() |
1932 | + super(BaseResourceEditView, self).update() |
1933 | + |
1934 | + @button.buttonAndHandler(_('Apply')) |
1935 | + def handle_apply_action(self, action): |
1936 | + data, errors = self.extractData() |
1937 | + if errors: |
1938 | + self.status = self.formErrorsMessage |
1939 | + return |
1940 | + self.applyChanges(data) |
1941 | + url = absoluteURL(self.context, self.request) |
1942 | + self.request.response.redirect(url) |
1943 | + |
1944 | + @button.buttonAndHandler(_('Cancel')) |
1945 | + def handle_cancel_action(self, action): |
1946 | + url = absoluteURL(self.context, self.request) |
1947 | + self.request.response.redirect(url) |
1948 | + |
1949 | + @property |
1950 | + def label(self): |
1951 | + return _(u'Change information for ${fullname}', |
1952 | + mapping={'fullname': self.context.title}) |
1953 | + |
1954 | + def updateActions(self): |
1955 | + super(BaseResourceEditView, self).updateActions() |
1956 | + self.actions['apply'].addClass('button-ok') |
1957 | + self.actions['cancel'].addClass('button-cancel') |
1958 | + |
1959 | + |
1960 | +############### Resource add/edit views ################ |
1961 | +class BaseResourceForm(object): |
1962 | + |
1963 | + resource_type = 'resource' |
1964 | + base_id = 'base-data' |
1965 | + base_legend = _('Resource identification') |
1966 | + demo_id = 'demo-data' |
1967 | + demo_legend = _('Resource demographics') |
1968 | + |
1969 | + def getBaseFields(self): |
1970 | + return field.Fields(IResource).omit('type') |
1971 | + |
1972 | + |
1973 | +class ResourceAddView(BaseResourceForm, BaseResourceAddView): |
1974 | + |
1975 | + label = _('Add new resource') |
1976 | + _factory = Resource |
1977 | + |
1978 | + |
1979 | +class ResourceEditView(BaseResourceForm, BaseResourceEditView): |
1980 | + |
1981 | + label = _('Edit resource') |
1982 | + |
1983 | + |
1984 | +############### Location add/edit views ################ |
1985 | +class BaseLocationForm(object): |
1986 | + |
1987 | + resource_type = 'location' |
1988 | + base_id = 'base-data' |
1989 | + base_legend = _('Location identification') |
1990 | + demo_id = 'demo-data' |
1991 | + demo_legend = _('Location demographics') |
1992 | + |
1993 | + def getBaseFields(self): |
1994 | + fields = field.Fields(ILocation).select('title', 'type', 'description', |
1995 | + 'capacity', 'notes') |
1996 | + return fields |
1997 | + |
1998 | + |
1999 | +class LocationAddView(BaseLocationForm, BaseResourceAddView): |
2000 | + |
2001 | + label = _('Add new location') |
2002 | + _factory = Location |
2003 | + |
2004 | + |
2005 | +class LocationEditView(BaseLocationForm, BaseResourceEditView): |
2006 | + |
2007 | + label = _('Edit location') |
2008 | + |
2009 | + |
2010 | +############### Equipment add/edit views ################ |
2011 | +class BaseEquipmentForm(object): |
2012 | + |
2013 | + resource_type = 'equipment' |
2014 | + base_id = 'base-data' |
2015 | + base_legend = _('Equipment identification') |
2016 | + demo_id = 'demo-data' |
2017 | + demo_legend = _('Equipment demographics') |
2018 | + |
2019 | + def getBaseFields(self): |
2020 | + fields = field.Fields(IEquipment).select('title', 'type', 'description', |
2021 | + 'manufacturer', 'model', 'serialNumber', 'purchaseDate', 'notes') |
2022 | + return fields |
2023 | + |
2024 | + |
2025 | +class EquipmentAddView(BaseEquipmentForm, BaseResourceAddView): |
2026 | + |
2027 | + label = _('Add new equipment') |
2028 | + _factory = Equipment |
2029 | + |
2030 | + |
2031 | +class EquipmentEditView(BaseEquipmentForm, BaseResourceEditView): |
2032 | + |
2033 | + label = _('Edit equipment') |
2034 | + |
2035 | |
2036 | === modified file 'src/schooltool/resource/browser/resources.txt' |
2037 | --- src/schooltool/resource/browser/resources.txt 2008-09-05 12:13:50 +0000 |
2038 | +++ src/schooltool/resource/browser/resources.txt 2011-01-17 05:56:49 +0000 |
2039 | @@ -7,11 +7,7 @@ |
2040 | Set up |
2041 | ------ |
2042 | |
2043 | - >>> from zope.testbrowser.testing import Browser |
2044 | - >>> manager = Browser() |
2045 | - >>> manager.addHeader('Authorization', 'Basic manager:schooltool') |
2046 | - >>> manager.handleErrors = False |
2047 | - >>> manager.open('http://localhost/') |
2048 | + >>> manager = Browser('manager', 'schooltool') |
2049 | |
2050 | Creating Resources |
2051 | ------------------ |
2052 | @@ -32,8 +28,7 @@ |
2053 | |
2054 | >>> manager.getLink('New Location').click() |
2055 | >>> manager.getControl('Title').value = 'Room 101' |
2056 | - >>> manager.getControl(name='field.type.newSubType').value = 'class room' |
2057 | - >>> manager.getControl('Type').value = [''] |
2058 | + >>> manager.getControl(name='form.widgets.type.newSubType').value = 'class room' |
2059 | >>> manager.getControl('Capacity').value = '10' |
2060 | >>> manager.getControl('Add').click() |
2061 | >>> manager.getLink('class room').click() |
2062 | @@ -44,7 +39,7 @@ |
2063 | |
2064 | >>> manager.getLink('New Equipment').click() |
2065 | >>> manager.getControl('Title').value = 'Projector 1' |
2066 | - >>> manager.getControl(name='field.type.newSubType').value = 'Projector' |
2067 | + >>> manager.getControl(name='form.widgets.type.newSubType').value = 'Projector' |
2068 | >>> manager.getControl('Manufacturer').value = 'Sony' |
2069 | >>> manager.getControl('Model').value = 'RS-1' |
2070 | >>> manager.getControl('Serial Number').value = 'ABC123-987' |
2071 | @@ -60,7 +55,7 @@ |
2072 | |
2073 | >>> manager.getLink('New Equipment').click() |
2074 | >>> manager.getControl('Title').value = 'Projector 2' |
2075 | - >>> manager.getControl('Type').value = ['Projector'] |
2076 | + >>> manager.getControl(name='form.widgets.type').value = ['Projector'] |
2077 | >>> manager.getControl('Manufacturer').value = 'Sony' |
2078 | >>> manager.getControl('Model').value = 'RS-1' |
2079 | >>> manager.getControl('Serial Number').value = 'ABC123-9876' |
2080 | |
2081 | === modified file 'src/schooltool/resource/browser/subtype_widget.pt' |
2082 | --- src/schooltool/resource/browser/subtype_widget.pt 2007-03-17 15:25:31 +0000 |
2083 | +++ src/schooltool/resource/browser/subtype_widget.pt 2011-01-17 05:56:49 +0000 |
2084 | @@ -1,13 +1,16 @@ |
2085 | +<html xmlns="http://www.w3.org/1999/xhtml" |
2086 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
2087 | + tal:omit-tag=""> |
2088 | <input type="text" |
2089 | tal:attributes="id string:${view/name}.newSubType; |
2090 | name string:${view/name}.newSubType; |
2091 | - value: view/_getFormValue" |
2092 | - /> |
2093 | - |
2094 | + value view/freeTextValue" /> |
2095 | <select tal:attributes="id string:${view/name}; |
2096 | name string:${view/name}"> |
2097 | - <option value="">Choose One</option> |
2098 | + <option value="" i18n:translate="">Choose One</option> |
2099 | <option tal:repeat="type view/subTypes" |
2100 | - tal:attributes="value type" |
2101 | + tal:attributes="value type; |
2102 | + selected python:type==view.value and 'selected' or None" |
2103 | tal:content="type">subtype</option> |
2104 | </select> |
2105 | +</html> |
2106 | |
2107 | === added directory 'src/schooltool/resource/browser/templates' |
2108 | === added file 'src/schooltool/resource/browser/templates/resource_form.pt' |
2109 | --- src/schooltool/resource/browser/templates/resource_form.pt 1970-01-01 00:00:00 +0000 |
2110 | +++ src/schooltool/resource/browser/templates/resource_form.pt 2011-01-17 05:56:49 +0000 |
2111 | @@ -0,0 +1,138 @@ |
2112 | +<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
2113 | + <body> |
2114 | + <metal:nothing metal:fill-slot="content-header" /> |
2115 | + <metal:block metal:fill-slot="body"> |
2116 | + <form action="." method="post" enctype="multipart/form-data" class="standalone" |
2117 | + metal:define-macro="form" |
2118 | + tal:attributes="method view/method; |
2119 | + enctype view/enctype; |
2120 | + acceptCharset view/acceptCharset; |
2121 | + accept view/accept; |
2122 | + action view/action; |
2123 | + name view/name; |
2124 | + id view/id"> |
2125 | + <metal:subform define-macro="subform"> |
2126 | + <div class="viewspace" metal:define-slot="viewspace"> |
2127 | + <metal:label define-slot="label"> |
2128 | + <h3 metal:define-macro="form-label" |
2129 | + tal:condition="view/label|nothing" |
2130 | + tal:content="view/label"> |
2131 | + Form Label |
2132 | + </h3> |
2133 | + </metal:label> |
2134 | + <metal:info define-slot="info"> |
2135 | + <div class="required-info" |
2136 | + metal:define-macro="required-info"> |
2137 | + <!--<span class="required">*</span> |
2138 | + – required --> |
2139 | + </div> |
2140 | + </metal:info> |
2141 | + <metal:header define-slot="header"> |
2142 | + <div class="status" |
2143 | + tal:condition="view/status" |
2144 | + metal:define-macro="form-header"> |
2145 | + <div class="summary" |
2146 | + i18n:translate="" |
2147 | + tal:content="view/status"> |
2148 | + Form status summary |
2149 | + </div> |
2150 | + <ul class="errors" |
2151 | + tal:define="errors view/errors|view/widgets/errors" |
2152 | + tal:condition="errors" |
2153 | + metal:define-macro="form-errors"> |
2154 | + <li tal:repeat="error errors"> |
2155 | + <tal:block condition="error/widget"> |
2156 | + <span tal:replace="error/widget/label" />: |
2157 | + </tal:block> |
2158 | + <span tal:replace="structure error/render">Error Type</span> |
2159 | + </li> |
2160 | + </ul> |
2161 | + </div> |
2162 | + </metal:header> |
2163 | + <div metal:define-slot="extra-info" tal:replace="nothing"> |
2164 | + </div> |
2165 | + <div metal:define-slot="main"> |
2166 | + <metal:widget-rows define-macro="widget-rows"> |
2167 | + <tal:loop repeat="fieldset view/fieldsets"> |
2168 | + <fieldset tal:condition="fieldset/rows" |
2169 | + tal:attributes="id fieldset/id"> |
2170 | + <legend tal:content="fieldset/legend">legend</legend> |
2171 | + <tal:loop repeat="row fieldset/rows"> |
2172 | + <tal:block repeat="widget_id row"> |
2173 | + <metal:block tal:define="widget python:view.widgets[widget_id]"> |
2174 | + <div id="" class="row" |
2175 | + tal:attributes="id string:${widget/id}-row" |
2176 | + tal:condition="python:widget.mode != 'hidden'"> |
2177 | + <metal:widget-row define-macro="widget-row"> |
2178 | + <div class="label"> |
2179 | + <label tal:attributes="for widget/id"> |
2180 | + <span i18n:translate="" |
2181 | + tal:content="widget/label">label</span> |
2182 | + <span class="required" |
2183 | + tal:condition="widget/required">*</span> |
2184 | + </label> |
2185 | + </div> |
2186 | + <p class="hint" tal:content="widget/field/description">Description of this field.</p> |
2187 | + <div class="widget" tal:content="structure widget/render"> |
2188 | + <input type="text" size="24" value="" /> |
2189 | + </div> |
2190 | + <div class="error" |
2191 | + tal:condition="widget/error"> |
2192 | + <span tal:replace="structure widget/error/render">error</span> |
2193 | + </div> |
2194 | + </metal:widget-row> |
2195 | + </div> |
2196 | + <input type="hidden" value="" |
2197 | + tal:condition="python:widget.mode == 'hidden'" |
2198 | + tal:replace="structure widget/render" /> |
2199 | + </metal:block> |
2200 | + </tal:block> |
2201 | + </tal:loop> |
2202 | + <div metal:define-slot="extra-widgets" tal:replace="nothing"> |
2203 | + </div> |
2204 | + </fieldset> |
2205 | + </tal:loop> |
2206 | + </metal:widget-rows> |
2207 | + <metal:groups define-macro="groups"> |
2208 | + <fieldset tal:condition="view/groups|nothing" |
2209 | + tal:repeat="view view/groups"> |
2210 | + <legend tal:condition="view/label" |
2211 | + tal:content="view/label">Label</legend> |
2212 | + <metal:group-header define-slot="group-header"> |
2213 | + <div class="status" |
2214 | + tal:condition="view/widgets/errors"> |
2215 | + <div metal:use-macro="template/macros/errors" /> |
2216 | + </div> |
2217 | + </metal:group-header> |
2218 | + <metal:group-rows define-slot="group-rows"> |
2219 | + <div metal:use-macro="template/macros/widget-rows" /> |
2220 | + </metal:group-rows> |
2221 | + </fieldset> |
2222 | + </metal:groups> |
2223 | + </div> |
2224 | + <metal:above-buttons define-slot="above-buttons"> |
2225 | + <tal:block |
2226 | + repeat="form view/subforms" |
2227 | + content="structure form/render" |
2228 | + condition="view/subforms|nothing" /> |
2229 | + </metal:above-buttons> |
2230 | + </div> |
2231 | + <metal:buttons define-slot="buttons"> |
2232 | + <div metal:define-macro="form-buttons"> |
2233 | + <div class="buttons controls" |
2234 | + metal:define-slot="bottom-buttons"> |
2235 | + <tal:block condition="view/actions|nothing"> |
2236 | + <input tal:repeat="action view/actions/values" |
2237 | + tal:replace="structure action/render" |
2238 | + /> |
2239 | + </tal:block> |
2240 | + </div> |
2241 | + </div> |
2242 | + </metal:buttons> |
2243 | + <metal:bottom define-slot="bottom"> |
2244 | + </metal:bottom> |
2245 | + </metal:subform> |
2246 | + </form> |
2247 | + </metal:block> |
2248 | + </body> |
2249 | +</html> |
2250 | |
2251 | === added file 'src/schooltool/resource/browser/templates/resource_view.pt' |
2252 | --- src/schooltool/resource/browser/templates/resource_view.pt 1970-01-01 00:00:00 +0000 |
2253 | +++ src/schooltool/resource/browser/templates/resource_view.pt 2011-01-17 05:56:49 +0000 |
2254 | @@ -0,0 +1,36 @@ |
2255 | +<html metal:use-macro="view/@@standard_macros/page"> |
2256 | + |
2257 | + <body> |
2258 | + <metal:nothing metal:fill-slot="content-header" /> |
2259 | + <metal:block metal:fill-slot="body"> |
2260 | + <tal:block repeat="widget view/widgets/values"> |
2261 | + <div id="" class="row" |
2262 | + tal:attributes="id string:${widget/id}-row" |
2263 | + tal:condition="python:widget.mode != 'hidden'"> |
2264 | + <metal:widget-row define-macro="widget-row"> |
2265 | + <div class="label"> |
2266 | + <label tal:attributes="for widget/id"> |
2267 | + <span i18n:translate="" |
2268 | + tal:content="widget/label">label</span> |
2269 | + <span class="required" |
2270 | + tal:condition="widget/required">*</span> |
2271 | + </label> |
2272 | + </div> |
2273 | + <p class="hint" tal:content="widget/field/description">Description of this field.</p> |
2274 | + <div class="widget" tal:content="structure widget/render"> |
2275 | + <input type="text" size="24" value="" /> |
2276 | + </div> |
2277 | + <div class="error" |
2278 | + tal:condition="widget/error"> |
2279 | + <span tal:replace="structure widget/error/render">error</span> |
2280 | + </div> |
2281 | + </metal:widget-row> |
2282 | + </div> |
2283 | + <input type="hidden" value="" |
2284 | + tal:condition="python:widget.mode == 'hidden'" |
2285 | + tal:replace="structure widget/render" /> |
2286 | + </tal:block> |
2287 | + </metal:block> |
2288 | + </body> |
2289 | +</html> |
2290 | + |
2291 | |
2292 | === modified file 'src/schooltool/resource/configure.zcml' |
2293 | --- src/schooltool/resource/configure.zcml 2010-01-19 12:09:22 +0000 |
2294 | +++ src/schooltool/resource/configure.zcml 2011-01-17 05:56:49 +0000 |
2295 | @@ -57,13 +57,19 @@ |
2296 | factory="schooltool.app.app.SimpleNameChooser" |
2297 | provides="zope.container.interfaces.INameChooser" /> |
2298 | |
2299 | - <!-- Application hook --> |
2300 | + <!-- Application Init --> |
2301 | <adapter |
2302 | for="schooltool.app.interfaces.ISchoolToolApplication" |
2303 | factory=".resource.ResourceInit" |
2304 | name="schooltool.resources" |
2305 | /> |
2306 | |
2307 | + <!-- Application StartUp --> |
2308 | + <adapter |
2309 | + factory=".resource.ResourceStartUp" |
2310 | + name="schooltool.resources.startup" |
2311 | + /> |
2312 | + |
2313 | <!-- sample data --> |
2314 | <configure |
2315 | xmlns:zcml="http://namespaces.zope.org/zcml" |
2316 | @@ -120,6 +126,9 @@ |
2317 | name="booking" |
2318 | /> |
2319 | |
2320 | + <adapter factory=".resource.getResourceDemographics" /> |
2321 | + <adapter factory=".resource.DemographicsFormAdapter" /> |
2322 | + <adapter factory=".resource.getResourceDemographicsFields" /> |
2323 | |
2324 | <class class=".booking.ResourceBookingCalendar"> |
2325 | <require permission="schooltool.view" |
2326 | @@ -133,6 +142,17 @@ |
2327 | /> |
2328 | </class> |
2329 | |
2330 | + <!-- resource demos --> |
2331 | + |
2332 | + <class class=".resource.ResourceDemographicsFields"> |
2333 | + <implements interface="schooltool.resource.interfaces.IResourceDemographicsFields" /> |
2334 | + <require |
2335 | + permission="schooltool.edit" |
2336 | + interface="schooltool.resource.interfaces.IResourceDemographicsFields" |
2337 | + set_schema="schooltool.resource.interfaces.IResourceDemographicsFields" /> |
2338 | + </class> |
2339 | + |
2340 | + <adapter factory=".resource.getLimitKeyVocabularyForResourceFields" /> |
2341 | |
2342 | <subscriber |
2343 | for="schooltool.resource.interfaces.IBookingCalendar |
2344 | |
2345 | === modified file 'src/schooltool/resource/ftesting.zcml' |
2346 | --- src/schooltool/resource/ftesting.zcml 2010-04-08 06:26:02 +0000 |
2347 | +++ src/schooltool/resource/ftesting.zcml 2011-01-17 05:56:49 +0000 |
2348 | @@ -8,6 +8,7 @@ |
2349 | <include package="schooltool.term" /> |
2350 | <include package="schooltool.schoolyear" /> |
2351 | <include package="schooltool.term" file="time_machine.zcml" /> |
2352 | + <include package="schooltool.basicperson" /> |
2353 | |
2354 | <browser:defaultSkin name="SchoolTool" /> |
2355 | |
2356 | |
2357 | === modified file 'src/schooltool/resource/interfaces.py' |
2358 | --- src/schooltool/resource/interfaces.py 2010-02-22 10:39:32 +0000 |
2359 | +++ src/schooltool/resource/interfaces.py 2011-01-17 05:56:49 +0000 |
2360 | @@ -21,15 +21,22 @@ |
2361 | """ |
2362 | |
2363 | import zope.schema |
2364 | +from zope.configuration.fields import PythonIdentifier |
2365 | from zope.container.interfaces import IContainer, IContained |
2366 | +from zope.container.interfaces import IContainer, IOrderedContainer |
2367 | from zope.container.constraints import contains, containers |
2368 | from zope.interface import Interface, Attribute |
2369 | |
2370 | +from schooltool.basicperson.interfaces import IDemographics, IDemographicsFields |
2371 | from schooltool.calendar.interfaces import ICalendarEvent |
2372 | from schooltool.calendar.interfaces import ICalendar |
2373 | from schooltool.common import SchoolToolMessage as _ |
2374 | |
2375 | |
2376 | +class ResourceSubType(zope.schema.TextLine): |
2377 | + """Sub-type of the resource is a free-form text line.""" |
2378 | + |
2379 | + |
2380 | class IBaseResource(Interface): |
2381 | """Resource.""" |
2382 | |
2383 | @@ -37,7 +44,7 @@ |
2384 | title=_("Title"), |
2385 | description=_("Title of the resource.")) |
2386 | |
2387 | - type = zope.schema.TextLine( |
2388 | + type = ResourceSubType( |
2389 | title=_("Resource Type"), |
2390 | description=_("Type of resource"), |
2391 | required=True) |
2392 | @@ -73,7 +80,7 @@ |
2393 | class ILocation(IBaseResource): |
2394 | """Location.""" |
2395 | |
2396 | - type = zope.schema.TextLine( |
2397 | + type = ResourceSubType( |
2398 | title=_("Location Type"), |
2399 | description=_("Type of location (i.e. computer lab, class room, etc.)"), |
2400 | required=True) |
2401 | @@ -88,7 +95,7 @@ |
2402 | class IEquipment(IBaseResource): |
2403 | """Equipment.""" |
2404 | |
2405 | - type = zope.schema.TextLine( |
2406 | + type = ResourceSubType( |
2407 | title=_("Equipment Type"), |
2408 | description=_("Type of equipment (i.e. camcorder, computer, etc.)"), |
2409 | required=True) |
2410 | @@ -160,3 +167,16 @@ |
2411 | |
2412 | class IBookingTimetableEvent(ICalendarEvent): |
2413 | """Event that represents a possible booking in a timetable slot.""" |
2414 | + |
2415 | + |
2416 | +class IResourceDemographics(IDemographics): |
2417 | + """Demographics data storage for a resource |
2418 | + |
2419 | + Stores any kind of data defined by field descriptions that are set |
2420 | + up for the resource container. |
2421 | + """ |
2422 | + |
2423 | + |
2424 | +class IResourceDemographicsFields(IDemographicsFields): |
2425 | + """Demographics field storage for resource demos.""" |
2426 | + |
2427 | |
2428 | === modified file 'src/schooltool/resource/resource.py' |
2429 | --- src/schooltool/resource/resource.py 2010-01-19 12:09:22 +0000 |
2430 | +++ src/schooltool/resource/resource.py 2011-01-17 05:56:49 +0000 |
2431 | @@ -24,23 +24,42 @@ |
2432 | __docformat__ = 'restructuredtext' |
2433 | |
2434 | from persistent import Persistent |
2435 | -from zope.component import adapts |
2436 | -from zope.interface import implements |
2437 | +from persistent.dict import PersistentDict |
2438 | + |
2439 | +from zope.component import adapts, adapter |
2440 | +from zope.interface import implements, implementer |
2441 | +from zope.interface import Interface |
2442 | from zope.annotation.interfaces import IAttributeAnnotatable |
2443 | from zope.container.contained import Contained |
2444 | from zope.container.btree import BTreeContainer |
2445 | +from zope.container.ordered import OrderedContainer |
2446 | +from zope.location.location import Location |
2447 | +from zope.schema import TextLine, Bool, Date, Choice |
2448 | +from zope.schema.vocabulary import SimpleVocabulary |
2449 | +from zope.schema.vocabulary import SimpleTerm |
2450 | |
2451 | from schooltool.app.app import Asset |
2452 | -from schooltool.app.app import InitBase |
2453 | +from schooltool.app.app import InitBase, StartUpBase |
2454 | +from schooltool.app.utils import vocabulary |
2455 | from schooltool.app.security import LeaderCrowd |
2456 | -from schooltool.securitypolicy.crowds import TeachersCrowd |
2457 | from schooltool.app.interfaces import ICalendarParentCrowd |
2458 | +from schooltool.app.interfaces import ISchoolToolApplication |
2459 | +from schooltool.app.utils import vocabulary |
2460 | +from schooltool.basicperson.demographics import PersonDemographicsData |
2461 | +from schooltool.basicperson.demographics import DemographicsFields |
2462 | +from schooltool.basicperson.demographics import IDemographicsForm |
2463 | +from schooltool.basicperson.interfaces import IFieldFilterVocabulary |
2464 | from schooltool.resource import interfaces |
2465 | +from schooltool.securitypolicy.crowds import TeachersCrowd |
2466 | from schooltool.securitypolicy.crowds import ConfigurableCrowd, AggregateCrowd |
2467 | from schooltool.securitypolicy.crowds import AuthenticatedCrowd |
2468 | from schooltool.common import SchoolToolMessage as _ |
2469 | |
2470 | |
2471 | +RESOURCE_DEMO_FIELDS_KEY = 'schooltool.resource.demographics_fields' |
2472 | +RESOURCE_DEMO_DATA_KEY = 'schooltool.resource.demographics_data' |
2473 | + |
2474 | + |
2475 | class ResourceContainer(BTreeContainer): |
2476 | """Container of resources.""" |
2477 | |
2478 | @@ -93,6 +112,18 @@ |
2479 | |
2480 | def __call__(self): |
2481 | self.app['resources'] = ResourceContainer() |
2482 | + self.app[RESOURCE_DEMO_FIELDS_KEY] = ResourceDemographicsFields() |
2483 | + self.app[RESOURCE_DEMO_DATA_KEY] = ResourceDemographicsDataContainer() |
2484 | + |
2485 | + |
2486 | +class ResourceStartUp(StartUpBase): |
2487 | + |
2488 | + def __call__(self): |
2489 | + if RESOURCE_DEMO_FIELDS_KEY not in self.app: |
2490 | + self.app[RESOURCE_DEMO_FIELDS_KEY] = ResourceDemographicsFields() |
2491 | + if RESOURCE_DEMO_DATA_KEY not in self.app: |
2492 | + self.app[RESOURCE_DEMO_DATA_KEY] = \ |
2493 | + ResourceDemographicsDataContainer() |
2494 | |
2495 | |
2496 | class ResourceViewersCrowd(ConfigurableCrowd): |
2497 | @@ -127,3 +158,77 @@ |
2498 | |
2499 | def crowdFactories(self): |
2500 | return [LeaderCrowd, TeachersCrowd] |
2501 | + |
2502 | + |
2503 | +################### Demographics ################# |
2504 | +class ResourceDemographicsFields(DemographicsFields): |
2505 | + """Storage for demographics fields for all resources.""" |
2506 | + implements(interfaces.IResourceDemographicsFields) |
2507 | + |
2508 | + |
2509 | +@implementer(interfaces.IResourceDemographicsFields) |
2510 | +@adapter(ISchoolToolApplication) |
2511 | +def getResourceDemographicsFields(app): |
2512 | + return app[RESOURCE_DEMO_FIELDS_KEY] |
2513 | + |
2514 | + |
2515 | +class ResourceDemographicsDataContainer(BTreeContainer): |
2516 | + """Storage for demographics information for all resources.""" |
2517 | + |
2518 | + |
2519 | +class ResourceDemographicsData(PersonDemographicsData): |
2520 | + """Storage for demographics information for a resource.""" |
2521 | + implements(interfaces.IResourceDemographics) |
2522 | + |
2523 | + def isValidKey(self, key): |
2524 | + app = ISchoolToolApplication(None) |
2525 | + demographics_fields = interfaces.IResourceDemographicsFields(app) |
2526 | + return key in demographics_fields |
2527 | + |
2528 | + |
2529 | +@adapter(interfaces.IBaseResource) |
2530 | +@implementer(interfaces.IResourceDemographics) |
2531 | +def getResourceDemographics(resource): |
2532 | + app = ISchoolToolApplication(None) |
2533 | + rdc = app[RESOURCE_DEMO_DATA_KEY] |
2534 | + demographics = rdc.get(resource.__name__, None) |
2535 | + if demographics is None: |
2536 | + rdc[resource.__name__] = demographics = ResourceDemographicsData() |
2537 | + return demographics |
2538 | + |
2539 | + |
2540 | +@adapter(interfaces.IResourceDemographicsFields) |
2541 | +@implementer(IFieldFilterVocabulary) |
2542 | +def getLimitKeyVocabularyForResourceFields(resource_field_description_container): |
2543 | + return vocabulary([ |
2544 | + ('resource', _('Resource')), |
2545 | + ('location', _('Location')), |
2546 | + ('equipment', _('Equipment')), |
2547 | + ]) |
2548 | + |
2549 | + |
2550 | +class DemographicsFormAdapter(object): |
2551 | + implements(IDemographicsForm) |
2552 | + adapts(interfaces.IBaseResource) |
2553 | + |
2554 | + def __init__(self, context): |
2555 | + self.__dict__['context'] = context |
2556 | + self.__dict__['demographics'] = interfaces.IResourceDemographics( |
2557 | + self.context) |
2558 | + |
2559 | + def __setattr__(self, name, value): |
2560 | + self.demographics[name] = value |
2561 | + |
2562 | + def __getattr__(self, name): |
2563 | + return self.demographics.get(name, None) |
2564 | + |
2565 | + |
2566 | +@adapter(interfaces.IResourceDemographicsFields) |
2567 | +@implementer(IFieldFilterVocabulary) |
2568 | +def getLimitKeyVocabularyForResourceFields(resource_field_description_container): |
2569 | + return vocabulary([ |
2570 | + ('resource', _('Resource')), |
2571 | + ('location', _('Location')), |
2572 | + ('equipment', _('Equipment')), |
2573 | + ]) |
2574 | + |
2575 | |
2576 | === modified file 'src/schooltool/resource/tests/test_resource.py' |
2577 | --- src/schooltool/resource/tests/test_resource.py 2010-01-10 17:31:54 +0000 |
2578 | +++ src/schooltool/resource/tests/test_resource.py 2011-01-17 05:56:49 +0000 |
2579 | @@ -68,6 +68,7 @@ |
2580 | |
2581 | """ |
2582 | |
2583 | + |
2584 | def doctest_Location(): |
2585 | r"""Test for Location |
2586 | |
2587 | @@ -84,6 +85,7 @@ |
2588 | |
2589 | """ |
2590 | |
2591 | + |
2592 | def doctest_Equipment(): |
2593 | r"""Test for Equipment |
2594 | |
2595 | @@ -105,6 +107,88 @@ |
2596 | >>> projector.purchaseDate |
2597 | """ |
2598 | |
2599 | + |
2600 | +def doctest_ResourceDemographics(): |
2601 | + r"""Test Resource Demographics objects and adapters |
2602 | + |
2603 | + >>> from schooltool.resource import interfaces, resource |
2604 | + >>> from schooltool.basicperson import demographics |
2605 | + |
2606 | + First we need to set up a mock app and register its adapter: |
2607 | + |
2608 | + >>> from zope.component import provideAdapter |
2609 | + >>> from zope.interface import implements |
2610 | + >>> from schooltool.app.interfaces import ISchoolToolApplication |
2611 | + |
2612 | + >>> class MockSchoolToolApplication(dict): |
2613 | + ... implements(ISchoolToolApplication) |
2614 | + >>> app = MockSchoolToolApplication() |
2615 | + >>> provideAdapter(lambda context: app, (None,), ISchoolToolApplication) |
2616 | + |
2617 | + We need to do what the AppInit adapter would otherwise do: |
2618 | + |
2619 | + >>> resource.ResourceInit(app)() |
2620 | + >>> resource.RESOURCE_DEMO_FIELDS_KEY in app |
2621 | + True |
2622 | + >>> resource.RESOURCE_DEMO_DATA_KEY in app |
2623 | + True |
2624 | + |
2625 | + There's an adapter for the resource demo fields container: |
2626 | + |
2627 | + >>> provideAdapter(resource.getResourceDemographicsFields) |
2628 | + >>> dfs = interfaces.IResourceDemographicsFields(app) |
2629 | + >>> interfaces.IResourceDemographicsFields.providedBy(dfs) |
2630 | + True |
2631 | + >>> len(dfs) |
2632 | + 0 |
2633 | + |
2634 | + We'll add some demo fields to the container, some that are limited to a |
2635 | + specific resource type or types: |
2636 | + |
2637 | + >>> dfs['ID'] = demographics.TextFieldDescription("ID", "Identifier") |
2638 | + >>> dfs['square_ft'] = demographics.TextFieldDescription("square_ft", |
2639 | + ... "Square Feet", limit_keys=['location']) |
2640 | + >>> dfs['warranty'] = demographics.TextFieldDescription("warranty", |
2641 | + ... "Warranty", limit_keys=['equiptment']) |
2642 | + >>> dfs['creation_date'] = demographics.DateFieldDescription( |
2643 | + ... "creation_date", "Creation Date", |
2644 | + ... limit_keys=['location', 'equiptment']) |
2645 | + |
2646 | + When we pass the filter_key method a key that does not |
2647 | + belong to any of the limit_keys lists, then it will only return |
2648 | + those fields that have empty limit_keys lists. |
2649 | + |
2650 | + >>> [f.__name__ for f in dfs.filter_key('anything')] |
2651 | + [u'ID'] |
2652 | + |
2653 | + When we pass 'location', it picks up the additional fields that are for |
2654 | + location type resources. |
2655 | + |
2656 | + >>> [f.__name__ for f in dfs.filter_key('location')] |
2657 | + [u'ID', u'square_ft', u'creation_date'] |
2658 | + |
2659 | + When we pass 'equiptment', it picks up the additional fields that are for |
2660 | + equiptment type resources. |
2661 | + |
2662 | + >>> [f.__name__ for f in dfs.filter_key('equiptment')] |
2663 | + [u'ID', u'warranty', u'creation_date'] |
2664 | + |
2665 | + Finally there's an adapter that adapts a resource it's demo data: |
2666 | + |
2667 | + >>> provideAdapter(resource.getResourceDemographics) |
2668 | + |
2669 | + Now we will create a resource and see what we get when we adapt it to |
2670 | + IDemographics: |
2671 | + |
2672 | + >>> sample = resource.Resource('Sample Resource') |
2673 | + >>> sample.__name__ = 'sample' |
2674 | + >>> demos = interfaces.IResourceDemographics(sample) |
2675 | + >>> interfaces.IResourceDemographics.providedBy(demos) |
2676 | + True |
2677 | + >>> len(demos) |
2678 | + 0 |
2679 | + """ |
2680 | + |
2681 | def test_suite(): |
2682 | return unittest.TestSuite([ |
2683 | doctest.DocTestSuite(optionflags=doctest.ELLIPSIS), |