UI Components for Access Codes
******************************

.. module:: waeup.kofa.accesscodes.browser

Here we visit the access-code related parts of a Kofa site using
a virtual browser.

Preliminaries
=============

Before we can do anything, we have to create a university site:

    >>> from zope.testbrowser.testing import Browser
    >>> browser = Browser()
    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    >>> browser.handleErrors = False
    >>> root = getRootFolder()

    >>> from waeup.kofa.app import University
    >>> u = University()
    >>> root['myuniversity'] = u

We set a new datacenter storage path:

  >>> import os
  >>> browser.open('http://localhost/myuniversity')
  >>> browser.getLink('Data Center').click()
  >>> browser.getLink('Edit settings').click()
  >>> pathsetting = browser.getControl(name='newpath')

  >>> cwd = os.getcwd()
  >>> uploadpath = os.path.join(cwd, 'ac_testfiles')
  >>> os.mkdir(uploadpath)
  >>> pathsetting.value = uploadpath
  >>> browser.getControl(name='save').click()

We remove any existing 'accesscodes' dir from datacenter dir:

  >>> import shutil
  >>> if os.path.exists(os.path.join(uploadpath, 'accesscodes')):
  ...   shutil.rmtree(os.path.join(uploadpath, 'accesscodes'))


Access Code Management Screen
=============================

For users that have the right to manage access-code related stuff, the
home page of a `University` instance provides a link to the
access-code management screen. In the beginning, there are already
three empty batches available which will be filled by online payments:

    >>> browser.open('http://localhost/myuniversity')
    >>> browser.getLink('Access Codes').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...
    ...<h1 class="kofa-content-label">Access Code Batches</h1>
    ...
    ... The following batches are available:
    ...CLR...
    ...HOS...
    ...SFE...
    ...

Adding Batches
==============

We can add a batch of access-codes using a button displayed in the
action bar:

    >>> browser.getLink('Add Access Code Batch').click()

The add screen shows a form where we have to enter a prefix, the
number of access codes to be generated and the costs for each card.

    >>> browser.getControl(name='form.prefix').value = 'APP'
    >>> browser.getControl(name='form.entry_num').value = '5'
    >>> browser.getControl(name='form.cost').value = '12.12'

If we click 'cancel' afterwards, the whole process will be cancelled
and we'll be redirected to the management screen:

    >>> browser.getControl('Cancel').click()
    >>> browser.url
    'http://localhost/myuniversity/accesscodes'

    >>> 'Batch creation cancelled' in browser.contents
    True

Now let's try again and this time we finish the procedure by clicking
'Create batch' in the form:

    >>> browser.getLink('Add Access Code Batch').click()
    >>> browser.getControl(name='form.prefix').value = 'APP'
    >>> browser.getControl(name='form.entry_num').value = '5'
    >>> browser.getControl(name='form.cost').value = '12.12'
    >>> browser.getControl('Create batch').click()

We're also redirected to the management screen, with a notice about
the freshly created batch:

    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...
    ...<h1 class="kofa-content-label">Access Code Batches</h1>
    ...
    ... The following batches are available:
    ...APP
    ...-
    ...1
    ...
    ...5
    ...
    ...0
    ...
    ...12.12...
    ...zope.mgr...
    ...

which means: there exists a batch named ``APP-1`` with 5 entries of
which 0 have been devalidated, each one costs ``12.12`` and the batch
was created by ``zope.mgr``.

We create a second batch to see whether searching and related stuff
works:

    >>> browser.getLink('Add Access Code Batch').click()
    >>> browser.getControl(name='form.prefix').value = 'APP'
    >>> browser.getControl(name='form.entry_num').value = '5'
    >>> browser.getControl(name='form.cost').value = '10.12'
    >>> browser.getControl('Create batch').click()

And a third one that can be deleted afterwards:

    >>> browser.getLink('Add Access Code Batch').click()
    >>> browser.getControl(name='form.prefix').value = 'BLA'
    >>> browser.getControl(name='form.entry_num').value = '3'
    >>> browser.getControl(name='form.cost').value = '19.12'
    >>> browser.getControl('Create batch').click()


Creating Archive Files
======================

Once a batch is created, we can archive it. To do so we have to tick
the respective checkbox and click on 'Archive':

    >>> ctrl = browser.getControl(name='batches')
    >>> ctrl.getControl(value='APP-2').selected = True
    >>> browser.getControl(name='archive').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...
    <div ...>Archived APP-2 (APP-2_archive-...-zope.mgr.csv)</div>
    ...

