Changeset 13804


Ignore:
Timestamp:
6 Apr 2016, 07:42:09 (9 years ago)
Author:
Henrik Bettermann
Message:

Rearrange datacenter upload page. Provide modal windows to view all
processors and sources and vocabularies.

See r12932, r12933 and 12955ff

Location:
main/waeup.ikoba/trunk
Files:
3 added
6 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.ikoba/trunk/CHANGES.txt

    r13803 r13804  
    440.2.dev0 (unreleased)
    55=====================
     6
     7* Rearrange datacenter upload page. Provide modal windows to view all
     8  processors and sources and vocabularies.
    69
    710* Enable temporary suspension of officer accounts. Plugins must be
  • main/waeup.ikoba/trunk/layout/theme.html

    r12193 r13804  
    328328            </div>
    329329            <div>...content that spans the whole page.</div>
     330            <br><br>
     331
     332            <h3>Accordions</h3>
     333            <div class="panel-group" id="accordion" role="tablist"
     334                 aria-multiselectable="true">
     335              <div class="panel panel-default">
     336                <div class="panel-heading" role="tab" id="headingTwo">
     337                  <h4 class="panel-title">
     338                    <a class="collapsed" data-toggle="collapse"
     339                       data-parent="#accordion" href="#collapseTwo"
     340                       aria-expanded="false" aria-controls="collapseTwo">
     341                      Collapsible Group
     342                    </a>
     343                  </h4>
     344                </div>
     345                <div id="collapseTwo" class="panel-collapse collapse"
     346                     role="tabpanel" aria-labelledby="headingTwo">
     347                  <div class="panel-body">
     348                    Anim pariatur cliche reprehenderit, enim eiusmod high life
     349                    terry richardson ad squid. 3 wolf moon officia aute, non
     350                    skateboard dolor brunch. Food truck quinoa laborum eiusmod.
     351                  </div>
     352                </div>
     353              </div>
     354            </div>
     355
    330356            <br><br>
    331357            <h3>A Sample Data Table</h3>
  • main/waeup.ikoba/trunk/src/waeup/ikoba/browser/pages.py

    r13803 r13804  
    5555    ILocalRolesAssignable, DuplicationError, IConfigurationContainer,
    5656    IJobManager,
    57     IPasswordValidator, IContactForm, IIkobaUtils, ICSVExporter)
     57    IPasswordValidator, IContactForm, IIkobaUtils, ICSVExporter,
     58    CurrencySource)
    5859from waeup.ikoba.permissions import (
    5960    get_users_with_local_roles, get_all_roles, get_all_users,
    6061    get_users_with_role)
    61 
     62from waeup.ikoba.customers.vocabularies import GenderSource
    6263from waeup.ikoba.authentication import LocalRoleSetEvent
    6364from waeup.ikoba.utils.helpers import get_user_account, check_csv_charset
    6465from waeup.ikoba.mandates.mandate import PasswordMandate
    6566from waeup.ikoba.datacenter import DataCenterFile
     67from waeup.ikoba.customers.utils import ICustomersUtils
     68from waeup.ikoba.documents.utils import IDocumentsUtils
    6669
    6770FORBIDDEN_CHARACTERS = (160,)
     
    245248            return True
    246249    return False
     250
     251def getImporters(context):
     252    importers = getAllUtilitiesRegisteredFor(IBatchProcessor)
     253    importer_props = []
     254    for x in importers:
     255        # Skip User Processor if user isn't allowed to manage users.
     256        if x.util_name == 'userprocessor' and not checkPermission(
     257            'waeup.manageUsers', context):
     258            continue
     259        iface_fields = schema.getFields(x.iface)
     260        available_fields = []
     261        for key in iface_fields.keys():
     262            iface_fields[key] = (iface_fields[key].__class__.__name__,
     263                iface_fields[key].required)
     264        for value in x.available_fields:
     265            available_fields.append(
     266                dict(f_name=value,
     267                     f_type=iface_fields.get(value, (None, False))[0],
     268                     f_required=iface_fields.get(value, (None, False))[1]
     269                     )
     270                )
     271        available_fields = sorted(available_fields, key=lambda k: k[
     272            'f_name'])
     273        importer_props.append(
     274            dict(title=x.name, name=x.util_name, fields=available_fields))
     275    return sorted(importer_props, key=lambda k: k['title'])
    247276
    248277class LocalRoleAssignmentUtilityView(object):
     
    496525            return html
    497526
     527class SourcesOverview(grok.View):
     528    grok.context(ICompany)
     529    grok.name('sourcesoverview')
     530    grok.require('waeup.Public')
     531
     532    def _set_con_cats(self):
     533        con_cats = getUtility(IIkobaUtils).CON_CATS_DICT
     534        self.con_cats = con_cats.items()
     535        return
     536
     537    def _set_payment_categories(self):
     538        payment_categories = getUtility(IIkobaUtils).PAYMENT_CATEGORIES
     539        self.payment_categories = payment_categories.items()
     540        return
     541
     542    def _set_customer_doctypes(self):
     543        customer_doctypes = getUtility(ICustomersUtils).DOCTYPES_DICT
     544        self.customer_doctypes = customer_doctypes.items()
     545        return
     546
     547    def _set_contypes(self):
     548        contypes = getUtility(ICustomersUtils).CONTYPES_DICT
     549        self.contypes = contypes.items()
     550        return
     551
     552    def _set_doctypes(self):
     553        doctypes = getUtility(IDocumentsUtils).DOCTYPES_DICT
     554        self.doctypes = doctypes.items()
     555        return
     556
     557    def _set_customer_states(self):
     558        customer_states = getUtility(ICustomersUtils).TRANSLATED_CUSTOMER_STATES
     559        self.customer_states = customer_states.items()
     560        return
     561
     562    def _set_contract_states(self):
     563        contract_states = getUtility(ICustomersUtils).TRANSLATED_CONTRACT_STATES
     564        self.contract_states = contract_states.items()
     565        return
     566
     567    def _set_customer_document_states(self):
     568        customer_document_states = getUtility(
     569            ICustomersUtils).TRANSLATED_DOCUMENT_STATES
     570        self.customer_document_states = customer_document_states.items()
     571        return
     572
     573    def _set_document_states(self):
     574        document_states = getUtility(
     575            IDocumentsUtils).TRANSLATED_DOCUMENT_STATES
     576        self.document_states = document_states.items()
     577        return
     578
     579    def _set_currencies(self):
     580        currenciessource = CurrencySource().factory
     581        self.currencies = []
     582        for code in currenciessource.getValues():
     583            title = currenciessource.getTitle(code)
     584            self.currencies.append((code, title))
     585        return
     586
     587    def _set_sex(self):
     588        gendersource = GenderSource().factory
     589        self.sex = []
     590        for sex in gendersource.getValues():
     591            title = gendersource.getTitle(sex)
     592            self.sex.append((sex, title))
     593        return
     594
     595    def update(self):
     596        self._set_con_cats()
     597        self._set_payment_categories()
     598        self._set_customer_doctypes()
     599        self._set_contypes()
     600        self._set_doctypes()
     601        self._set_currencies()
     602        self._set_sex()
     603        self._set_customer_states()
     604        self._set_contract_states()
     605        self._set_customer_document_states()
     606        self._set_document_states()
     607
     608class SourcesOverviewPage(IkobaPage, SourcesOverview):
     609    grok.name('sources')
     610    grok.require('waeup.Public')
     611    label = _(u'Sources & Vocabularies')
     612    pnav = 0
     613
     614class ProcessorsOverview(grok.View):
     615    grok.context(ICompany)
     616    grok.name('processorsoverview')
     617    grok.require('waeup.Public')
     618
     619    def getImporters(self):
     620        return getImporters(self.context)
     621
     622class ProcessorsOverviewPage(IkobaPage, ProcessorsOverview):
     623    grok.name('processors')
     624    grok.require('waeup.Public')
     625    label = _(u'Available Processors (Importers)')
     626    pnav = 0
     627
     628class SkeletonDownloadView(UtilityView, grok.View):
     629    grok.context(ICompany)
     630    grok.name('skeleton')
     631    grok.require('waeup.Public')
     632
     633    def update(self, processorname=None):
     634        self.processorname = self.request.form['name']
     635        self.filename = ('%s_000.csv' %
     636            self.processorname.replace('processor','import'))
     637        return
     638
     639    def render(self):
     640        #ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     641        #self.context.logger.info(
     642        #    '%s - skeleton downloaded: %s' % (ob_class, self.filename))
     643        self.response.setHeader(
     644            'Content-Type', 'text/csv; charset=UTF-8')
     645        self.response.setHeader(
     646            'Content-Disposition:', 'attachment; filename="%s' % self.filename)
     647        processor = getUtility(IBatchProcessor, name=self.processorname)
     648        csv_data = processor.get_csv_skeleton()
     649        return csv_data
     650
    498651class AdministrationPage(IkobaPage):
    499652    """ The administration overview page.
     
    9971150
    9981151    def getImporters(self):
    999         importers = getAllUtilitiesRegisteredFor(IBatchProcessor)
    1000         ikoba_utils = getUtility(IIkobaUtils)
    1001         importer_props = []
    1002         for x in importers:
    1003             if not x.util_name in ikoba_utils.BATCH_PROCESSOR_NAMES:
    1004                 continue
    1005             # Skip User Processor if user isn't allowed to manage users.
    1006             if x.util_name == 'userprocessor' and not checkPermission(
    1007                 'waeup.manageUsers', self.context):
    1008                 continue
    1009             iface_fields = schema.getFields(x.iface)
    1010             available_fields = []
    1011             for key in iface_fields.keys():
    1012                 iface_fields[key] = (iface_fields[key].__class__.__name__,
    1013                     iface_fields[key].required)
    1014             for value in x.available_fields:
    1015                 available_fields.append(
    1016                     dict(f_name=value,
    1017                          f_type=iface_fields.get(value, (None, False))[0],
    1018                          f_required=iface_fields.get(value, (None, False))[1]
    1019                          )
    1020                     )
    1021             available_fields = sorted(available_fields, key=lambda k: k['f_name'])
    1022             importer_props.append(
    1023                 dict(title=x.name, name=x.util_name, fields=available_fields))
    1024         return sorted(importer_props, key=lambda k: k['title'])
     1152        return getImporters(self.context)
    10251153
    10261154class FileDownloadView(UtilityView, grok.View):
     
    10441172        fullpath = os.path.join(self.context.storage, self.filename)
    10451173        return open(fullpath, 'rb').read()
    1046 
    1047 class SkeletonDownloadView(UtilityView, grok.View):
    1048     grok.context(IDataCenter)
    1049     grok.name('skeleton')
    1050     grok.require('waeup.manageDataCenter')
    1051 
    1052     def update(self, processorname=None):
    1053         self.processorname = self.request.form['name']
    1054         self.filename = ('%s_000.csv' %
    1055             self.processorname.replace('processor','import'))
    1056         return
    1057 
    1058     def render(self):
    1059         #ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
    1060         #self.context.logger.info(
    1061         #    '%s - skeleton downloaded: %s' % (ob_class, self.filename))
    1062         self.response.setHeader(
    1063             'Content-Type', 'text/csv; charset=UTF-8')
    1064         self.response.setHeader(
    1065             'Content-Disposition:', 'attachment; filename="%s' % self.filename)
    1066         processor = getUtility(IBatchProcessor, name=self.processorname)
    1067         csv_data = processor.get_csv_skeleton()
    1068         return csv_data
    10691174
    10701175class DatacenterImportStep1(IkobaPage):
  • main/waeup.ikoba/trunk/src/waeup/ikoba/browser/templates/datacenteruploadpage.pt

    r11949 r13804  
    1 <p i18n:translate="">
    2     Before uploading a file check that your file header corresponds
    3     with header format of the selected processor. All available headers
    4     are listed below.
    5     After file upload click the 'Process data' button and proceed up to
    6     import step 3. Verify that the data format
    7     meets all the import criteria and requirements of the processor.
    8 </p>
    9 
     1<span i18n:translate="">
     2    Before uploading a file, check that your file header corresponds
     3    with the header format of the selected processor:
     4</span>
     5<!-- Processors Modal -->
     6<button i18n:translate="" class="btn btn-info btn-xs"
     7   data-toggle="modal" data-target="#ProcessorsModal"
     8   href="../processorsoverview">
     9   View available processors (importers)
     10</button>
     11<div class="modal fade" id="ProcessorsModal" tabindex="-1"
     12     role="dialog" aria-labelledby="ProcessorsModalLabel" aria-hidden="true">
     13  <div class="modal-dialog">
     14    <div class="modal-content">
     15    </div>
     16  </div>
     17</div>
     18<br /><br />
     19<span i18n:translate="">
     20    Many importer fields are of type 'Choice' which means only definied keywords
     21    are allowed:
     22</span>
     23<!-- Sources Modal -->
     24<button i18n:translate="" class="btn btn-info btn-xs"
     25   data-toggle="modal" data-target="#SourcesModal"
     26   href="../sourcesoverview">
     27   View choices
     28</button>
     29<div class="modal fade" id="SourcesModal" tabindex="-1"
     30     role="dialog" aria-labelledby="SourcesModalLabel" aria-hidden="true">
     31  <div class="modal-dialog">
     32    <div class="modal-content">
     33    </div>
     34  </div>
     35</div>
     36<br />
    1037<form method="post" enctype="multipart/form-data">
    1138<br />
     
    1643      </td>
    1744      <td>
    18                 <div class="input-group half">
    19                         <div class="input-group-btn">
    20                                 <div class="btn btn-default btn-file">
     45        <div class="input-group half">
     46          <div class="input-group-btn">
     47            <div class="btn btn-default btn-file">
    2148              Select&hellip;
    2249              <input type="file" name="uploadfile:file" />
    2350            </div>
    24                         </div>
    25                         <input type="text" class="form-control" readonly>
     51          </div>
     52          <input type="text" class="form-control" readonly>
    2653        </div>
    2754      </td>
     
    5986      tal:attributes="value view/cancel_button"/>
    6087</form>
    61 <br />
    62 <p i18n:translate="">
    63     Import managers will be automatically informed by email after file upload.
    64     There is no need for assigning tickets or sending emails anymore.
    65     The data will be imported according to the information given.
    66 </p>
     88
    6789
    6890<br /><br />
    6991
    70 <h2 i18n:translate="">Available Processors (Importers)</h2>
    71 
    72 <table i18n:domain="waeup.ikoba" class="table table-condensed">
    73   <thead>
    74     <tr>
    75       <th i18n:translate="">Processor</th>
    76       <th i18n:translate="">Required Schema Fields</th>
    77       <th i18n:translate="">Optional Schema Fields</th>
    78       <th i18n:translate="">Non-Schema Fields</th>
    79     </tr>
    80   </thead>
    81   <tr tal:repeat="importer view/getImporters">
    82     <td>
    83       <span tal:content="importer/title">TITLE</span><br /><br />
    84       <a i18n:translate="" class="btn btn-primary btn-xs"
    85          tal:attributes="href python: 'skeleton?name=' + importer['name']">
    86          Download CSV Skeleton File
    87       </a>
    88     </td>
    89     <td nowrap>
    90       <span tal:repeat="field importer/fields">
    91           <tal:schemafield condition="field/f_type">
    92             <tal:required condition="field/f_required">
    93               <span tal:replace="field/f_name"></span>
    94               (<span tal:replace="field/f_type"></span>)
    95               <br />
    96             </tal:required>
    97           </tal:schemafield>
    98       </span>
    99     </td>
    100     <td nowrap>
    101       <span tal:repeat="field importer/fields">
    102           <tal:schemafield condition="field/f_type">
    103             <tal:notrequired condition="not:field/f_required">
    104               <span tal:replace="field/f_name"></span>
    105               (<span tal:replace="field/f_type"></span>)
    106               <br />
    107             </tal:notrequired>
    108           </tal:schemafield>
    109       </span>
    110     </td>
    111     <td>
    112       <span tal:repeat="field importer/fields">
    113           <tal:extrafield condition="not:field/f_type">
    114             <span tal:replace="field/f_name"></span>
    115             <br />
    116           </tal:extrafield>
    117       </span>
    118     </td>
    119 
    120   </tr>
    121 </table>
     92<div i18n:translate="">
     93    After file upload click the 'Process data' button and proceed up to
     94    import step 3. Verify that the data format
     95    meets all the import criteria and requirements of the processor.
     96</div>
     97<br />
     98<div i18n:translate="">
     99    Import managers will be automatically informed by email after file upload.
     100</div>
  • main/waeup.ikoba/trunk/src/waeup/ikoba/browser/tests/test_browser.py

    r13803 r13804  
    214214            'This account has been suspended.' in self.browser.contents)
    215215        return
     216
     217    def test_sources_overview(self):
     218        self.browser.open('http://localhost/app/sources')
     219        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     220        self.assertTrue('id="headingCustomerStates"' in self.browser.contents)
     221
     222    def test_processors_overview(self):
     223        self.browser.open('http://localhost/app/processors')
     224        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     225        self.browser.getLink(url='skeleton?name=productprocessor').click()
     226        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     227        self.assertEqual(self.browser.headers['Content-Type'],
     228                         'text/csv; charset=UTF-8')
     229        self.assertEqual(self.browser.contents,
     230            'contract_category,contract_title,description,'
     231            'options,product_id,terms_and_conditions,title,'
     232            'valid_from,valid_to\r\n')
     233        return
  • main/waeup.ikoba/trunk/src/waeup/ikoba/browser/viewlets.py

    r12525 r13804  
    5757    grok.name('widgets')
    5858
     59class SourcesLeft(grok.ViewletManager):
     60    grok.name('sources_left')
     61
     62class SourcesRight(grok.ViewletManager):
     63    grok.name('sources_right')
     64
    5965
    6066#
     
    601607        super(SubobjectLister, self).update()
    602608        return
     609
     610class ConCatsSource(grok.Viewlet):
     611    """Avalable contract categories for the sources overview page.
     612    """
     613    grok.order(1)
     614    grok.viewletmanager(SourcesLeft)
     615    grok.require('waeup.Public')
     616    grok.template('source')
     617    column = 'left'
     618    name = 'ConCats'
     619    source_name = 'con_cats'
     620    title = _('Contract Categories')
     621
     622    def heading(self):
     623        return 'heading%s' % self.name
     624
     625    def accordion(self):
     626        return '#accordion-%s' % self.column
     627
     628    def collapse(self):
     629        return 'collapse%s' % self.name
     630
     631    def hash_collapse(self):
     632        return '#collapse%s' % self.name
     633
     634    def source(self):
     635        return getattr(self.view, self.source_name)
     636
     637class PaymentCatsSource(ConCatsSource):
     638    """Avalable payment categories for the sources overview page.
     639    """
     640    grok.order(2)
     641    name = 'PaymentCats'
     642    source_name = 'payment_categories'
     643    title = _('Payment Categories')
     644
     645class PaymentCatsSource(ConCatsSource):
     646    """Avalable payment categories for the sources overview page.
     647    """
     648    grok.order(2)
     649    name = 'PaymentCats'
     650    source_name = 'payment_categories'
     651    title = _('Payment Categories')
     652
     653class CustomerDocTypesSource(ConCatsSource):
     654    """Avalable customer document types for the sources overview page.
     655    """
     656    grok.order(3)
     657    name = 'CustomerDocTypes'
     658    source_name = 'customer_doctypes'
     659    title = _('Customer Document Types')
     660
     661class ConTypesSource(ConCatsSource):
     662    """Avalable contract types for the sources overview page.
     663    """
     664    grok.order(4)
     665    name = 'ConTypes'
     666    source_name = 'contypes'
     667    title = _('Contract Types')
     668
     669class DocTypesSource(ConCatsSource):
     670    """Avalable document types for the sources overview page.
     671    """
     672    grok.order(5)
     673    name = 'DocTypes'
     674    source_name = 'doctypes'
     675    title = _('Document Types')
     676
     677class SexSource(ConCatsSource):
     678    """Avalable sex values for the sources overview page.
     679    """
     680    grok.order(6)
     681    name = 'Sex'
     682    source_name = 'sex'
     683    title = _('Sex')
     684
     685class CurrencySource(ConCatsSource):
     686    """Avalable currencies for the sources overview page.
     687    """
     688    grok.order(7)
     689    name = 'Currencies'
     690    source_name = 'currencies'
     691    title = _('Currencies')
     692
     693class CustomerStatesSource(ConCatsSource):
     694    """Avalable customer sates for the sources overview page.
     695    """
     696    grok.order(1)
     697    grok.viewletmanager(SourcesRight)
     698    column = 'right'
     699    name = 'CustomerStates'
     700    source_name = 'customer_states'
     701    title = _('Customer Registration States')
     702
     703class ContractStatesSource(ConCatsSource):
     704    """Avalable contract sates for the sources overview page.
     705    """
     706    grok.order(2)
     707    grok.viewletmanager(SourcesRight)
     708    column = 'right'
     709    name = 'ContractStates'
     710    source_name = 'contract_states'
     711    title = _('Contract Workflow States')
     712
     713class CustomerDocumentStatesSource(ConCatsSource):
     714    """Avalable customer document sates for the sources overview page.
     715    """
     716    grok.order(3)
     717    grok.viewletmanager(SourcesRight)
     718    column = 'right'
     719    name = 'CustomerDocumentStates'
     720    source_name = 'customer_document_states'
     721    title = _('Customer Document Workflow States')
     722
     723class DocumentsDocumentStatesSource(ConCatsSource):
     724    """Avalable document sates for the sources overview page.
     725    """
     726    grok.order(4)
     727    grok.viewletmanager(SourcesRight)
     728    column = 'right'
     729    name = 'DocumentStates'
     730    source_name = 'document_states'
     731    title = _('Document Workflow States')
Note: See TracChangeset for help on using the changeset viewer.