Ignore:
Timestamp:
20 Sep 2012, 08:22:52 (12 years ago)
Author:
uli
Message:

Merge changes from trunk r9171:9207.

Location:
main/waeup.kofa/branches/uli-async-update
Files:
7 edited
3 copied

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/branches/uli-async-update

  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser.py

    r9166 r9208  
    9393
    9494    def title(self):
    95         co = self.context.getBedCoordinates()
     95        co = self.context.coordinates
    9696        return _('Block ${a}, Room ${b}, Bed ${c}',
    9797            mapping = {'a':co[1], 'b':co[2], 'c':co[3]})
     
    130130    form_fields = grok.AutoFields(IHostelsContainer)
    131131    taboneactions = [_('Save')]
    132     tabtwoactions = [_('Add hostel'), _('Remove selected')]
     132    tabtwoactions = [_('Add hostel'),
     133        _('Clear all hostels'),
     134        _('Remove selected')]
    133135
    134136    def update(self):
     
    164166        return
    165167
     168    @jsaction(_('Clear all hostels'))
     169    def clearHostels(self, **data):
     170        self.context.clearAllHostels()
     171        self.flash(_('All hostels cleared.'))
     172        write_log_message(self, 'all hostels cleared')
     173        self.redirect(self.url(self.context, '@@manage')+'?tab2')
     174        return
     175
    166176    @action(_('Save'), style='primary')
    167177    def save(self, **data):
     
    232242    tabtwoactions = [_('Update all beds'),
    233243        _('Switch reservation of selected beds'),
    234         _('Release selected beds')]
     244        _('Release selected beds'),
     245        _('Clear hostel')]
    235246    not_occupied = NOT_OCCUPIED
    236247
     
    243254        tabs.need()
    244255        datatable.need()
     256        warning.need()
    245257        self.tab1 = self.tab2 = ''
    246258        qs = self.request.get('QUERY_STRING', '')
     
    327339        return
    328340
     341    @jsaction(_('Clear hostel'))
     342    def clearHostel(self, **data):
     343        self.context.clearHostel()
     344        self.flash(_('Hostel cleared.'))
     345        write_log_message(self, 'cleared')
     346        self.redirect(self.url(self.context, '@@manage')+'?tab2')
     347        return
     348
    329349class BedManageFormPage(KofaEditFormPage):
    330350    """ View to edit bed data
     
    334354    grok.require('waeup.manageHostels')
    335355    form_fields = grok.AutoFields(IBedAllocateStudent).omit(
    336         'bed_id').omit('bed_number').omit('bed_type')
     356        'bed_id', 'bed_number', 'bed_type')
    337357    label = _('Allocate student')
    338358    pnav = 5
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/browser_templates/containerpage.pt

    r8685 r9208  
    2121      <th i18n:translate="">Id</th>
    2222      <th i18n:translate="">Name</th>
     23      <th i18n:translate="">Booked Beds</th>
    2324    </tr>
    2425  </thead>
    2526  <tbody>
    26     <tr tal:repeat="value context/values">
    27       <td> <a tal:attributes="href value/__name__">
    28           <span tal:content="value/hostel_id">ID</span></a></td>
    29       <td tal:content="value/hostel_name">NAME</td>
    30     </tr>
     27    <tal:tbody repeat="value context/values">
     28      <tr tal:define = "stats value/bed_statistics">
     29        <td> <a tal:attributes="href value/__name__">
     30            <span tal:content="value/hostel_id">ID</span></a></td>
     31        <td tal:content="value/hostel_name">NAME</td>
     32        <td>
     33          <span tal:replace="stats/booked">10</span> of <span tal:replace="stats/total">100</span>
     34        </td>
     35      </tr>
     36    </tal:tbody>
    3137  </tbody>
    3238</table>
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/container.py

    r8686 r9208  
    5151        return
    5252
     53    def clearAllHostels(self):
     54        """Clear all hostels.
     55        """
     56        for hostel in self.values():
     57            hostel.clearHostel()
     58        return
     59
    5360    logger_name = 'waeup.kofa.${sitename}.hostels'
    5461    logger_filename = 'hostels.log'
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/hostel.py

    r8685 r9208  
    2222from zope.event import notify
    2323from zope.component import getUtility
     24from zope.component.interfaces import IFactory
    2425from datetime import datetime
    2526from waeup.kofa.utils.helpers import attrs_to_fields
     
    4041        target = self.__name__
    4142        return grok.getSite()['hostels'].logger_info(ob_class,target,comment)
     43
     44    @property
     45    def bed_statistics(self):
     46        total = len(self.keys())
     47        booked = 0
     48        for value in self.values():
     49            if value.owner != NOT_OCCUPIED:
     50                booked += 1
     51        return {'booked':booked, 'total':total}
     52
     53    def clearHostel(self):
     54        """Remove all beds
     55        """
     56        keys = [i for i in self.keys()] # create deep copy
     57        for bed in keys:
     58            del self[bed]
     59        return
    4260
    4361    def addBed(self, bed):
     
    128146    grok.provides(IBed)
    129147
    130     def getBedCoordinates(self):
     148    @property
     149    def coordinates(self):
    131150        """Determine the coordinates from the bed_id.
    132151        """
    133152        return self.bed_id.split('_')
     153
     154    # The following property attributes are only needed
     155    # for the exporter to ease evaluation with  Excel.
     156
     157    @property
     158    def hall(self):
     159        return self.coordinates[0]
     160
     161    @property
     162    def block(self):
     163        return self.coordinates[1]
     164
     165    @property
     166    def room(self):
     167        return self.coordinates[2]
     168
     169    @property
     170    def bed(self):
     171        return self.coordinates[3]
     172
     173    @property
     174    def special_handling(self):
     175        return self.bed_type.split('_')[0]
     176
     177    @property
     178    def sex(self):
     179        return self.bed_type.split('_')[1]
     180
     181    @property
     182    def bt(self):
     183        return self.bed_type.split('_')[2]
     184
    134185
    135186    def bookBed(self, student_id):
     
    145196        """
    146197        sh, sex, bt = self.bed_type.split('_')
    147         hostel_id, block, room_nr, bed = self.getBedCoordinates()
     198        hostel_id, block, room_nr, bed = self.coordinates
    148199        hostel = self.__parent__
    149200        beds_for_fresh = getattr(hostel,'beds_for_fresh',[])
     
    221272Bed = attrs_to_fields(Bed)
    222273
     274class HostelFactory(grok.GlobalUtility):
     275    """A factory for hostels.
     276
     277    We need this factory for the hostel processor.
     278    """
     279    grok.implements(IFactory)
     280    grok.name(u'waeup.Hostel')
     281    title = u"Create a new hostel.",
     282    description = u"This factory instantiates new hostel instances."
     283
     284    def __call__(self, *args, **kw):
     285        return Hostel()
     286
     287    def getInterfaces(self):
     288        return implementedBy(Hostel)
     289
     290
    223291@grok.subscribe(IBedTicket, grok.IObjectRemovedEvent)
    224292def handle_bedticket_removed(bedticket, event):
     
    229297        bedticket.bed.owner = NOT_OCCUPIED
    230298        notify(grok.ObjectModifiedEvent(bedticket.bed))
     299
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/interfaces.py

    r9169 r9208  
    1717##
    1818from datetime import datetime
    19 from zope.interface import invariant, Invalid
     19from zope.interface import invariant, Invalid, Attribute
    2020from zope import schema
    2121from waeup.kofa.interfaces import (
     
    5858        )
    5959
     60    def clearAllHostels():
     61        """Clear all hostels.
     62        """
     63
    6064class IHostel(IKofaObject):
    6165    """A base representation of hostels.
     
    6367    """
    6468
     69    bed_statistics = Attribute('Number of booked and total beds')
     70
    6571    def loggerInfo(ob_class, comment):
    6672        """Adds an INFO message to the log file
     73        """
     74
     75    def clearHostel():
     76        """Remove all beds.
    6777        """
    6878
     
    189199    """
    190200
     201    coordinates = Attribute('The coordinates of the bed from bed_id')
     202
    191203    def loggerInfo(ob_class, comment):
    192204        """Adds an INFO message to the log file
    193205        """
    194206
    195     def getBedCoordinates():
    196         """Determine the coordinates from bed_id.
    197         """
    198207    def bookBed(student_id):
    199208        """Book a bed for a student.
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/hostels/tests.py

    r8686 r9208  
    1919Tests for hostels and their UI components.
    2020"""
     21import os
    2122import shutil
    2223import tempfile
     
    3637from waeup.kofa.hostels.container import HostelsContainer
    3738from waeup.kofa.hostels.hostel import Hostel, Bed
     39from waeup.kofa.hostels.batching import HostelProcessor
     40from waeup.kofa.hostels.export import BedExporter, HostelExporter
    3841from waeup.kofa.testing import (FunctionalLayer, FunctionalTestCase)
    3942from waeup.kofa.students.student import Student
    4043from waeup.kofa.students.accommodation import BedTicket
    4144from waeup.kofa.university.department import Department
     45
     46HOSTEL_SAMPLE_DATA = open(
     47    os.path.join(os.path.dirname(__file__), 'sample_hostel_data.csv'),
     48    'rb').read()
     49
     50HOSTEL_HEADER_FIELDS = HOSTEL_SAMPLE_DATA.split(
     51    '\n')[0].split(',')
    4252
    4353class HostelsContainerTestCase(FunctionalTestCase):
     
    142152        # Create a bed
    143153        bed = Bed()
    144         bed.bed_id = u'xyz'
     154        bed.bed_id = u'hall_block_room_bed'
    145155        bed.bed_number = 1
    146         bed.bed_type = u'abc'
     156        bed.bed_type = u'a_b_c'
    147157        self.app['hostels'][hostel.hostel_id][bed.bed_id] = bed
    148158
     
    157167        self.browser.handleErrors = False
    158168
     169        self.logfile = os.path.join(
     170            self.app['datacenter'].storage, 'logs', 'hostels.log')
     171
    159172    def tearDown(self):
    160173        super(HostelsFullSetup, self).tearDown()
     
    174187        # We can find a certain bed
    175188        cat = queryUtility(ICatalog, name='beds_catalog')
    176         results = cat.searchResults(bed_type=(u'abc', u'abc'))
     189        results = cat.searchResults(bed_type=(u'a_b_c', u'a_b_c'))
    177190        results = [x for x in results] # Turn results generator into list
    178191        assert len(results) == 1
    179         assert results[0] is self.app['hostels']['hall-x']['xyz']
     192        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
    180193
    181194    def test_search_by_owner(self):
    182195        # We can find a certain bed
    183         myobj = self.app['hostels']['hall-x']['xyz']
     196        myobj = self.app['hostels']['hall-x']['hall_block_room_bed']
    184197        myobj.owner = u'abc'
    185198        notify(grok.ObjectModifiedEvent(myobj))
     
    188201        results = [x for x in results] # Turn results generator into list
    189202        assert len(results) == 1
    190         assert results[0] is self.app['hostels']['hall-x']['xyz']
     203        assert results[0] is self.app['hostels']['hall-x']['hall_block_room_bed']
    191204
    192205class HostelsUITests(HostelsFullSetup):
     
    300313        self.assertMatches(
    301314          '...No allocated bed selected...', self.browser.contents)
    302         # Managers can manually allocate studenst after cancellation
     315        # Managers can manually allocate students after cancellation
    303316        self.browser.open(self.container_path + '/hall-1/hall-1_A_101_A')
    304317        self.browser.getControl(name="form.owner").value = [self.student_id]
     
    334347        # Also the number of the bed has changed.
    335348        self.assertFalse(new_number == old_number)
     349        # The number of occupied bed are displayed on container page.
     350        self.browser.open(self.container_path)
     351        self.assertTrue('1 of 8' in self.browser.contents)
    336352        # Remove entire hostel
    337353        self.browser.open(self.manage_container_path)
     
    346362        results = [x for x in results]
    347363        assert len(results) == 0
     364
     365    def test_clear_hostels(self):
     366        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     367        self.browser.open(self.container_path)
     368        self.browser.getLink("Manage accommodation").click()
     369        self.browser.getControl("Add hostel").click()
     370        self.browser.getControl("Create hostel").click()
     371        hall = self.app['hostels']['hall-1']
     372        hall.blocks_for_female = ['A','B']
     373        hall.rooms_per_floor = 1
     374        hall.beds_for_fresh = ['A']
     375        hall.beds_for_returning = ['B']
     376        hall.beds_for_final = ['C']
     377        hall.beds_for_all = ['D','E']
     378        self.browser.open(self.container_path + '/hall-1/manage')
     379        self.browser.getControl("Update all beds").click()
     380        cat = queryUtility(ICatalog, name='beds_catalog')
     381        results = cat.searchResults(bed_type=(None, None))
     382        self.assertEqual(len(results), 11)
     383        self.browser.getControl("Clear hostel").click()
     384        self.assertEqual(len(self.app['hostels']['hall-1']), 0)
     385        # Only the bed in hall-x remains in the catalog.
     386        results = cat.searchResults(bed_type=(None, None))
     387        self.assertEqual(len(results), 1)
     388        # We can clear all hostels at the same time.
     389        self.browser.open(self.manage_container_path)
     390        self.browser.getControl("Clear all hostels").click()
     391        results = cat.searchResults(bed_type=(None, None))
     392        self.assertEqual(len(results), 0)
     393        # Both actions have been logged.
     394        logcontent = open(self.logfile).read()
     395        self.assertTrue('INFO - zope.mgr - hostels.browser.HostelManageFormPage'
     396                        ' - hall-1 - cleared' in logcontent)
     397        self.assertTrue('zope.mgr - hostels.browser.HostelsContainerManagePage'
     398                        ' - hostels - all hostels cleared' in logcontent)
     399
     400class ExportTests(HostelsFullSetup):
     401
     402    layer = FunctionalLayer
     403
     404    def setUp(self):
     405        super(ExportTests, self).setUp()
     406        self.workdir = tempfile.mkdtemp()
     407        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
     408        return
     409
     410    def test_export_hostels(self):
     411        exporter = HostelExporter()
     412        exporter.export_all(self.app, self.outfile)
     413        result = open(self.outfile, 'rb').read()
     414        self.assertEqual(
     415            result,
     416            'beds_for_all,beds_for_final,beds_for_fresh,beds_for_pre,'
     417            'beds_for_returning,beds_reserved,blocks_for_female,'
     418            'blocks_for_male,floors_per_block,hostel_id,hostel_name,'
     419            'rooms_per_floor,sort_id,special_handling\r\n,,,,,[],,,1,'
     420            'hall-x,Hall 1,2,10,regular\r\n'
     421            )
     422        return
     423
     424    def test_export_beds(self):
     425        exporter = BedExporter()
     426        exporter.export_all(self.app, self.outfile)
     427        result = open(self.outfile, 'rb').read()
     428        self.assertEqual(
     429            result,
     430            'bed_id,bed_number,bed_type,owner,hall,block,room,bed,'
     431            'special_handling,sex,bt\r\nhall_block_room_bed,1,a_b_c,,'
     432            'hall,block,room,bed,a,b,c\r\n'
     433            )
     434        return
     435
     436class HostelProcessorTest(HostelsFullSetup):
     437
     438    layer = FunctionalLayer
     439
     440    def test_import(self):
     441        self.processor = HostelProcessor()
     442        self.workdir = tempfile.mkdtemp()
     443        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
     444        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
     445        num, num_warns, fin_file, fail_file = self.processor.doImport(
     446            self.csv_file, HOSTEL_HEADER_FIELDS)
     447        self.assertEqual(num_warns,0)
     448        self.assertEqual(len(self.app['hostels'].keys()), 11) # including hall-x
     449        self.assertEqual(self.app['hostels'][
     450            'block-a-upper-hostel'].hostel_id,'block-a-upper-hostel')
     451        self.assertEqual(self.app['hostels'][
     452            'block-a-upper-hostel'].beds_for_final, ['A', 'B'])
     453        logcontent = open(self.logfile).read()
     454        self.assertTrue(
     455            "Hostel Processor - block-a-upper-hostel - "
     456            "Record updated: beds_for_pre=['G'], floors_per_block=1, "
     457            "beds_for_final=['A', 'B'], rooms_per_floor=32, "
     458            "blocks_for_male=[], hostel_id=block-a-upper-hostel, "
     459            "sort_id=20, beds_for_returning=['C', 'D'], "
     460            "hostel_name=Block A Upper Hostel, beds_for_fresh=['E', 'F'], "
     461            "blocks_for_female=['A'], beds_for_all=[], beds_reserved=[]"
     462            in logcontent)
     463        shutil.rmtree(os.path.dirname(fin_file))
     464        return
Note: See TracChangeset for help on using the changeset viewer.