If we do not select a batch and try to archive or delete, the system
will complain:

    >>> browser.getControl(name='archive').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...
    <div ...>No batch selected.</div>
    ...

Deleting Batches
================

We can delete batches. They are automatically archived when doing so:

    >>> ctrl = browser.getControl(name='batches')
    >>> ctrl.getControl(value='BLA-1').selected = True
    >>> browser.getControl('Archive and delete').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...Archived BLA-1 (BLA-1_archive-...-zope.mgr.csv)...
    ...Deleted batch BLA-1...
    ...


Reimporting Batches
===================

We can reimport batches using the log files written when a batch was
created before. So one can reimport the freshly deleted BLA-1
batch.

To do so we must copy the logfile into the ``imports`` dir of
accesscodes inside the university's datacenter storage. Otherwisae the
list of importable files is empty:

    >>> browser.getLink('Reimport Access Code Batch').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...
    ...No import batches available...
    ...

We can cancel that operation:

    >>> browser.getControl('Cancel').click()

We copy the ``BLA-1`` archive batch file over to the ``imports`` directory:

    >>> ac_storage = os.path.join(uploadpath, 'accesscodes')
    >>> logfile2 = os.path.join(ac_storage, 
    ...                         sorted(os.listdir(ac_storage))[-2])
    >>> filename = os.path.basename(logfile2)
    >>> filename
    'BLA-1_archive...-zope.mgr.csv'

    >>> import shutil
    >>> import_path = os.path.join(ac_storage, 'imports')
    >>> shutil.copy(logfile2, import_path)

Now the file will be presented as import source:

    >>> browser.getLink('Reimport Access Code Batch').click()
    >>> filename in browser.contents
    True

If we do not tick a filename to import and click 'Reimport', we will
be warned:

    >>> browser.getControl('Reimport').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...No file chosen. Action cancelled...

Now let's really reimport the batch:

    >>> browser.getLink('Reimport Access Code Batch').click()
    >>> ctrl = browser.getControl(name='filenames')
    >>> ctrl.getControl(value=filename).selected = True
    >>> browser.getControl('Reimport').click()

The batch does exist now again:

    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...Successfully reimported: BLA-1_archive...-zope.mgr.csv...
    ...

If we try to reimport an existing batch, that won't work:

    >>> browser.getLink('Reimport Access Code Batch').click()
    >>> ctrl = browser.getControl(name='filenames')
    >>> ctrl.getControl(value=filename).selected = True
    >>> browser.getControl('Reimport').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...This batch already exists: BLA-1_archive...-zope.mgr.csv...
    ...

Searching Access Codes
======================

We can search for access codes already created.

First we pick some really existing access codes to look for:

    >>> codes = getRootFolder()['myuniversity']['accesscodes']
    >>> app_1_codes = codes[u'APP-1']
    >>> app_2_codes = codes[u'APP-2']
    >>> bla_1_codes = codes[u'BLA-1']

    >>> browser.open('http://localhost/myuniversity/accesscodes/search')
    >>> ctrl = browser.getControl(name='searchtype')
    >>> ctrl.getControl(value='code').selected = True
    >>> browser.getControl(name='searchterm').value = app_1_codes.keys()[0]
    >>> browser.getControl(name='search').click()

The first access code in the ``APP-1`` batch is displayed:

    >>> print browser.contents
    <!DOCTYPE html ...
    ...
      value="APP-1..." /></td>
      <td>...</td>
      <td>APP-1-...</td>
      <td>initialized</td>
      <td>... - initialized by Manager</td>
    ...

We can also search for batch serials (the number of an access code
inside its batch). Looking for number ``1`` will display first access
code of each batch ('APP-1', 'APP-2', and 'BLA-1')

    >>> browser.open('http://localhost/myuniversity/accesscodes/search')
    >>> ctrl = browser.getControl(name='searchtype')
    >>> ctrl.getControl(value='batch_serial').selected = True
    >>> browser.getControl(name='searchterm').value = '1'
    >>> browser.getControl(name='search').click()
    >>> print browser.contents
    <!DOCTYPE html ...
    ...
      value="APP-1..." /></td>
      <td>1</td>
      <td>APP-1-...</td>
      <td>initialized</td>
      <td>... - initialized by Manager</td>
    ...

Searching for non-integer values does not result in an exception:

    >>> ctrl = browser.getControl(name='searchtype')
    >>> ctrl.getControl(value='batch_serial').selected = True
    >>> browser.getControl(name='searchterm').value = 'xyz'
    >>> browser.getControl(name='search').click()
    >>> print browser.contents
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    ...

