source: main/waeup.sirp/trunk/src/waeup/sirp/browser/viewlets.py @ 7738

Last change on this file since 7738 was 7736, checked in by Henrik Bettermann, 13 years ago

Add viewletmanager which renders widgets in forms.

  • Property svn:keywords set to Id
File size: 23.2 KB
Line 
1## $Id: viewlets.py 7736 2012-03-01 06:50:40Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19from urllib import urlencode
20from zope.i18n import translate
21from zope.component import getMultiAdapter, queryAdapter, getUtility
22from zope.interface import Interface
23from zope.location.interfaces import ISite
24from zope.traversing.browser import absoluteURL
25from waeup.sirp.browser.pages import (
26    UniversityPage, FacultiesContainerPage, DatacenterPage, FacultyPage,
27    DepartmentPage, CoursePage, CertificatePage, CertificateCoursePage,
28    UsersContainerPage, UserManageFormPage)
29from waeup.sirp.browser.interfaces import (
30    IFacultiesContainer, IFaculty, IDepartment, ICourse, ICertificate,
31    ICertificateCourse, IBreadcrumbContainer, IUniversity, IUsersContainer)
32from waeup.sirp.interfaces import (
33    ISIRPUtils, ISIRPObject, ISIRPXMLExporter,
34    ISIRPXMLImporter, IDataCenter, IUserAccount)
35from waeup.sirp.browser.layout import SIRPPage, default_primary_nav_template
36from waeup.sirp.utils.helpers import get_user_account
37
38from waeup.sirp.interfaces import MessageFactory as _
39
40grok.templatedir('templates')
41grok.context(ISIRPObject) # Make ISIRPObject the default context
42
43class LeftSidebar(grok.ViewletManager):
44    grok.name('left')
45
46class BreadCrumbManager(grok.ViewletManager):
47    grok.name('breadcrumbs')
48
49class LanguageManager(grok.ViewletManager):
50    grok.name('languages')
51
52class ActionBar(grok.ViewletManager):
53    grok.name('actionbar')
54
55class AdministrationTasks(grok.ViewletManager):
56    grok.name('admintasks')
57
58class WidgetsSlot(grok.ViewletManager):
59    grok.name('widgets')
60
61
62#
63# Baseclasses that give some defaults for really used viewlets.
64#
65class ActionButton(grok.Viewlet):
66    """A base for action buttons.
67
68    An action button provides an icon, some text and links to a
69    target.  If you want to set a different text, icon or target name
70    for some active button below, just override the approriate
71    attribute in the concerned viewlet.
72
73    Action buttons provide by default dynamic attributes
74
75     * ``alt``
76          An alternative text for the icon. By default the same as
77          the text.
78
79     * ``icon_url``
80          The URL of the icon.
81
82     * ``target_url``
83          The URL of the link target.
84
85    """
86    grok.baseclass()
87    grok.context(ISIRPObject)
88    grok.viewletmanager(ActionBar)
89    icon = 'actionicon_modify.png' # File must exist in static/
90    target = '@@manage' # link to this viewname.
91    text = _('Edit') # Text to display on the button
92
93    # We set the template file explicitly (instead of using
94    # ``grok.template('actionbutton')``) to stick with this template
95    # also in derived classes in other packages. If we didn't, those
96    # derived ActionButton viewlets had to provide an own template,
97    # which would not be updated automatically, when the local
98    # template ``templates/actionbutton.pt`` changes.
99    #
100    # Inheriting viewlets that wish to use their own template anyway
101    # can do so by setting their local ``grok.template(<mytemplate>)``
102    # and setting ``template`` to ``None`` for the class::
103    #
104    # class DerivedActionButton(ActionButton):
105    #   ...
106    #   grok.template('overriding_template')
107    #   template = None
108    #   ...
109    #
110    template = grok.PageTemplateFile('templates/actionbutton.pt')
111
112    @property
113    def alt(self):
114        """Alternative text for icon.
115        """
116        return self.text
117
118    @property
119    def icon_url(self):
120        """Get the icon URL.
121        """
122        static = self.view.static
123        if static is None or static.get(self.icon, None) is None:
124            # In derived classes defined in other modules/packages
125            # than w.s.browser, ``static`` might refer to a static dir
126            # local to the derived class' module. As we often like to
127            # get the icons from here
128            # (i.e. waeup.sirp.browser/static), we set the directory
129            # resource appropiately.
130            #
131            # XXX: The hardcoding of 'w.s.browser' should be replaced
132            #      by something smarter.
133            #
134            # TODO: notes in here should go to general documentation.
135            static = queryAdapter(
136                self.request, Interface, name='waeup.sirp.browser')
137        return static[self.icon]()
138
139    @property
140    def target_url(self):
141        """Get a URL to the target...
142        """
143        return self.view.url(self.view.context, self.target)
144
145class PlainActionButton(ActionButton):
146    """A base for action buttons without image
147    """
148    grok.baseclass()
149    template = grok.PageTemplateFile('templates/plainactionbutton.pt')
150
151   
152class ManageActionButton(ActionButton):
153    """A base for 'edit' buttons
154    """
155    grok.baseclass()
156    grok.order(2)
157    grok.require('waeup.manageUniversity')
158    icon = 'actionicon_modify.png'
159    target = '@@manage'
160    text = _('Edit')
161
162class AddActionButton(ActionButton):
163    """A base for 'add' buttons.
164    """
165    grok.baseclass()
166    grok.order(4)
167    grok.require('waeup.manageUniversity')
168    icon = 'actionicon_add.png'
169    target = 'add'
170    text = _('Add')
171   
172class RemoveActionButton(ActionButton):
173    """A base for 'remove' buttons.
174    """
175    grok.baseclass()
176    grok.order(4)
177    grok.require('waeup.manageUniversity')
178    icon = 'actionicon_delete.png'
179    target = 'remove'
180    text = _('Remove')
181
182class SearchActionButton(ActionButton):
183    """A base for 'search' buttons.
184    """
185    grok.baseclass()
186    grok.order(5)
187    grok.require('waeup.manageUniversity')
188    icon = 'actionicon_search.png'
189    target = 'search'
190    text = _('Search')
191
192
193#
194# General viewlets (for more than one page/context)
195#
196
197class BreadCrumbs(grok.Viewlet):
198    grok.context(ISIRPObject)
199    grok.viewletmanager(BreadCrumbManager)
200    grok.order(1)
201
202    def getEntries(self):
203        result = []
204        site = grok.getSite()
205        context = self.context
206        breadcrumbs = IBreadcrumbContainer(self.view)
207        for breadcrumb in breadcrumbs:
208            if breadcrumb.target:
209                yield dict(
210                    title = breadcrumb.title,
211                    url = self.view.url(breadcrumb.context, breadcrumb.target)
212                    )
213
214class LanguagesLink(grok.Viewlet):
215    """ The language selector itself.
216    """
217    grok.viewletmanager(LanguageManager)
218    grok.context(ISIRPObject)
219    grok.require('waeup.Public')
220    title = u'Languages'
221
222    def render(self):
223        preferred_languages = getUtility(ISIRPUtils).PREFERRED_LANGUAGES_DICT
224        html = u''
225        for key, value in sorted(
226            preferred_languages.items(), key=lambda lang: lang[1][0]):
227            args = {'lang':key, 'view_name':self.view.__name__}
228            url = self.view.url(
229                self.context) + '/@@change_language?%s' % urlencode(args)
230            html += u'| <a href="%s" title="%s">%s</a> ' % (url, value[1], key)
231        return html
232
233# Problem with circular references. Disabled for now...
234# class ExportXMLAction(grok.Viewlet):
235#    grok.viewletmanager(ActionBar)
236#     #grok.view(Index)
237#     grok.order(98)
238#     grok.require('waeup.manageUniversity')
239
240#class ImportXMLAction(grok.Viewlet):
241#    grok.viewletmanager(ActionBar)
242#    #grok.view(Index)
243#    grok.order(99)
244#    grok.require('waeup.manageUniversity')
245#
246#    def update(self):
247#        # We cannot simply replace local sites.
248#        self.can_import = not ISite.providedBy(self.context)
249
250class LeftSidebarLink(grok.Viewlet):
251    """ An entry on left sidebar.
252
253    This is only a baseclass that won't be rendered actually. Deriving
254    viewlets can override certain values and will be rendered with the
255    values set here as default.
256    """
257    grok.baseclass()
258    grok.viewletmanager(LeftSidebar)
259    grok.context(ISIRPObject)
260    grok.order(5)
261    grok.require('waeup.manageUniversity')
262    icon = 'actionicon_modify.png' # File must exist in static/
263    title = 'Text of link'
264
265    @property
266    def url(self):
267        return '@@index'
268
269    @property
270    def icon_url(self):
271        """Get the icon URL.
272        """
273        if self.icon:
274            static = self.view.static
275            if static is None or static.get(self.icon, None) is None:
276                # In derived classes defined in other modules/packages
277                # than w.s.browser, ``static`` might refer to a static dir
278                # local to the derived class' module. As we often like to
279                # get the icons from here
280                # (i.e. waeup.sirp.browser/static), we set the directory
281                # resource appropiately.
282                #
283                # XXX: The hardcoding of 'w.s.browser' should be replaced
284                #      by something smarter.
285                #
286                # TODO: notes in here should go to general documentation.
287                static = queryAdapter(
288                    self.request, Interface, name='waeup.sirp.browser')
289            return static[self.icon]()
290        return
291   
292    # Render link only if url is provided.
293    def render(self):
294        if self.url:
295            if self.icon_url:
296                return u'<li><a href="%s"><img src="%s" /> %s </a></li>' % (
297                    self.url, self.icon_url, self.title)
298            else:
299                return u'<li><a href="%s">%s </a></li>' % (
300                    self.url, self.title)
301        else:
302            return ''
303
304class WidgetsTableRows(grok.Viewlet):
305    """A base for action buttons without image
306    """
307    template = grok.PageTemplateFile('templates/widgets.pt')
308    grok.viewletmanager(WidgetsSlot)
309
310
311#
312# waeup.sirp.app.University viewlets...
313#
314class Login(grok.Viewlet):
315    """This viewlet allows to login in the sidebar.
316    """
317    grok.viewletmanager(LeftSidebar)
318    grok.context(ISIRPObject)
319    grok.view(Interface)
320    grok.order(2)
321    grok.require('waeup.Anonymous')
322    text = _('Login')
323    link = 'login'
324
325    def render(self):
326        if self.request.principal.id != 'zope.anybody':
327            return ''
328        url = self.view.url(grok.getSite(), self.link)
329        return u'<li"><a href="%s">%s</a></li>' % (
330                url, self.text)
331
332
333class ManageLink(grok.Viewlet):
334    """A link displayed in the upper left box.
335
336    This viewlet renders a link to the application object's settings
337    form (the 'manage' view).
338
339    In derived classes you can create different links by setting a
340    different link and text attribute. The `link` parameter is
341    understood relative to the respective application object, so that
342    ``@@manage`` will create a link to
343    ``localhost:8080/app/@@manage``.
344
345    Links defined by descendants from this viewlet are displayed on
346    every page the user is allowed to go to, if the user has also the
347    permissions set by `grok.require()`. By default only users with
348    ``waeup.manageUniversity`` permission will see links defined by
349    this or derivated classes.
350    """
351    grok.viewletmanager(LeftSidebar)
352    grok.context(ISIRPObject)
353    grok.view(Interface)
354    grok.order(5)
355    # This link is only displayed, if the user is
356    # allowed to use it!
357    grok.require('waeup.managePortalConfiguration')
358
359    link = 'configuration'
360    text = _(u'Portal Configuration')
361   
362    def render(self):
363        url = self.view.url(grok.getSite(), self.link)
364        text = translate(self.text, context=self.request)
365        return u'<li><a href="%s">%s</a></li>' % (
366                url, text)
367
368class ManageUsersLink(ManageLink):
369    """A link to users management, placed in upper left box.
370    """
371    grok.order(6)
372    grok.require('waeup.manageUsers')
373
374    link = u'users'
375    text = _(u'Portal Users')
376
377class ContactActionButton(ManageActionButton):
378    grok.order(4)
379    grok.context(IUserAccount)
380    grok.view(UserManageFormPage)
381    grok.require('waeup.manageUsers')
382    icon = 'actionicon_mail.png'
383    text = _('Send email')
384    target = 'contactuser'
385
386class ManageDataCenter(ManageLink):
387    """A link to datacenter, placed in upper left box.
388    """
389    grok.order(6)
390    grok.require('waeup.manageDataCenter')
391
392    link = u'datacenter'
393    text = _(u'Data Center')
394
395class MyPreferences(LeftSidebarLink):
396    """A link to personal preferences, placed in upper left box.
397    """
398    grok.order(7)
399    grok.require('waeup.Public')
400    title = _(u'My Preferences')
401    icon = ''
402
403    @property
404    def url(self):
405        account_object = get_user_account(self.request)
406        if account_object:
407            return self.view.url(account_object)
408        return
409
410class MyRoles(LeftSidebarLink):
411    """A link to display site and local roles.
412    """
413    grok.order(8)
414    grok.require('waeup.Public')
415    title = _(u'My Roles')
416    icon = ''
417
418    @property
419    def url(self):
420        account_object = get_user_account(self.request)
421        if account_object:
422            return self.view.url(account_object) + '/my_roles'
423        return
424
425#
426# Manage ("Edit settings") actions...
427#
428
429class ManageDataCenterActionButton(ManageActionButton):
430    """ 'Edit settings' button for datacenter.
431    """
432    grok.context(IDataCenter)
433    grok.view(DatacenterPage)
434    text = _('Edit settings')
435
436class ManageFacultiesContainerActionButton(ManageActionButton):
437    """ 'Manage settings' button for faculties.
438    """
439    grok.context(IFacultiesContainer)
440    grok.view(FacultiesContainerPage)
441    text = _('Manage academic section')
442
443class SearchFacultiesContainerActionButton(ManageActionButton):
444    """ 'Manage settings' button for faculties.
445    """
446    grok.context(IFacultiesContainer)
447    grok.view(FacultiesContainerPage)
448    text = _('Search academic section')
449    icon = 'actionicon_search.png'
450    target = '@@search'
451   
452class ManageFacultyActionButton(ManageActionButton):
453    """ 'Manage settings' button for faculties.
454    """
455    grok.context(IFaculty)
456    grok.view(FacultyPage)
457    text = _('Manage faculty')
458
459class ManageDepartmentActionButton(ManageActionButton):
460    """ 'Manage settings' button for departments.
461    """
462    grok.context(IDepartment)
463    grok.view(DepartmentPage)
464    text = _('Manage department')
465
466class ShowDepartmentStudentsActionButton(ManageActionButton):
467    """ 'Show students' button for departments.
468    """
469    grok.context(IDepartment)
470    grok.view(DepartmentPage)
471    grok.require('waeup.showStudents')
472    icon = 'actionicon_student.png'
473    text = _('Show students')
474    target = 'showdepstudents'
475
476class ManageCourseActionButton(ManageActionButton):
477    """ 'Edit settings' button for courses.
478    """
479    grok.context(ICourse)
480    grok.view(CoursePage)
481    text = _('Edit course')
482   
483class ManageCertificateActionButton(ManageActionButton):
484    """ 'Manage settings' button for certificates.
485    """
486    grok.context(ICertificate)
487    grok.view(CertificatePage)
488    text = _('Manage certificate')
489
490class ShowCertificateStudentsActionButton(ManageActionButton):
491    """ 'Show students' button for certificates.
492    """
493    grok.context(ICertificate)
494    grok.view(CertificatePage)
495    grok.require('waeup.showStudents')
496    icon = 'actionicon_student.png'
497    text = _('Show students')
498    target = 'showcertstudents'
499
500class ManageCertificateCourseActionButton(ManageActionButton):
501    """ 'Manage settings' button for certificate courses.
502    """
503    grok.context(ICertificateCourse)
504    grok.view(CertificateCoursePage)
505    text = _('Edit course referrer')
506
507#
508# Add actions...
509#
510
511class AddUserActionButton(AddActionButton):
512    grok.require('waeup.manageUsers')
513    grok.context(IUsersContainer)
514    grok.view(UsersContainerPage)
515    text = _('Add user')
516
517#
518# Actions with a 'browse' icon...
519#
520class BrowseActionButton(ActionButton):
521    grok.baseclass()
522    grok.context(ISIRPObject)
523    grok.template('actionbutton')
524    grok.viewletmanager(ActionBar)
525    grok.require('waeup.manageUniversity')
526    icon = 'actionicon_manage.png' # File must exist in static/
527    target = '@@show' # link to this viewname.
528    text = _('Show batch logs') # Text to display on the button
529
530class BrowseDatacenterLogs(BrowseActionButton):
531    grok.context(IDataCenter)
532    grok.view(DatacenterPage)
533    grok.order(4)
534    icon = 'documentinfo_templet.png'
535    target = '@@logs'
536    text = _('Show batch logs')
537
538#
539# Misc. buttons...
540#
541class BatchOpButton(ActionButton):
542    grok.context(IDataCenter)
543    grok.view(DatacenterPage)
544    grok.require('waeup.manageUniversity')
545    grok.order(6)
546    icon = 'actionbox_templet.png'
547    target = '@@import1'
548    text = _('Batch processing')
549
550class UploadCSVButton(ActionButton):
551    grok.context(IDataCenter)
552    grok.view(DatacenterPage)
553    grok.require('waeup.manageUniversity')
554    grok.order(5)
555    icon = 'go-up-16x16.png'
556    target = '@@upload'
557    text = _('Upload CSV file')
558
559#
560# Primary navigation tabs (in upper left navigation bar)...
561#
562class PrimaryNavManager(grok.ViewletManager):
563    """Viewlet manager for the primary navigation tab.
564    """
565    grok.name('primary_nav')
566
567class PrimaryNavTab(grok.Viewlet):
568    """Base for primary nav tabs.
569    """
570    grok.baseclass()
571    grok.viewletmanager(PrimaryNavManager)
572    grok.order(1)
573    grok.require('waeup.Public')
574    template = default_primary_nav_template
575
576    pnav = 0 # This is a kind of id of a tab. If some page provides
577             # also a 'pnav' attribute with the same value (here: 0),
578             # then the tab will be rendered as 'active' when the page
579             # gets rendered.
580             #
581             # This way you can assign certain pages to certain
582             # primary nav tabs. Each primary tab should therefore set
583             # the 'pnav' attribute to a different value (or several
584             # tabs might be rendered as active simultanously when the
585             # page gets rendered.
586    tab_title = u'Some Text'
587   
588    @property
589    def link_target(self):
590        return self.view.application_url()
591
592    @property
593    def active(self):
594        view_pnav = getattr(self.view, 'pnav', 0)
595        if view_pnav == self.pnav:
596            return 'active'
597        return ''
598
599#class HomeTab(PrimaryNavTab):
600#    """Home-tab in primary navigation.
601#    """
602#    grok.order(1)
603#    grok.require('waeup.Public')
604
605#    pnav = 0
606#    tab_title = u'Home'
607
608class FacultiesTab(PrimaryNavTab):
609    """Faculties-tab in primary navigation.
610    """
611    grok.order(2)
612    grok.require('waeup.viewAcademics')
613
614    pnav = 1
615    tab_title = _(u'Academics')
616
617    @property
618    def link_target(self):
619        return self.view.application_url('faculties')
620
621   
622class EnquiriesTab(PrimaryNavTab):
623    """Contact tab in primary navigation.
624
625    Display tab only for anonymous. Authenticated users can call a
626    contact form from the user navigation bar.
627    """
628    grok.order(6)
629    grok.require('waeup.Anonymous')
630    tab_title = _(u'Enquiries')
631    pnav = 2
632
633    # Also zope.manager has role Anonymous.
634    # To avoid displaying this tab, we have to check the principal id too.
635    @property
636    def link_target(self):
637        if self.request.principal.id == 'zope.anybody':
638            return self.view.application_url('enquiries')
639        return
640
641#
642# Administration tasks
643#
644class AdminTask(grok.Viewlet):
645    """The base for task entries on administration page.
646    """
647    grok.baseclass()
648    grok.order(1)
649    grok.viewletmanager(AdministrationTasks)
650    grok.require('waeup.manageUniversity')
651    grok.template('admintask')
652
653    link_title = 'Manage users' # How the link to the target will be titled.
654    target_viewname = 'users'   # The name of the target view.
655   
656    @property
657    def link_target(self):
658        return self.view.url(self.context[self.target_viewname])
659
660class AdminTaskPortalConfiguration(AdminTask):
661    """Entry on administration page that link to portal settings.
662    """
663    grok.order(1)
664    grok.require('waeup.managePortalConfiguration')
665
666    link_title = _('Portal Configuration')
667    def link_target(self):
668        return self.view.url(self.view.context, 'configuration')
669
670class AdminTaskUsers(AdminTask):
671    """Entry on administration page that link to user folder.
672    """
673    grok.order(2)
674    grok.require('waeup.manageUsers')
675
676    link_title = _('Portal Users')
677    target_viewname = 'users'
678   
679class AdminTaskDatacenter(AdminTask):
680    """Entry on administration page that link to datacenter.
681    """
682    grok.order(3)
683    grok.require('waeup.manageDataCenter')
684
685    link_title = _('Data Center')
686    target_viewname = 'datacenter'
687
688# The SubobjectLister and its viewlets below are not used in SIRP.
689
690class SubobjectLister(grok.ViewletManager):
691    """Very special viewlet manager that displays lists of subobjects.
692    """
693    grok.name('subobjectlist')
694    grok.template('subobjectlist')
695
696    def update(self):
697        # The default implementation of update() sets self.viewlets to
698        # a list of viewlets for the current context
699        # (self.context). We make use of that fact by retrieving all
700        # viewlets for all items in our context container by simply
701        # setting these items as context while we call the default
702        # update() method. So we get a list of lists of viewlets for
703        # each item in a 'row' (where a single item is a row).
704        rows = []
705        orig_context = self.context
706        for name, value in self.context.items():
707            # Retrieve all viewlets for the current item (not the context)
708            self.context = value
709            super(SubobjectLister, self).update() # sets self.viewlets
710            rows.append(self.viewlets)
711            self.context = orig_context
712        self.rows = rows
713        # Finally, set the viewlets we would retrieve normally...
714        super(SubobjectLister, self).update()
715        return
716
717
718class FacultiesContainerListHead(grok.Viewlet):
719    """The header line of faculty container subobject lists.
720    """
721    grok.order(1)
722    grok.viewletmanager(SubobjectLister)
723    grok.context(IFacultiesContainer)
724    grok.require('waeup.viewAcademics')
725
726    def render(self):
727        return u'<th>Code</th><th>Title</th><th></th>'
728
729class FacultyListName(grok.Viewlet):
730    """Display a the title of a faculty as link in a list.
731    """
732    grok.order(1)
733    grok.viewletmanager(SubobjectLister)
734    grok.context(IFaculty)
735    grok.require('waeup.viewAcademics')
736
737    def render(self):
738        return u'<a href="%s">%s</a>' % (
739            self.view.url(self.context), self.context.__name__)
740
741class FacultyListTitle(grok.Viewlet):
742    """Display the title of a faculty in a list.
743    """
744    grok.order(2)
745    grok.viewletmanager(SubobjectLister)
746    grok.context(IFaculty)
747    grok.require('waeup.viewAcademics')
748
749    def render(self):
750        return self.context.title
751
752class FacultyRemoveButton(grok.Viewlet):
753    """Render a remove button for faculty lists.
754    """
755    grok.order(3)
756    grok.viewletmanager(SubobjectLister)
757    grok.context(IFaculty)
758    grok.require('waeup.manageUniversity')
759
760    def render(self):
761        return u'<div class="text-right"><form method="POST">' + \
762            '<input class="text-right" type="submit" value="remove" />' + \
763            '</form></div>'
Note: See TracBrowser for help on using the repository browser.