Changeset 9217 for main/waeup.kofa/trunk/src/waeup/kofa
- Timestamp:
- 21 Sep 2012, 11:21:05 (12 years ago)
- Location:
- main/waeup.kofa/trunk/src/waeup/kofa
- Files:
-
- 16 added
- 29 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/browser.py
r8387 r9217 23 23 from hurry.workflow.interfaces import InvalidTransitionError 24 24 from waeup.kofa.browser.resources import datatable 25 from waeup.kofa.browser import KofaPage, KofaAddFormPage, NullValidator25 from waeup.kofa.browser.layout import KofaPage, KofaAddFormPage, NullValidator 26 26 from waeup.kofa.browser.breadcrumbs import Breadcrumb 27 27 from waeup.kofa.browser.viewlets import ( -
main/waeup.kofa/trunk/src/waeup/kofa/app.py
r8846 r9217 18 18 import grok 19 19 from zope.authentication.interfaces import IAuthentication 20 from zope.component import getUtilit iesFor20 from zope.component import getUtility, getUtilitiesFor 21 21 from zope.component.interfaces import ObjectEvent 22 22 from zope.pluggableauth import PluggableAuthentication 23 23 from waeup.kofa.authentication import setup_authentication 24 24 from waeup.kofa.datacenter import DataCenter 25 from waeup.kofa.students.container import StudentsContainer26 from waeup.kofa.hostels.container import HostelsContainer27 25 from waeup.kofa.mandates.container import MandatesContainer 28 26 from waeup.kofa.interfaces import ( 29 IUniversity, IKofaPluggable, IObjectUpgradeEvent, ) 27 IUniversity, IKofaPluggable, IObjectUpgradeEvent, IJobManager, 28 VIRT_JOBS_CONTAINER_NAME) 30 29 from waeup.kofa.userscontainer import UsersContainer 31 30 from waeup.kofa.utils.logger import Logger … … 55 54 the like. 56 55 """ 56 from waeup.kofa.students.container import StudentsContainer 57 from waeup.kofa.hostels.container import HostelsContainer 58 57 59 self['users'] = UsersContainer() 58 60 self['datacenter'] = DataCenter() … … 69 71 plugin.setup(self, name, self.logger) 70 72 return 73 74 def traverse(self, name): 75 if name == VIRT_JOBS_CONTAINER_NAME: 76 return getUtility(IJobManager) 77 return None 71 78 72 79 def updatePlugins(self): -
main/waeup.kofa/trunk/src/waeup/kofa/applicants/applicant.py
r9121 r9217 29 29 from zope.schema.interfaces import RequiredMissing, ConstraintNotSatisfied 30 30 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState 31 from waeup.kofa.app import University32 31 from waeup.kofa.image import KofaImageFile 33 32 from waeup.kofa.imagestorage import DefaultFileStoreHandler 34 33 from waeup.kofa.interfaces import ( 35 34 IObjectHistory, IFileStoreHandler, IFileStoreNameChooser, IKofaUtils, 36 IExtFileStore, IPDF, IUserAccount )35 IExtFileStore, IPDF, IUserAccount, IUniversity) 37 36 from waeup.kofa.interfaces import MessageFactory as _ 38 37 from waeup.kofa.students.vocabularies import RegNumNotInSource … … 189 188 """A catalog indexing :class:`Applicant` instances in the ZODB. 190 189 """ 191 grok.site( University)190 grok.site(IUniversity) 192 191 grok.name('applicants_catalog') 193 192 grok.context(IApplicant) -
main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
r9178 r9217 38 38 INITIALIZED, STARTED, PAID, SUBMITTED, ADMITTED) 39 39 from waeup.kofa.browser import ( 40 KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,40 # KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage, 41 41 DEFAULT_PASSPORT_IMAGE_PATH) 42 from waeup.kofa.browser.layout import ( 43 KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage) 42 44 from waeup.kofa.browser.interfaces import ICaptchaManager 43 45 from waeup.kofa.browser.breadcrumbs import Breadcrumb -
main/waeup.kofa/trunk/src/waeup/kofa/applicants/interfaces.py
r9115 r9217 27 27 from zope.schema.interfaces import ( 28 28 ValidationError, ISource, IContextSourceBinder) 29 from waeup.kofa.browser.interfaces import IApplicantBase 29 30 from waeup.kofa.schema import TextLineChoice, FormattedDate 30 31 from waeup.kofa.interfaces import ( … … 311 312 'year'].order = IApplicantsContainer['year'].order 312 313 313 class IApplicantBaseData(I KofaObject):314 class IApplicantBaseData(IApplicantBase): 314 315 """The data for an applicant. 315 316 -
main/waeup.kofa/trunk/src/waeup/kofa/browser/__init__.py
r7819 r9217 1 1 import os 2 2 3 from waeup.kofa.browser.layout import (4 KofaPage, KofaForm, KofaLayout, KofaDisplayFormPage, KofaEditFormPage,5 KofaAddFormPage, NullValidator)6 from waeup.kofa.browser.pages import ContactAdminForm3 #from waeup.kofa.browser.layout import ( 4 # KofaPage, KofaForm, KofaLayout, KofaDisplayFormPage, KofaEditFormPage, 5 # KofaAddFormPage, NullValidator) 6 #from waeup.kofa.browser.pages import ContactAdminForm 7 7 8 8 IMAGE_PATH = os.path.join( -
main/waeup.kofa/trunk/src/waeup/kofa/browser/breadcrumbs.py
r7819 r9217 26 26 IConfigurationContainer, ISessionConfiguration) 27 27 from waeup.kofa.interfaces import MessageFactory as _ 28 from waeup.kofa.browser import interfaces 29 from waeup.kofa.browser.interfaces import (IBreadcrumb, 30 IBreadcrumbIgnorable, IBreadcrumbContainer) 28 from waeup.kofa.browser.interfaces import ( 29 IBreadcrumb, IBreadcrumbIgnorable, IBreadcrumbContainer, IKofaObject, 30 IUniversity, IFacultiesContainer, IUsersContainer, IDataCenter, IFaculty, 31 IDepartment, ICourse, ICertificate, ICoursesContainer, ICertificateCourse, 32 ICertificatesContainer, 33 ) 31 34 32 35 class Breadcrumb(grok.Adapter): … … 34 37 """ 35 38 grok.provides(IBreadcrumb) 36 grok.context( interfaces.IKofaObject)39 grok.context(IKofaObject) 37 40 grok.name('index') 38 41 … … 87 90 """A breadcrumb for university index pages. 88 91 """ 89 grok.context( interfaces.IUniversity)92 grok.context(IUniversity) 90 93 title = _(u'Home') 91 94 parent = None … … 100 103 itself is bound to. 101 104 """ 102 grok.context( interfaces.IUniversity)105 grok.context(IUniversity) 103 106 grok.name('manage') 104 107 title = _(u'Portal Settings') … … 113 116 """A breadcrumb for faculty containers. 114 117 """ 115 grok.context( interfaces.IFacultiesContainer)118 grok.context(IFacultiesContainer) 116 119 title = _(u'Academics') 117 120 … … 119 122 """A breadcrumb for administration areas of University instances. 120 123 """ 121 grok.context( interfaces.IUniversity)124 grok.context(IUniversity) 122 125 grok.name('administration') 123 126 title = _(u'Administration') … … 145 148 """A breadcrumb for user containers. 146 149 """ 147 grok.context( interfaces.IUsersContainer)150 grok.context(IUsersContainer) 148 151 title = _(u'Portal Users') 149 152 parent_viewname = 'administration' … … 152 155 """A breadcrumb for data centers. 153 156 """ 154 grok.context( interfaces.IDataCenter)157 grok.context(IDataCenter) 155 158 title = _(u'Data Center') 156 159 parent_viewname = 'administration' … … 159 162 """A breadcrumb for faculties. 160 163 """ 161 grok.context( interfaces.IFaculty)164 grok.context(IFaculty) 162 165 163 166 @property … … 168 171 """A breadcrumb for departments. 169 172 """ 170 grok.context( interfaces.IDepartment)173 grok.context(IDepartment) 171 174 172 175 class CourseBreadcrumb(FacultyBreadcrumb): 173 176 """A breadcrumb for courses. 174 177 """ 175 grok.context( interfaces.ICourse)178 grok.context(ICourse) 176 179 177 180 class CertificateBreadcrumb(FacultyBreadcrumb): 178 181 """A breadcrumb for certificates. 179 182 """ 180 grok.context( interfaces.ICertificate)183 grok.context(ICertificate) 181 184 182 185 class CoursesContainerBreadcrumb(Breadcrumb): 183 186 """ We don't want course container breadcrumbs. 184 187 """ 185 grok.context( interfaces.ICoursesContainer)188 grok.context(ICoursesContainer) 186 189 grok.implements(IBreadcrumbIgnorable) 187 190 … … 189 192 """ We don't want course container breadcrumbs. 190 193 """ 191 grok.context( interfaces.ICertificatesContainer)194 grok.context(ICertificatesContainer) 192 195 grok.implements(IBreadcrumbIgnorable) 193 196 … … 195 198 """ We don't want course container breadcrumbs. 196 199 """ 197 grok.context( interfaces.ICertificateCourse)200 grok.context(ICertificateCourse) 198 201 @property 199 202 def title(self): -
main/waeup.kofa/trunk/src/waeup/kofa/browser/captcha.py
r8127 r9217 28 28 from zope.interface import Interface 29 29 from zope.publisher.interfaces.http import IHTTPRequest 30 from waeup.kofa.browser import KofaPage, resources 30 from waeup.kofa.browser import resources 31 from waeup.kofa.browser.layout import KofaPage 31 32 from waeup.kofa.browser.interfaces import ( 32 33 ICaptchaRequest, ICaptchaResponse, ICaptcha, ICaptchaConfig, -
main/waeup.kofa/trunk/src/waeup/kofa/browser/interfaces.py
r8257 r9217 21 21 from zope.interface import Interface, Attribute 22 22 from waeup.kofa.interfaces import ( 23 IKofaObject, IUniversity, IUsersContainer, IDataCenter) 23 IKofaObject, IUniversity, IUsersContainer, IDataCenter, validate_email) 24 from waeup.kofa.interfaces import MessageFactory as _ 24 25 from waeup.kofa.university.interfaces import ( 25 26 IFacultiesContainer, IFaculty, IFacultyAdd, IDepartment, IDepartmentAdd, … … 196 197 If no `title` is given, nothing will be rendered. 197 198 """ 199 200 class IChangePassword(IKofaObject): 201 """Interface needed for change pasword page. 202 203 """ 204 identifier = schema.TextLine( 205 title = _(u'Unique Identifier'), 206 description = _( 207 u'User Name, Student or Applicant Id, Matriculation or ' 208 u'Registration Number'), 209 required = True, 210 readonly = False, 211 ) 212 213 email = schema.ASCIILine( 214 title = _(u'Email Address'), 215 required = True, 216 constraint=validate_email, 217 ) 218 219 class IStudentNavigationBase(IKofaObject): 220 """Objects that provide student navigation (whatever it is) should 221 implement this interface. 222 """ 223 student = Attribute('''Some student object that has a ''' 224 ''' `display_fullname` attribute.''') 225 226 class IApplicantBase(IKofaObject): 227 """Some Applicant. 228 """ 229 display_fullname = Attribute('''Fullname.''') -
main/waeup.kofa/trunk/src/waeup/kofa/browser/layout.py
r9088 r9217 36 36 from waeup.kofa.interfaces import MessageFactory as _ 37 37 from waeup.kofa.utils.helpers import to_timezone 38 from waeup.kofa.browser.interfaces import ITheme 38 from waeup.kofa.browser.interfaces import ( 39 ITheme, IStudentNavigationBase, IApplicantBase) 39 40 from waeup.kofa.browser.theming import get_all_themes, KofaThemeBase 40 from waeup.kofa.students.interfaces import IStudentNavigation41 from waeup.kofa.applicants.interfaces import IApplicant42 41 from waeup.kofa.authentication import get_principal_role_manager 43 42 … … 288 287 """Return the student name. 289 288 """ 290 if IStudentNavigation .providedBy(self.context):289 if IStudentNavigationBase.providedBy(self.context): 291 290 return self.context.student.display_fullname 292 291 return … … 295 294 """Return the applicant name. 296 295 """ 297 if IApplicant.providedBy(self.context): 296 if IApplicantBase.providedBy(self.context): 297 # XXX: shouldn't that be `display_fullname???` 298 298 return self.context.fullname 299 299 return -
main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py
r9172 r9217 18 18 """ Viewing components for Kofa objects. 19 19 """ 20 import copy21 20 import csv 22 21 import grok … … 24 23 import re 25 24 import sys 26 import time27 import re28 25 from urllib import urlencode 29 26 from zope import schema … … 35 32 getUtilitiesFor, 36 33 ) 37 #from zope.component.interfaces import Invalid38 34 from zope.event import notify 39 from zope.securitypolicy.interfaces import ( 40 IPrincipalRoleManager, IPrincipalRoleMap) 35 from zope.securitypolicy.interfaces import IPrincipalRoleManager 41 36 from zope.session.interfaces import ISession 42 37 from zope.password.interfaces import IPasswordManager 43 from waeup.kofa.browser import (38 from waeup.kofa.browser.layout import ( 44 39 KofaPage, KofaForm, KofaEditFormPage, KofaAddFormPage, 45 40 KofaDisplayFormPage, NullValidator) … … 48 43 IDepartment, IDepartmentAdd, ICourse, ICourseAdd, ICertificate, 49 44 ICertificateAdd, ICertificateCourse, ICertificateCourseAdd, 50 ICaptchaManager )45 ICaptchaManager, IChangePassword) 51 46 from waeup.kofa.browser.layout import jsaction, action, UtilityView 52 from waeup.kofa.browser.resources import warning, datepicker, tabs, datatable 47 from waeup.kofa.browser.resources import ( 48 warning, tabs, datatable) 53 49 from waeup.kofa.interfaces import MessageFactory as _ 54 50 from waeup.kofa.interfaces import( … … 56 52 IKofaXMLImporter, IKofaXMLExporter, IBatchProcessor, 57 53 ILocalRolesAssignable, DuplicationError, IConfigurationContainer, 58 ISessionConfiguration, ISessionConfigurationAdd, 59 IPasswordValidator, IContactForm, IKofaUtils, ICSVExporter, 60 IChangePassword) 54 ISessionConfiguration, ISessionConfigurationAdd, IJobManager, 55 IPasswordValidator, IContactForm, IKofaUtils, ICSVExporter,) 61 56 from waeup.kofa.permissions import ( 62 57 get_users_with_local_roles, get_all_roles, get_all_users) 58 63 59 from waeup.kofa.students.catalog import search as searchstudents 64 60 from waeup.kofa.university.catalog import search 65 61 from waeup.kofa.university.vocabularies import course_levels 66 62 from waeup.kofa.authentication import LocalRoleSetEvent 67 #from waeup.kofa.widgets.restwidget import ReSTDisplayWidget68 63 from waeup.kofa.widgets.htmlwidget import HTMLDisplayWidget 69 from waeup.kofa.authentication import get_principal_role_manager70 64 from waeup.kofa.utils.helpers import get_user_account 71 65 from waeup.kofa.mandates.mandate import PasswordMandate … … 797 791 def delFiles(self, **data): 798 792 form = self.request.form 799 logger = self.context.logger800 793 if form.has_key('val_id'): 801 794 child_id = form['val_id'] … … 1196 1189 return False 1197 1190 1198 @property1199 def nextstep(self):1200 return self.url(self.context, '@@import4')1201 1202 1191 def update(self, headerfield=None, back2=None, cancel=None, proceed=None): 1203 1192 datatable.need() … … 1390 1379 label = _('Download portal data as CSV file') 1391 1380 pnav = 0 1392 export_button = _(u'Download') 1381 export_button = _(u'Create CSV file') 1382 _running_exports = None 1393 1383 1394 1384 def getExporters(self): … … 1398 1388 return sorted(title_name_tuples) 1399 1389 1400 def update(self, export=None, exporter=None): 1401 if None in (export, exporter): 1402 return 1403 self.redirect( 1404 self.url(self.context, 'export.csv') + '?exporter=%s' % exporter) 1390 def job_finished(self, status): 1391 return status == 'completed' 1392 1393 def getRunningExports(self): 1394 """Returns running exports as list of tuples. 1395 1396 Only exports triggered by the current user (identified by 1397 principal.id) are returned. 1398 1399 Each tuple has the form (<STATUS>, <STATUS_TITLE>, <EXPORTER_NAME>). 1400 1401 ``STATUS``: 1402 the status as machine readable string (something like 1403 ``'completed'``) 1404 1405 ``STATUS_TITLE``: 1406 status of export as translated string. 1407 1408 ``EXPORTER_NAME``: 1409 string representing the exporter title used when triggering 1410 the export job. 1411 """ 1412 if self._running_exports is None: 1413 self._running_exports = self._getRunningExports() 1414 return self._running_exports 1415 1416 def _getRunningExports(self): 1417 result = self.context.get_export_jobs_status(self.user_id) 1418 return result 1419 1420 def update(self, export=None, start_export=None, exporter=None, 1421 discard=None, download=None): 1422 self.user_id = self.request.principal.id 1423 if discard: 1424 myjobs = self.context.get_running_export_jobs(self.user_id) 1425 for entry in myjobs: 1426 self.context.delete_export_entry(entry) 1427 self.flash(_('Discarded export result')) 1428 return 1429 if download: 1430 myjobs = self.context.get_running_export_jobs(self.user_id) 1431 if not len(myjobs): 1432 self.flash(_('This export was already discarded.')) 1433 return 1434 job_id = myjobs[0][0] 1435 self.redirect( 1436 self.url(self.context, 'export.csv') + '?job_id=%s' % job_id) 1437 return 1438 if None in (start_export, exporter): 1439 return 1440 job_id = self.context.start_export_job( 1441 exporter, self.request.principal.id) 1442 self.redirect(self.url(self.context, 'export')) 1405 1443 return 1406 1444 … … 1410 1448 grok.require('waeup.manageDataCenter') 1411 1449 1412 def render(self, exporter=None): 1413 if exporter is None: 1414 return 1415 ob_class = self.__implemented__.__name__.replace('waeup.kofa.','') 1416 self.context.logger.info( 1417 '%s - exported: %s' % (ob_class, exporter)) 1418 exporter = getUtility(ICSVExporter, name=exporter) 1419 csv_data = exporter.export_all(grok.getSite()) 1420 #csv_data.seek(0) 1450 def render(self, exporter=None, job_id=None): 1451 1452 manager = getUtility(IJobManager) 1453 job = manager.get(job_id) 1454 if job is None: 1455 return 1456 if hasattr(job.result, 'traceback'): 1457 # XXX: Some error happened. Do something more approriate here... 1458 return 1459 path = job.result 1460 if not os.path.exists(path): 1461 # XXX: Do something more appropriate here... 1462 return 1463 result = open(path, 'rb').read() 1464 acronym = grok.getSite()['configuration'].acronym.replace(' ','') 1465 filename = "%s_%s" % (acronym, os.path.basename(path)) 1421 1466 self.response.setHeader( 1422 1467 'Content-Type', 'text/csv; charset=UTF-8') 1423 acronym = grok.getSite()['configuration'].acronym.replace(' ','')1424 filename = "%s%s.csv" % (acronym, exporter.title.title().replace(' ',''))1425 1468 self.response.setHeader( 1426 'Content-Disposition:', 'attachment; filename="%s' % filename) 1427 return csv_data 1469 'Content-Disposition', 'attachment; filename="%s' % filename) 1470 # remove job and running_exports entry from context 1471 self.context.delete_export_entry( 1472 self.context.entry_from_job_id(job_id)) 1473 return result 1428 1474 1429 1475 class ExportXMLPage(grok.View): -
main/waeup.kofa/trunk/src/waeup/kofa/browser/resources.py
r8126 r9217 302 302 waeup_kofa, 'recaptcha_white.js' 303 303 ) 304 305 #: A page reloader (reloads after 3 secs) 306 page_reloader = ResourceInclusion( 307 waeup_kofa, 'page_reloader.js', depends=[jquery]) 308 309 #: Disables page reloader (which is triggered by a JavaScript timer). 310 page_not_reloader = ResourceInclusion( 311 waeup_kofa, 'page_not_reloader.js', depends=[jquery]) 312 313 loadbar = ResourceInclusion( 314 waeup_kofa, 'loadbar.js', depends=[jquery]) -
main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/datacenterexportpage.pt
r7974 r9217 1 1 <div i18n:domain="waeup.kofa" class="row"> 2 <div class="span4 columns">3 2 <div class="span8 columns"> 4 <p i18n:translate="">Here you can download parts of portal data.</p> 5 <p i18n:translate=""> 6 Please pick the type of objects you want to export from the 7 selection below. 8 </p> 9 </div> 3 <div class="span8 columns"> 4 <p i18n:translate=""> 5 Here you can create CSV files from parts of portal data. 6 </p> 7 <p i18n:translate=""> 8 Please pick the type of objects you want to export from the 9 selection below. 10 </p> 11 <p i18n:translate=""> 12 The file will be generated and then be made available for you 13 to download. You must download or discard any existing export 14 file before creating a new one. 15 </p> 16 </div> 10 17 11 <form method="POST">12 <fieldset>18 <form method="POST"> 19 <fieldset> 13 20 <div class="clearfix"> 14 <label for="exporter">Data Type:</label> 15 <div class="input"> 16 <select name="exporter"> 17 <span tal:repeat="items view/getExporters" tal:omit-tag=""> 18 <option 19 tal:define="name python: items[1]; title python: items[0]" 20 tal:attributes="value name"> 21 <span tal:replace="title">TITLE</span> 22 </option> 21 <label for="running_exports" 22 tal:condition="view/getRunningExports">Running Export:</label> 23 <div class="input span6"> 24 <div tal:repeat="items view/getRunningExports"> 25 <div> 26 Data type: 27 <span tal:content="python: items[2]">exporter</span><br /> 28 Status: 29 <span tal:content="python: items[1]">status</span><br /> 30 <br /><br /> 31 </div> 32 <div> 33 <span tal:condition="not: python: view.job_finished(items[0])"> 34 <img src="" 35 tal:attributes="src static/ajax-loader.gif" 36 alt="Loading..." 37 class="spinner" /> 38 <input type="submit" name="reload" value="Reload" 39 class="btn primary" /> 40 </span> 41 <div> 42 <span tal:condition="python: view.job_finished(items[0])"> 43 <input type="submit" name="download" value="Download" 44 class="btn primary" /> 45 <input type="submit" name="discard" value="Discard" 46 class="btn secondary" /> 47 </span> 48 </div> 49 </div> 50 </div> 51 52 <div> 53 <tal:loading_bar content="structure provider:loadingbar" /> 54 </div> 55 </div> 56 57 <div class="clearfix" 58 tal:condition="not: view/getRunningExports"> 59 <label for="exporter">Data Type:</label> 60 <div class="input"> 61 <select name="exporter"> 62 <span tal:repeat="items view/getExporters" tal:omit-tag=""> 63 <option 64 tal:define="name python: items[1]; title python: items[0]" 65 tal:attributes="value name"> 66 <span tal:replace="title">TITLE</span> 67 </option> 68 </span> 69 </select> 70 <span class="help-inline" i18n:translate=""> 71 Type of objects to export 23 72 </span> 24 </select> 25 <span class="help-inline" i18n:translate=""> 26 Type of objects to export 27 </span> 73 </div> 28 74 </div> 29 </div> 30 <div class="input"> 31 <input i18n:translate="" type="submit" class="btn primary" 32 name="export" tal:attributes="value view/export_button" /> 75 <div class="input" 76 tal:condition="not: view/getRunningExports"> 77 <input i18n:translate="" type="submit" class="btn primary" 78 name="start_export" 79 tal:attributes="value view/export_button" /> 80 </div> 33 81 </div> 34 82 </fieldset> -
main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py
r9034 r9217 21 21 import shutil 22 22 import tempfile 23 import pytz24 from datetime import datetime, timedelta25 from StringIO import StringIO26 23 import os 27 import grok 28 from zope.event import notify 29 from zope.component import createObject, queryUtility 24 from zc.async.testing import wait_for_result 25 from zope.component import createObject, getUtility 30 26 from zope.component.hooks import setSite, clearSite 31 from zope.catalog.interfaces import ICatalog32 27 from zope.security.interfaces import Unauthorized 33 from zope.securitypolicy.interfaces import IPrincipalRoleManager34 28 from zope.testbrowser.testing import Browser 35 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState36 29 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase 37 30 from waeup.kofa.app import University 31 from waeup.kofa.interfaces import IJobManager 32 from waeup.kofa.tests.test_async import FunctionalAsyncTestCase 38 33 from waeup.kofa.university.faculty import Faculty 39 34 from waeup.kofa.university.department import Department … … 106 101 shutil.rmtree(self.dc_root) 107 102 108 109 103 class DataCenterUITests(UniversitySetup): 110 104 # Tests for DataCenter class views and pages … … 149 143 return 150 144 151 def test_export(self): 145 146 class DataCenterUIExportTests(UniversitySetup, FunctionalAsyncTestCase): 147 # Tests for DataCenter class views and pages 148 149 layer = FunctionalLayer 150 151 def wait_for_export_job_completed(self): 152 # helper function waiting until the current export job is completed 153 manager = getUtility(IJobManager) 154 job_id = self.app['datacenter'].running_exports[0][0] 155 job = manager.get(job_id) 156 wait_for_result(job) 157 return job_id 158 159 def stored_in_datacenter(self, job_id): 160 # tell whether job_id is stored in datacenter's running jobs list 161 for entry in list(self.app['datacenter'].running_exports): 162 if entry[0] == job_id: 163 return True 164 return False 165 166 def test_export_start(self): 167 # we can trigger export file creation 152 168 self.browser.addHeader('Authorization', 'Basic mgr:mgrpw') 153 169 self.browser.open(self.datacenter_path) … … 156 172 self.browser.getLink("Export data").click() 157 173 self.browser.getControl(name="exporter").value = ['faculties'] 174 self.browser.getControl("Create CSV file").click() 175 self.assertEqual(self.browser.headers['Status'], '200 Ok') 176 return 177 178 def test_export_download(self): 179 # we can download a generated export result 180 self.test_export_start() 181 # while the export file is created, we get a reload button 182 # (or a loading bar if javascript is enabled)... 183 self.browser.getControl("Reload").click() 184 # ...which is displayed as long as the job is not finished. 185 # When the job is finished and we reload the page... 186 job_id = self.wait_for_export_job_completed() 187 try: 188 self.browser.getControl("Reload").click() 189 except LookupError: 190 # if the job completed very fast, we will get the download 191 # link immediately 192 pass 193 # ...we can download the result 158 194 self.browser.getControl("Download").click() 159 self.assertEqual(self.browser.headers['Status'], '200 Ok') 160 self.assertEqual(self.browser.headers['Content-Type'], 195 self.assertEqual(self.browser.headers['content-type'], 161 196 'text/csv; charset=UTF-8') 162 self.assert True ('WAeUP.KofaFaculties.csv' in163 self.browser.headers['content-disposition'])197 self.assertEqual(self.browser.headers['content-disposition'], 198 'attachment; filename="WAeUP.Kofa_faculties.csv') 164 199 self.assertEqual(self.browser.contents, 165 200 'code,title,title_prefix,users_with_local_roles\r\n' 166 201 'fac1,Unnamed Faculty,faculty,[]\r\n') 167 logfile = os.path.join( 168 self.app['datacenter'].storage, 'logs', 'datacenter.log') 169 logcontent = open(logfile).read() 170 self.assertTrue('zope.mgr - browser.pages.ExportCSVView - ' 171 'exported: faculties' in logcontent) 202 203 # after download, the job and the result file are removed 204 manager = getUtility(IJobManager) 205 result = manager.get(job_id) 206 self.assertEqual(result, None) 207 self.assertEqual(self.stored_in_datacenter(job_id), False) 208 #logfile = os.path.join( 209 # self.app['datacenter'].storage, 'logs', 'datacenter.log') 210 #logcontent = open(logfile).read() 211 #self.assertTrue('zope.mgr - browser.pages.ExportCSVView - ' 212 # 'exported: faculties' in logcontent) 213 return 214 215 def test_export_discard(self): 216 # we can discard a generated export result 217 self.test_export_start() 218 self.wait_for_export_job_completed() 219 self.browser.open(self.datacenter_path + '/@@export') 220 self.browser.getControl("Discard").click() 221 self.assertTrue('Discarded export result' in self.browser.contents) 172 222 return 173 223 -
main/waeup.kofa/trunk/src/waeup/kofa/catalog.py
r7819 r9217 19 19 """ 20 20 import grok 21 from grok import index22 from hurry.query import Eq23 21 from hurry.query.interfaces import IQuery 24 22 from hurry.query.query import Query … … 26 24 from zope.component import getUtility 27 25 from zope.intid.interfaces import IIntIds 28 29 from waeup.kofa.app import University30 26 from waeup.kofa.interfaces import IQueryResultItem 31 27 -
main/waeup.kofa/trunk/src/waeup/kofa/configure.zcml
r8889 r9217 3 3 xmlns:i18n="http://namespaces.zope.org/i18n"> 4 4 5 < !-- This will load and configure most packages we need. -->5 <include package="waeup.kofa" file="locales.zcml" /> 6 6 <include package="grok" /> 7 8 <!-- Meta configurations. -->9 <include package="zope.sendmail" file="meta.zcml" />10 7 <include package="waeup.kofa" file="meta.zcml" /> 11 12 <!-- Extra packages we have to configure --> 13 <include package="grokcore.message" /> 14 <include package="grokui.admin" /> 15 <include package="hurry.zoperesource" /> 16 <include package="megrok.layout" /> 17 <include package="zc.sourcefactory" /> 18 <include package="zope.app.authentication" /> 19 <include package="zope.sendmail" /> 8 <includeDependencies package="." /> 20 9 21 10 <!-- Comment the following line if you don't want dolmen.beaker, … … 26 15 be found by the includeDependencies directive. --> 27 16 <include package="dolmen.beaker" file="configure.zcml" /> 17 <grok:grok package="." /> 28 18 29 <include package="waeup.kofa" file="locales.zcml" /> 30 <grok:grok package="waeup.kofa" /> 31 <!-- The following configurations are large (or expected to become 32 large) and therefore 'outsourced' - the only ethical way to do 33 outsourcing ;-) 34 --> 19 <!-- include package="." file="async.zcml" / --> 35 20 <includeOverrides package="waeup.kofa.widgets" file="overrides.zcml" /> 36 21 <includeOverrides package="waeup.kofa.utils" file="overrides.zcml" /> -
main/waeup.kofa/trunk/src/waeup/kofa/datacenter.py
r9074 r9217 31 31 IDataCenterStorageMovedEvent, 32 32 IDataCenterConfig) 33 from waeup.kofa.utils.batching import ExportJobContainer 33 34 from waeup.kofa.utils.helpers import copy_filesystem_tree, merge_csv_files 34 35 from waeup.kofa.utils.logger import Logger … … 37 38 RE_LOGFILE_BACKUP_NAME = re.compile('^.+\.\d+$') 38 39 39 class DataCenter(grok.Container, Logger ):40 class DataCenter(grok.Container, Logger, ExportJobContainer): 40 41 """A data center contains CSV files. 41 42 """ -
main/waeup.kofa/trunk/src/waeup/kofa/ftesting.zcml
r7811 r9217 9 9 <includeOverrides package="waeup.kofa" /> 10 10 <include package="waeup.kofa" file="mail.zcml" /> 11 <include package="waeup.kofa" file="async_single.zcml" /> 11 12 12 13 <!-- Where should the datacenter reside by default? --> -
main/waeup.kofa/trunk/src/waeup/kofa/hostels/browser.py
r9199 r9217 22 22 from zope.i18n import translate 23 23 from zope.component import getUtility 24 from waeup.kofa.browser import (24 from waeup.kofa.browser.layout import ( 25 25 KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage, 26 26 NullValidator) -
main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py
r9115 r9217 19 19 import re 20 20 import codecs 21 import zc.async.interfaces 21 22 import zope.i18nmessageid 22 23 from datetime import datetime … … 29 30 from zope.component import getUtility 30 31 from zope.component.interfaces import IObjectEvent 31 from zope.container.interfaces import INameChooser 32 from zope.configuration.fields import Path 33 from zope.container.interfaces import INameChooser, IContainer 32 34 from zope.interface import Interface, Attribute 33 35 from zope.schema.interfaces import IObject … … 39 41 DELETION_MARKER = 'XXX' 40 42 IGNORE_MARKER = '<IGNORE>' 43 WAEUP_KEY = 'waeup.kofa' 44 VIRT_JOBS_CONTAINER_NAME = 'jobs' 41 45 42 46 CREATED = 'created' … … 49 53 REGISTERED = 'courses registered' 50 54 VALIDATED = 'courses validated' 55 56 #: A dict giving job status as tuple (<STRING>, <TRANSLATED_STRING>), 57 #: the latter for UI purposes. 58 JOB_STATUS_MAP = { 59 zc.async.interfaces.NEW: ('new', _('new')), 60 zc.async.interfaces.COMPLETED: ('completed', _('completed')), 61 zc.async.interfaces.PENDING: ('pending', _('pending')), 62 zc.async.interfaces.ACTIVE: ('active', _('active')), 63 zc.async.interfaces.ASSIGNED: ('assigned', _('assigned')), 64 zc.async.interfaces.CALLBACKS: ('callbacks', _('callbacks')), 65 } 51 66 52 67 #default_rest_frontpage = u'' + codecs.open(os.path.join( … … 1082 1097 """ 1083 1098 1084 from zope.configuration.fields import Path 1099 1085 1100 class IDataCenterConfig(Interface): 1086 1101 path = Path( … … 1091 1106 ) 1092 1107 1093 class IChangePassword(IKofaObject): 1094 """Interface needed for change pasword page. 1095 1096 """ 1097 identifier = schema.TextLine( 1098 title = _(u'Unique Identifier'), 1099 description = _( 1100 u'User Name, Student or Applicant Id, Matriculation or ' 1101 u'Registration Number'), 1102 required = True, 1103 readonly = False, 1104 ) 1105 1106 email = schema.ASCIILine( 1107 title = _(u'Email Address'), 1108 required = True, 1109 constraint=validate_email, 1110 ) 1108 # 1109 # Asynchronous job handling and related 1110 # 1111 class IJobManager(IKofaObject): 1112 """A manager for asynchronous running jobs (tasks). 1113 """ 1114 def put(job, site=None): 1115 """Put a job into task queue. 1116 1117 If no `site` is given, queue job in context of current local 1118 site. 1119 1120 Returns a job_id to identify the put job. This job_id is 1121 needed for further references to the job. 1122 """ 1123 1124 def jobs(site=None): 1125 """Get an iterable of jobs stored. 1126 """ 1127 1128 def get(job_id, site=None): 1129 """Get the job with id `job_id`. 1130 1131 For the `site` parameter see :meth:`put`. 1132 """ 1133 1134 def remove(job_id, site=None): 1135 """Remove job with `job_id` from stored jobs. 1136 """ 1137 1138 def start_test_job(site=None): 1139 """Start a test job. 1140 """ 1141 1142 class IProgressable(Interface): 1143 """A component that can indicate its progress status. 1144 """ 1145 percent = schema.Float( 1146 title = u'Percent of job done already.', 1147 ) 1148 1149 class IJobContainer(IContainer): 1150 """A job container contains IJob objects. 1151 """ 1152 1153 class IExportJob(zc.async.interfaces.IJob): 1154 def __init__(site, exporter_name): 1155 pass 1156 1157 class IExportJobContainer(Interface): 1158 """A component that contains (maybe virtually) export jobs. 1159 """ 1160 def start_export_job(exporter_name, user_id): 1161 """Start asynchronous export job. 1162 1163 `exporter_name` is the name of an exporter utility to be used. 1164 1165 `user_id` is the ID of the user that triggers the export. 1166 1167 The job_id is stored along with exporter name and user id in a 1168 persistent list. 1169 1170 Returns the job ID of the job started. 1171 """ 1172 1173 def get_running_export_jobs(user_id=None): 1174 """Get export jobs for user with `user_id` as list of tuples. 1175 1176 Each tuples holds ``<job_id>, <exporter_name>, <user_id>`` in 1177 that order. The ``<exporter_name>`` is the utility name of the 1178 used exporter. 1179 1180 If `user_id` is ``None``, all running jobs are returned. 1181 """ 1182 1183 def get_export_jobs_status(user_id=None): 1184 """Get running/completed export jobs for `user_id` as list of tuples. 1185 1186 Each tuple holds ``<raw status>, <status translated>, 1187 <exporter title>`` in that order, where ``<status 1188 translated>`` and ``<exporter title>`` are translated strings 1189 representing the status of the job and the human readable 1190 title of the exporter used. 1191 """ 1192 1193 def delete_export_entry(entry): 1194 """Delete the export denoted by `entry`. 1195 1196 Removes `entry` from the local `running_exports` list and also 1197 removes the regarding job via the local job manager. 1198 1199 `entry` is a tuple ``(<job id>, <exporter name>, <user id>)`` 1200 as created by :meth:`start_export_job` or returned by 1201 :meth:`get_running_export_jobs`. 1202 """ 1203 1204 def entry_from_job_id(job_id): 1205 """Get entry tuple for `job_id`. 1206 1207 Returns ``None`` if no such entry can be found. 1208 """ -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
r9204 r9217 33 33 invalidate_accesscode, get_access_code) 34 34 from waeup.kofa.accesscodes.workflow import USED 35 from waeup.kofa.browser import (35 from waeup.kofa.browser.layout import ( 36 36 KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage, 37 ContactAdminForm, KofaForm, NullValidator) 37 KofaForm, NullValidator) 38 from waeup.kofa.browser.pages import ContactAdminForm 38 39 from waeup.kofa.browser.breadcrumbs import Breadcrumb 39 40 from waeup.kofa.browser.resources import datepicker, datatable, tabs, warning -
main/waeup.kofa/trunk/src/waeup/kofa/students/catalog.py
r9170 r9217 63 63 self.state = context.state 64 64 self.translated_state = context.translated_state 65 #try: 66 # current_level = course_levels.getTerm( 67 # context['studycourse'].current_level).title 68 #except LookupError: 69 # current_level = None 70 #self.current_level = current_level 71 72 self.current_level = context['studycourse'].current_level 73 65 try: 66 current_level = course_levels.getTerm( 67 context['studycourse'].current_level).title 68 except LookupError: 69 current_level = None 70 self.current_level = current_level 74 71 try: 75 72 current_session = academic_sessions_vocab.getTerm( -
main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
r9182 r9217 21 21 from zope import schema 22 22 from zc.sourcefactory.contextual import BasicContextualSourceFactory 23 from waeup.kofa.browser.interfaces import IStudentNavigationBase 23 24 from waeup.kofa.interfaces import ( 24 25 IKofaObject, academic_sessions_vocab, validate_email, ICSVExporter) … … 131 132 unique_student_id = Attribute("""A unique student id.""") 132 133 133 class IStudentNavigation(I KofaObject):134 class IStudentNavigation(IStudentNavigationBase): 134 135 """Interface needed for student navigation, logging, etc. 135 136 -
main/waeup.kofa/trunk/src/waeup/kofa/tests/test_app.py
r7811 r9217 23 23 from zope.interface.verify import verifyClass, verifyObject 24 24 from waeup.kofa.app import University 25 from waeup.kofa.interfaces import IUniversity 25 from waeup.kofa.interfaces import ( 26 IUniversity, IJobManager, VIRT_JOBS_CONTAINER_NAME) 26 27 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase 27 28 … … 62 63 self.app.updatePlugins() 63 64 self.assertTrue('accesscodes' in self.app.keys()) 65 return 66 67 def test_jobs(self): 68 # We can get the global job manager when traversing to it... 69 result = self.app.traverse(VIRT_JOBS_CONTAINER_NAME) 70 self.assertTrue(IJobManager.providedBy(result)) 71 return -
main/waeup.kofa/trunk/src/waeup/kofa/tests/test_datacenter.py
r8725 r9217 10 10 from zope.interface.verify import verifyObject, verifyClass 11 11 from waeup.kofa.datacenter import DataCenter 12 from waeup.kofa.interfaces import IDataCenter, IDataCenterConfig 12 from waeup.kofa.interfaces import ( 13 IDataCenter, IDataCenterConfig, IExportJobContainer) 13 14 14 15 class DataCenterLogQueryTests(unittest.TestCase): … … 180 181 return 181 182 182 def test_iface (self):183 def test_ifaces(self): 183 184 # we comply with interfaces 184 185 obj = DataCenter() 185 186 verifyClass(IDataCenter, DataCenter) 187 verifyClass(IExportJobContainer, DataCenter) 186 188 verifyObject(IDataCenter, obj) 189 verifyObject(IExportJobContainer, obj) 187 190 return 188 191 -
main/waeup.kofa/trunk/src/waeup/kofa/university/course.py
r9170 r9217 21 21 from zope.catalog.interfaces import ICatalog 22 22 from zope.interface import implementedBy 23 from zope.schema import getFields24 from zope.intid.interfaces import IIntIds25 23 from zope.component import getUtility 26 24 from zope.component.interfaces import IFactory, ComponentLookupError 27 from waeup.kofa.interfaces import IKofaPluggable28 25 from waeup.kofa.university.interfaces import ICourse, ICourseAdd 29 26 … … 40 37 credits=0, 41 38 passmark=40, 42 semester=1, 43 former_course=False, 44 **kw): 39 semester=1, **kw): 45 40 super(Course, self).__init__(**kw) 46 41 self.title = title … … 49 44 self.passmark = passmark 50 45 self.semester = semester 51 self.former_course = former_course52 46 53 47 def longtitle(self): … … 89 83 cert._p_changed = True 90 84 return 91 92 class CoursesPlugin(grok.GlobalUtility):93 """A plugin that updates courses.94 """95 96 grok.implements(IKofaPluggable)97 grok.name('courses')98 99 deprecated_attributes = []100 101 def setup(self, site, name, logger):102 return103 104 def update(self, site, name, logger):105 cat = getUtility(ICatalog, name='courses_catalog')106 results = cat.apply({'code':(None,None)})107 uidutil = getUtility(IIntIds, context=cat)108 items = getFields(ICourse).items()109 for r in results:110 o = uidutil.getObject(r)111 # Add new attributes112 for i in items:113 if not hasattr(o,i[0]):114 setattr(o,i[0],i[1].missing_value)115 logger.info(116 'CoursesPlugin: %s attribute %s added.' % (117 o.code,i[0]))118 # Remove deprecated attributes119 for i in self.deprecated_attributes:120 try:121 delattr(o,i)122 logger.info(123 'CoursesPlugin: %s attribute %s deleted.' % (124 o.code,i))125 except AttributeError:126 pass127 return -
main/waeup.kofa/trunk/src/waeup/kofa/university/interfaces.py
r9170 r9217 168 168 ) 169 169 170 former_course = schema.Bool(171 title = _(u'Former Course'),172 description = _(173 u'If this attribute is being set all certificate courses '174 'referring to this course will be automatically deleted.'),175 required = False,176 default = False,177 )178 179 170 def longtitle(): 180 171 """ -
main/waeup.kofa/trunk/src/waeup/kofa/utils/batching.py
r9170 r9217 25 25 import datetime 26 26 import os 27 import shutil 27 28 import tempfile 28 29 import time 29 30 from cStringIO import StringIO 30 from zope.component import createObject 31 from persistent.list import PersistentList 32 from zope.component import createObject, getUtility 33 from zope.component.hooks import setSite 31 34 from zope.interface import Interface 32 35 from zope.schema import getFields 33 36 from zope.event import notify 37 from waeup.kofa.async import AsyncJob 34 38 from waeup.kofa.interfaces import ( 35 IBatchProcessor, FatalCSVError, IObjectConverter, 36 ICSVExporter, IGNORE_MARKER, DuplicationError) 39 IBatchProcessor, FatalCSVError, IObjectConverter, IJobManager, 40 ICSVExporter, IGNORE_MARKER, DuplicationError, JOB_STATUS_MAP, 41 IExportJobContainer, IExportJob) 37 42 38 43 class BatchProcessor(grok.GlobalUtility): … … 361 366 self.writeFailedRow( 362 367 failed_writer, string_row, 363 "Cannot remove: no such entry ")368 "Cannot remove: no such entry.") 364 369 continue 365 370 self.delEntry(row, site) … … 498 503 """ 499 504 raise NotImplementedError 505 506 507 def export_job(site, exporter_name): 508 """Export all entries delivered by exporter and store it in a temp file. 509 510 `site` gives the site to search. It will be passed to the exporter 511 and also be set as 'current site' as the function is used in 512 asynchronous jobs which run in their own threads and have no site 513 set initially. Therefore `site` must also be a valid value for use 514 with `zope.component.hooks.setSite()`. 515 516 `exporter_name` is the utility name under which the desired 517 exporter was registered with the ZCA. 518 519 The resulting CSV file will be stored in a new temporary directory 520 (using :func:`tempfile.mkdtemp`). It will be named after the 521 exporter used with `.csv` filename extension. 522 523 Returns the path to the created CSV file. 524 525 .. note:: It is the callers responsibility to clean up the used 526 file and its parent directory. 527 """ 528 setSite(site) 529 exporter = getUtility(ICSVExporter, name=exporter_name) 530 output_dir = tempfile.mkdtemp() 531 filename = '%s.csv' % exporter_name 532 output_path = os.path.join(output_dir, filename) 533 exporter.export_all(site, filepath=output_path) 534 return output_path 535 536 class AsyncExportJob(AsyncJob): 537 """An IJob that exports data to CSV files. 538 539 `AsyncExportJob` instances are regular `AsyncJob` instances with a 540 different constructor API. Instead of a callable to execute, you 541 must pass a `site` and some `exporter_name` to trigger an export. 542 543 The real work is done when an instance of this class is put into a 544 queue. See :mod:`waeup.kofa.async` to learn more about 545 asynchronous jobs. 546 547 The `exporter_name` must be the name under which an ICSVExporter 548 utility was registered with the ZCA. 549 550 The `site` must be a valid site or ``None``. 551 552 The result of an `AsyncExportJob` is the path to generated CSV 553 file. The file will reside in a temporary directory that should be 554 removed after being used. 555 """ 556 grok.implements(IExportJob) 557 558 def __init__(self, site, exporter_name): 559 super(AsyncExportJob, self).__init__( 560 export_job, site, exporter_name) 561 562 class ExportJobContainer(object): 563 """A mix-in that provides functionality for asynchronous export jobs. 564 """ 565 grok.implements(IExportJobContainer) 566 running_exports = PersistentList() 567 568 def start_export_job(self, exporter_name, user_id): 569 """Start asynchronous export job. 570 571 `exporter_name` is the name of an exporter utility to be used. 572 573 `user_id` is the ID of the user that triggers the export. 574 575 The job_id is stored along with exporter name and user id in a 576 persistent list. 577 578 Returns the job ID of the job started. 579 """ 580 site = grok.getSite() 581 manager = getUtility(IJobManager) 582 job = AsyncExportJob(site, exporter_name) 583 job_id = manager.put(job) 584 # Make sure that the persisted list is stored in ZODB 585 self.running_exports = PersistentList(self.running_exports) 586 self.running_exports.append((job_id, exporter_name, user_id)) 587 return job_id 588 589 def get_running_export_jobs(self, user_id=None): 590 """Get export jobs for user with `user_id` as list of tuples. 591 592 Each tuples holds ``<job_id>, <exporter_name>, <user_id>`` in 593 that order. The ``<exporter_name>`` is the utility name of the 594 used exporter. 595 596 If `user_id` is ``None``, all running jobs are returned. 597 """ 598 entries = [] 599 to_delete = [] 600 manager = getUtility(IJobManager) 601 for entry in self.running_exports: 602 if user_id is not None and entry[2] != user_id: 603 continue 604 if manager.get(entry[0]) is None: 605 to_delete.append(entry) 606 continue 607 entries.append(entry) 608 if to_delete: 609 self.running_exports = PersistentList( 610 [x for x in self.running_exports if x not in to_delete]) 611 return entries 612 613 def get_export_jobs_status(self, user_id=None): 614 """Get running/completed export jobs for `user_id` as list of tuples. 615 616 Each tuple holds ``<raw status>, <status translated>, 617 <exporter title>`` in that order, where ``<status 618 translated>`` and ``<exporter title>`` are translated strings 619 representing the status of the job and the human readable 620 title of the exporter used. 621 """ 622 entries = self.get_running_export_jobs(user_id) 623 result = [] 624 manager = getUtility(IJobManager) 625 for entry in entries: 626 job = manager.get(entry[0]) 627 if job is None: 628 continue 629 status, status_translated = JOB_STATUS_MAP[job.status] 630 exporter_name = getUtility(ICSVExporter, name=entry[1]).title 631 result.append((status, status_translated, exporter_name)) 632 return result 633 634 def delete_export_entry(self, entry): 635 """Delete the export denoted by `entry`. 636 637 Removes given entry from the local `running_exports` list and also 638 removes the regarding job via the local job manager. 639 640 `entry` must be a tuple ``(<job id>, <exporter name>, <user 641 id>)`` as created by :meth:`start_export_job` or returned by 642 :meth:`get_running_export_jobs`. 643 """ 644 manager = getUtility(IJobManager) 645 job = manager.get(entry[0]) 646 if job is not None: 647 # remove created export file 648 if isinstance(job.result, basestring): 649 if os.path.exists(os.path.dirname(job.result)): 650 shutil.rmtree(os.path.dirname(job.result)) 651 manager.remove(entry[0], self) 652 new_entries = [x for x in self.running_exports 653 if x != entry] 654 self.running_exports = PersistentList(new_entries) 655 return 656 657 def entry_from_job_id(self, job_id): 658 """Get entry tuple for `job_id`. 659 660 Returns ``None`` if no such entry can be found. 661 """ 662 for entry in self.running_exports: 663 if entry[0] == job_id: 664 return entry 665 return None -
main/waeup.kofa/trunk/src/waeup/kofa/utils/tests/test_batching.py
r8380 r9217 23 23 import tempfile 24 24 import unittest 25 from zc.async.interfaces import IJob, COMPLETED 25 26 from zope import schema 26 from zope.component import provideUtility 27 from zope.component import provideUtility, getGlobalSiteManager 27 28 from zope.component.factory import Factory 28 29 from zope.component.hooks import clearSite … … 30 31 from zope.interface import Interface, implements, verify 31 32 from waeup.kofa.app import University 32 from waeup.kofa.interfaces import ICSVExporter, IBatchProcessor 33 from waeup.kofa.interfaces import ( 34 ICSVExporter, IBatchProcessor, IExportJobContainer, IJobManager, 35 IExportJob) 33 36 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase 34 from waeup.kofa.utils.batching import ExporterBase 37 from waeup.kofa.utils.batching import ( 38 ExporterBase, BatchProcessor, export_job, AsyncExportJob, 39 ExportJobContainer) 35 40 36 41 optionflags = ( … … 71 76 self.owner = owner 72 77 self.taxpayer = taxpayer 73 #Cave = attrs_to_fields(Cave)74 78 75 79 stoneville = dict 76 80 77 from waeup.kofa.utils.batching import BatchProcessor 81 SAMPLE_DATA = """name,dinoports,owner,taxpayer 82 Barneys Home,2,Barney,1 83 Wilmas Asylum,1,Wilma,1 84 Freds Dinoburgers,10,Fred,0 85 Joeys Drive-in,110,Joey,0 86 """ 87 78 88 class CaveProcessor(BatchProcessor): 79 89 util_name = 'caveprocessor' 80 #grok.name(util_name)81 90 name = 'Cave Processor' 82 91 iface = ICave … … 136 145 # Provide sample data 137 146 self.newcomers_csv = os.path.join(self.workdir, 'newcomers.csv') 138 open(self.newcomers_csv, 'wb').write( 139 """name,dinoports,owner,taxpayer 140 Barneys Home,2,Barney,1 141 Wilmas Asylum,1,Wilma,1 142 Freds Dinoburgers,10,Fred,0 143 Joeys Drive-in,110,Joey,0 144 """) 147 open(self.newcomers_csv, 'wb').write(SAMPLE_DATA) 145 148 self.setupLogger() 146 149 self.stoneville = stoneville … … 336 339 self.assertEqual(result, None) 337 340 return 341 342 343 class CaveExporter(ExporterBase): 344 # A minimal fake exporter suitable to be called by export_jobs 345 fields = ('name', 'dinoports', 'owner', 'taxpayer') 346 title = u'Dummy cave exporter' 347 348 def export_all(self, site, filepath=None): 349 if filepath is None: 350 return SAMPLE_DATA 351 open(filepath, 'wb').write(SAMPLE_DATA) 352 return 353 354 class ExportJobTests(unittest.TestCase): 355 # Test asynchronous export functionality (simple cases) 356 357 def setUp(self): 358 # register a suitable ICSVExporter as named utility 359 self.exporter = CaveExporter() 360 self.gsm = getGlobalSiteManager() 361 self.gsm.registerUtility( 362 self.exporter, ICSVExporter, name='cave_exporter') 363 364 def tearDown(self): 365 self.gsm.unregisterUtility(self.exporter) 366 367 def test_export_job_func(self): 368 # the export_job func does really export data... 369 result_path = export_job(None, 'cave_exporter') 370 self.assertTrue(os.path.isfile(result_path)) 371 contents = open(result_path, 'rb').read() 372 shutil.rmtree(os.path.dirname(result_path)) 373 self.assertEqual(contents, SAMPLE_DATA) 374 return 375 376 def test_export_job_interfaces(self): 377 # the AsyncExportJob implements promised interfaces correctly... 378 job = AsyncExportJob(None, None) 379 verify.verifyClass(IJob, AsyncExportJob) 380 verify.verifyObject(IJob, job) 381 verify.verifyClass(IExportJob, AsyncExportJob) 382 verify.verifyObject(IExportJob, job) 383 return 384 385 386 class FakeJob(object): 387 388 status = COMPLETED 389 result = None 390 391 class FakeJobWithResult(FakeJob): 392 393 def __init__(self): 394 dir_path = tempfile.mkdtemp() 395 self.result = os.path.join(dir_path, 'fake.csv') 396 open(self.result, 'wb').write('a fake result') 397 return 398 399 class FakeJobManager(object): 400 401 _jobs = dict() 402 _curr_num = 1 403 404 def get(self, job_id): 405 if job_id == '3': 406 return FakeJob() 407 return self._jobs.get(job_id, None) 408 409 def put(self, job): 410 num = str(self._curr_num) 411 self._jobs[num] = job 412 self._curr_num += 1 413 return num 414 415 def remove(self, job_id, site): 416 if job_id in self._jobs: 417 del self._jobs[job_id] 418 return 419 420 class ExportJobContainerTests(unittest.TestCase): 421 # Test ExportJobContainer 422 423 def setUp(self): 424 # register a suitable ICSVExporter as named utility 425 self.exporter = CaveExporter() 426 self.job_manager = FakeJobManager() 427 self.gsm = getGlobalSiteManager() 428 self.gsm.registerUtility( 429 self.exporter, ICSVExporter, name='cave_exporter') 430 self.gsm.registerUtility( 431 self.job_manager, IJobManager) 432 433 def tearDown(self): 434 self.gsm.unregisterUtility(self.exporter) 435 self.gsm.unregisterUtility(self.job_manager, IJobManager) 436 437 def test_export_job_interfaces(self): 438 # the ExportJobContainer implements promised interfaces correctly... 439 container = ExportJobContainer() 440 verify.verifyClass(IExportJobContainer, ExportJobContainer) 441 verify.verifyObject(IExportJobContainer, container) 442 return 443 444 def test_start_export_job(self): 445 # we can start jobs 446 container = ExportJobContainer() 447 container.start_export_job('cave_exporter', 'bob') 448 result = self.job_manager._jobs.values()[0] 449 self.assertTrue(IJob.providedBy(result)) 450 self.assertEqual( 451 container.running_exports, 452 [('1', 'cave_exporter', 'bob')] 453 ) 454 return 455 456 def test_get_running_export_jobs_all(self): 457 # we can get export jobs of all users 458 container = ExportJobContainer() 459 container.start_export_job('cave_exporter', 'bob') 460 container.start_export_job('cave_exporter', 'alice') 461 result = container.get_running_export_jobs() 462 self.assertEqual( 463 result, 464 [('1', 'cave_exporter', 'bob'), 465 ('2', 'cave_exporter', 'alice')] 466 ) 467 return 468 469 def test_get_running_export_jobs_user(self): 470 # we can get the export jobs running for a certain user 471 container = ExportJobContainer() 472 container.start_export_job('cave_exporter', 'bob') 473 container.start_export_job('cave_exporter', 'alice') 474 result1 = container.get_running_export_jobs(user_id='alice') 475 result2 = container.get_running_export_jobs(user_id='foo') 476 self.assertEqual( 477 result1, [('2', 'cave_exporter', 'alice')]) 478 self.assertEqual( 479 result2, []) 480 return 481 482 def test_get_running_export_jobs_only_if_exist(self): 483 # we get only jobs that are accessible through the job manager... 484 container = ExportJobContainer() 485 container.start_export_job('cave_exporter', 'bob') 486 container.start_export_job('cave_exporter', 'bob') 487 self.assertTrue( 488 ('2', 'cave_exporter', 'bob') in container.running_exports) 489 # we remove the second entry from job manager 490 del self.job_manager._jobs['2'] 491 result = container.get_running_export_jobs(user_id='bob') 492 self.assertEqual( 493 result, [('1', 'cave_exporter', 'bob')]) 494 self.assertTrue( 495 ('2', 'cave_exporter', 'bob') not in container.running_exports) 496 return 497 498 def test_get_export_job_status(self): 499 # we can get the stati of jobs... 500 container = ExportJobContainer() 501 container.start_export_job('cave_exporter', 'alice') 502 container.start_export_job('cave_exporter', 'bob') 503 container.start_export_job('cave_exporter', 'bob') 504 result = container.get_export_jobs_status(user_id='bob') 505 # we'll get the raw value, a translation and the title of the 506 # exporter 507 self.assertEqual( 508 result, 509 [('new', u'new', u'Dummy cave exporter'), 510 ('completed', u'completed', u'Dummy cave exporter')] 511 ) 512 return 513 514 def test_delete_export_entry(self): 515 # we can remove export entries in local lists and the job 516 # manager as well... 517 container = ExportJobContainer() 518 container.start_export_job('cave_exporter', 'bob') 519 entry = container.running_exports[0] 520 container.delete_export_entry(entry) 521 # both, running_exports list and job manager are empty now 522 self.assertEqual( 523 container.running_exports, []) 524 self.assertEqual( 525 self.job_manager._jobs, {}) 526 return 527 528 def test_delete_export_entry_remove_file(self): 529 # any result files of exports are deleted as well 530 container = ExportJobContainer() 531 entry = ('4', 'cave_exporter', 'bob') 532 container.running_exports = [entry] 533 fake_job = FakeJobWithResult() 534 self.job_manager._jobs['4'] = fake_job 535 self.assertTrue(os.path.isfile(fake_job.result)) 536 container.delete_export_entry(entry) 537 self.assertTrue(not os.path.exists(fake_job.result)) 538 return 539 540 def test_entry_from_job_id(self): 541 # we can get an entry for a job_id if the id exists 542 container = ExportJobContainer() 543 entry = ('4', 'cave_exporter', 'bob') 544 container.running_exports = [entry] 545 fake_job = FakeJobWithResult() 546 self.job_manager._jobs['4'] = fake_job 547 result1 = container.entry_from_job_id(None) 548 result2 = container.entry_from_job_id('4') 549 result3 = container.entry_from_job_id('23') 550 self.assertEqual(result1, None) 551 self.assertEqual(result2, ('4', 'cave_exporter', 'bob')) 552 self.assertEqual(result3, None) 553 return
Note: See TracChangeset for help on using the changeset viewer.