And we can search for text in history messages of access
codes. Looking for the string ``initialized`` we get nearly all
entries:

    >>> browser.open('http://localhost/myuniversity/accesscodes/search')
    >>> ctrl = browser.getControl(name='searchtype')
    >>> ctrl.getControl(value='history').selected = True
    >>> browser.getControl(name='searchterm').value = 'initialized'
    >>> browser.getControl(name='search').click()
    >>> print browser.contents
    <!DOCTYPE html ...
    ...
      value="APP-1-<10-DIGITS>" /></td>
      <td>1</td>
      <td>APP-1-<10-DIGITS></td>
      <td>initialized</td>
      <td>... - initialized by Manager</td>
    ...

Enabling and Disabling Found Access Codes
=========================================

If a search is successfull, we can enable or disable the found access
codes:

    >>> browser.open('http://localhost/myuniversity/accesscodes/search')
    >>> ctrl = browser.getControl(name='searchtype')
    >>> ctrl.getControl(value='history').selected = True
    >>> browser.getControl(name='searchterm').value = 'initialized'
    >>> browser.getControl(name='search').click()

This lists all access codes. We now tick one to disable it and click
on the appropriate button:

    >>> entries = browser.getControl(name='entries')
    >>> entries.controls[0].selected = True
    >>> browser.getControl('Disable ACs').click()
    >>> print browser.contents
    <!DOCTYPE html ...
    ...APP-1-<10-DIGITS> disabled.</div>
    ...

We cannot disable the same entry again:

    >>> entries = browser.getControl(name='entries')
    >>> entries.controls[0].selected = True
    >>> browser.getControl('Disable ACs').click()
    >>> print browser.contents
    <!DOCTYPE html ...
    <div ...>APP-1-<10-DIGITS>: Disable transition not allowed.</div>
    ...

But we can reenable the same entry:

    >>> entries = browser.getControl(name='entries')
    >>> entries.controls[0].selected = True
    >>> browser.getControl('Enable ACs').click()
    >>> print browser.contents
    <!DOCTYPE html ...
    ...APP-1-<10-DIGITS> (re-)enabled.</div>
    ...

Enabling already enabled items gives a warning:

    >>> entries = browser.getControl(name='entries')
    >>> entries.controls[0].selected = True
    >>> browser.getControl('Enable ACs').click()
    >>> print browser.contents
    <!DOCTYPE html ...
    <div ...>APP-1-<10-DIGITS>: Re-enable transition not allowed.</div>
    ...

Log- and Archive Files
======================

We store log- and archive-files inside the storage managed by the
local datacenter. Access-code related files are placed inside an
``accesscode`` subdir:

    >>> ac_storage = os.path.join(uploadpath, 'accesscodes')

Log files for access-code batches
---------------------------------

Whenever a batch is created, there is also a log file with all entries
created:

    >>> sorted(os.listdir(ac_storage))
    ['APP-1-...-zope.mgr.csv', 'APP-2-...-zope.mgr.csv', ...]

Each logfile name contains the prefix, batch number, date of creation
and userid of creator.

    >>> logfile1 = os.path.join(ac_storage, 
    ...                         sorted(os.listdir(ac_storage))[0])
    >>> print open(logfile1, 'rb').read()
    "serial","ac","cost"
    "APP","1","12.12"
    "0","APP-1-<10-DIGITS>"
    "1","APP-1-<10-DIGITS>"
    "2","APP-1-<10-DIGITS>"
    "3","APP-1-<10-DIGITS>"
    "4","APP-1-<10-DIGITS>"


Archive files
-------------

We created an archive file above. An archive file name consists of the
batch prefix, batch number, the string ``_archive``, creation datetime of
the archive file and userid of batch creator:

    >>> sorted(os.listdir(ac_storage))
    [..., 'BLA-1_archive-...-zope.mgr.csv', 'imports']

Archive files eventually also contain infos about invalidation dates
and have a slightly different format therefore.

    >>> archive_file = os.path.join(ac_storage,
    ...                             sorted(os.listdir(ac_storage))[-2])
    >>> print open(archive_file, 'rb').read()
    "prefix","serial","ac","state","history","cost","owner"
    "BLA","19.12","1","3"
    "BLA","0","BLA-1-<10-DIGITS>","initialized","...","19.12",""
    "BLA","1","BLA-1-<10-DIGITS>","initialized","...","19.12",""
    "BLA","2","BLA-1-<10-DIGITS>","initialized","...","19.12",""

Clean up:

    >>> shutil.rmtree(uploadpath)
