web/views/cwsources.py
changeset 7995 9a9f35ef418c
parent 7994 af3fb709c061
child 8136 273d8a03700c
equal deleted inserted replaced
7994:af3fb709c061 7995:9a9f35ef418c
    20 """
    20 """
    21 
    21 
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
    23 _ = unicode
    23 _ = unicode
    24 
    24 
       
    25 import logging
    25 from itertools import repeat, chain
    26 from itertools import repeat, chain
    26 
    27 from logilab.mtconverter import xml_escape
    27 from cubicweb import Unauthorized
    28 from logilab.common.decorators import cachedproperty
    28 from cubicweb.selectors import is_instance, score_entity, match_user_groups
    29 
       
    30 from cubicweb import Unauthorized, tags
       
    31 from cubicweb.utils import make_uid
       
    32 from cubicweb.selectors import (is_instance, score_entity, has_related_entities,
       
    33                                 match_user_groups, match_kwargs, match_view)
    29 from cubicweb.view import EntityView, StartupView
    34 from cubicweb.view import EntityView, StartupView
    30 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
    35 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
    31 from cubicweb.web import uicfg, formwidgets as wdgs
    36 from cubicweb.web import uicfg, formwidgets as wdgs, facet
    32 from cubicweb.web.views import tabs, actions, ibreadcrumbs, tableview, add_etype_button
    37 from cubicweb.web.views import add_etype_button
       
    38 from cubicweb.web.views import (tabs, actions, ibreadcrumbs, navigation,
       
    39                                 tableview, pyviews)
    33 
    40 
    34 
    41 
    35 _abaa = uicfg.actionbox_appearsin_addmenu
    42 _abaa = uicfg.actionbox_appearsin_addmenu
    36 # there are explicit 'add' buttons for those
    43 # there are explicit 'add' buttons for those
    37 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_schema', '*'), False)
    44 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_schema', '*'), False)
    38 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_for_source', '*'), False)
    45 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_for_source', '*'), False)
    39 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_host_config_of', '*'), False)
    46 _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_host_config_of', '*'), False)
       
    47 _abaa.tag_object_of(('CWDataImport', 'cw_import_of', '*'), False)
    40 
    48 
    41 _afs = uicfg.autoform_section
    49 _afs = uicfg.autoform_section
    42 _afs.tag_object_of(('*', 'cw_for_source', 'CWSource'), 'main', 'hidden')
    50 _afs.tag_object_of(('*', 'cw_for_source', 'CWSource'), 'main', 'hidden')
    43 
    51 
    44 _affk = uicfg.autoform_field_kwargs
    52 _affk = uicfg.autoform_field_kwargs
    60 _rc.tag_attribute(('CWSourceSchemaConfig', 'options'), {'rvid': 'verbatimattr'})
    68 _rc.tag_attribute(('CWSourceSchemaConfig', 'options'), {'rvid': 'verbatimattr'})
    61 
    69 
    62 
    70 
    63 class CWSourcePrimaryView(tabs.TabbedPrimaryView):
    71 class CWSourcePrimaryView(tabs.TabbedPrimaryView):
    64     __select__ = is_instance('CWSource')
    72     __select__ = is_instance('CWSource')
    65     tabs = [_('cwsource-main'), _('cwsource-mapping')]
    73     tabs = [_('cwsource-main'), _('cwsource-mapping'), _('cwsource-imports')]
    66     default_tab = 'cwsource-main'
    74     default_tab = 'cwsource-main'
    67 
    75 
    68 
    76 
    69 class CWSourceMainTab(tabs.PrimaryTab):
    77 class CWSourceMainTab(tabs.PrimaryTab):
    70     __regid__ = 'cwsource-main'
    78     __regid__ = 'cwsource-main'
    71     __select__ = tabs.PrimaryTab.__select__ & is_instance('CWSource')
    79     __select__ = is_instance('CWSource')
    72 
    80 
    73     def render_entity_attributes(self, entity):
    81     def render_entity_attributes(self, entity):
    74         super(CWSourceMainTab, self).render_entity_attributes(entity)
    82         super(CWSourceMainTab, self).render_entity_attributes(entity)
    75         self.w(add_etype_button(self._cw, 'CWSourceHostConfig',
    83         self.w(add_etype_button(self._cw, 'CWSourceHostConfig',
    76                                 __linkto='cw_host_config_of:%s:subject' % entity.eid,
    84                                 __linkto='cw_host_config_of:%s:subject' % entity.eid,
    91 
    99 
    92 MAPPED_SOURCE_TYPES = set( ('pyrorql', 'datafeed') )
   100 MAPPED_SOURCE_TYPES = set( ('pyrorql', 'datafeed') )
    93 
   101 
    94 class CWSourceMappingTab(EntityView):
   102 class CWSourceMappingTab(EntityView):
    95     __regid__ = 'cwsource-mapping'
   103     __regid__ = 'cwsource-mapping'
    96     __select__ = (tabs.PrimaryTab.__select__ & is_instance('CWSource')
   104     __select__ = (is_instance('CWSource')
    97                   & match_user_groups('managers')
   105                   & match_user_groups('managers')
    98                   & score_entity(lambda x:x.type in MAPPED_SOURCE_TYPES))
   106                   & score_entity(lambda x:x.type in MAPPED_SOURCE_TYPES))
    99 
   107 
   100     def entity_call(self, entity):
   108     def entity_call(self, entity):
   101         _ = self._cw._
   109         _ = self._cw._
   246 
   254 
   247 MAPPING_CHECKERS = {
   255 MAPPING_CHECKERS = {
   248     'pyrorql': PyroRQLMappingChecker,
   256     'pyrorql': PyroRQLMappingChecker,
   249     }
   257     }
   250 
   258 
       
   259 
       
   260 class CWSourceImportsTab(EntityView):
       
   261     __regid__ = 'cwsource-imports'
       
   262     __select__ = (is_instance('CWSource')
       
   263                   & has_related_entities('cw_import_of', 'object'))
       
   264 
       
   265     def entity_call(self, entity):
       
   266         rset = self._cw.execute('Any X, XST, XET, XS ORDERBY XST DESC WHERE '
       
   267                                 'X cw_import_of S, S eid %(s)s, X status XS, '
       
   268                                 'X start_timestamp XST, X end_timestamp XET',
       
   269                                 {'s': entity.eid})
       
   270         self._cw.view('cw.imports-table', rset, w=self.w)
       
   271 
       
   272 
       
   273 class CWImportsTable(tableview.EntityTableView):
       
   274     __regid__ = 'cw.imports-table'
       
   275     __select__ = is_instance('CWDataImport')
       
   276     columns = ['import', 'start_timestamp', 'end_timestamp']
       
   277     column_renderers = {'import': tableview.MainEntityColRenderer()}
       
   278     layout_args = {'display_filter': 'top'}
       
   279 
       
   280 
       
   281 
       
   282 
   251 # sources management view ######################################################
   283 # sources management view ######################################################
   252 
   284 
   253 class ManageSourcesAction(actions.ManagersAction):
   285 class ManageSourcesAction(actions.ManagersAction):
   254     __regid__ = 'cwsource'
   286     __regid__ = 'cwsource'
   255     title = _('data sources')
   287     title = _('data sources')
   270 
   302 
   271 
   303 
   272 class CWSourcesTable(tableview.EntityTableView):
   304 class CWSourcesTable(tableview.EntityTableView):
   273     __regid__ = 'cw.sources-table'
   305     __regid__ = 'cw.sources-table'
   274     __select__ = is_instance('CWSource')
   306     __select__ = is_instance('CWSource')
   275     columns = ['source', 'type', 'parser', 'latest_retrieval']
   307     columns = ['source', 'type', 'parser', 'latest_retrieval', 'latest_import']
   276 
   308 
   277     # @tableview.etable_header_title('CWSource_plural', addcount=True)
   309     class LatestImportColRenderer(tableview.EntityTableColRenderer):
   278     # @tableview.etable_entity_sortvalue()
   310         def render_cell(self, w, rownum):
   279     # def source_cell(self, w, entity):
   311             entity = self.entity(rownum)
   280     #     w(entity.view('incontext'))
   312             rset = self._cw.execute('Any X,XS,XST ORDERBY XST DESC LIMIT 1 WHERE '
       
   313                                     'X cw_import_of S, S eid %(s)s, X status XS, '
       
   314                                     'X start_timestamp XST', {'s': entity.eid})
       
   315             if rset:
       
   316                 self._cw.view('incontext', rset, row=0, w=w)
       
   317             else:
       
   318                 w(self.empty_cell_content)
       
   319 
       
   320     column_renderers = {
       
   321         'source': tableview.MainEntityColRenderer(),
       
   322         'latest_import': LatestImportColRenderer(header=_('latest import'),
       
   323                                                  sortable=False)
       
   324         }
       
   325 
       
   326 # datafeed source import #######################################################
       
   327 
       
   328 REVERSE_SEVERITIES = {
       
   329     logging.DEBUG :   _('DEBUG'),
       
   330     logging.INFO :    _('INFO'),
       
   331     logging.WARNING : _('WARNING'),
       
   332     logging.ERROR :   _('ERROR'),
       
   333     logging.FATAL :   _('FATAL')
       
   334 }
       
   335 
       
   336 
       
   337 def log_to_table(req, rawdata):
       
   338     data = []
       
   339     for msg_idx, msg in enumerate(rawdata.split('<br/>')):
       
   340         record = msg.strip()
       
   341         if not record:
       
   342             continue
       
   343         try:
       
   344             severity, url, line, msg = record.split('\t', 3)
       
   345         except ValueError:
       
   346             req.warning('badly formated log %s' % record)
       
   347             url = line = u''
       
   348             severity = logging.DEBUG
       
   349             msg = record
       
   350         data.append( (severity, url, line, msg) )
       
   351     return data
       
   352 
       
   353 
       
   354 class LogTableLayout(tableview.TableLayout):
       
   355     __select__ = match_view('cw.log.table')
       
   356     needs_js = tableview.TableLayout.needs_js + ('cubicweb.log.js',)
       
   357     needs_css = tableview.TableLayout.needs_css + ('cubicweb.log.css',)
       
   358     columns_css = {
       
   359         0: 'logSeverity',
       
   360         1: 'logPath',
       
   361         2: 'logLine',
       
   362         3: 'logMsg',
       
   363         }
       
   364 
       
   365     def render_table(self, w, actions, paginate):
       
   366         default_level = self.view.cw_extra_kwargs['default_level']
       
   367         if default_level != 'Debug':
       
   368             self._cw.add_onload('$("select.logFilter").val("%s").change();'
       
   369                            % self._cw.form.get('logLevel', default_level))
       
   370         w(u'\n<form action="#"><fieldset>')
       
   371         w(u'<label>%s</label>' % self._cw._(u'Message threshold'))
       
   372         w(u'<select class="log_filter" onchange="filterLog(\'%s\', this.options[this.selectedIndex].value)">'
       
   373           % self.view.domid)
       
   374         for level in ('Debug', 'Info', 'Warning', 'Error', 'Fatal'):
       
   375             w('<option value="%s">%s</option>' % (level, self._cw._(level)))
       
   376         w(u'</select>')
       
   377         w(u'</fieldset></form>')
       
   378         super(LogTableLayout, self).render_table(w, actions, paginate)
       
   379 
       
   380     def table_attributes(self):
       
   381         attrs = super(LogTableLayout, self).table_attributes()
       
   382         attrs['id'] = 'table'+self.view.domid
       
   383         return attrs
       
   384 
       
   385     def row_attributes(self, rownum):
       
   386         attrs = super(LogTableLayout, self).row_attributes(rownum)
       
   387         attrs['id'] = 'log_msg_%i' % rownum
       
   388         severityname = REVERSE_SEVERITIES[int(self.view.pyvalue[rownum][0])]
       
   389         attrs['class'] = 'log%s' % severityname.capitalize()
       
   390         return attrs
       
   391 
       
   392     def cell_attributes(self, rownum, colnum, colid):
       
   393         attrs = super(LogTableLayout, self).cell_attributes(rownum, colnum, colid)
       
   394         attrs['class'] = self.columns_css[colnum]
       
   395         return attrs
       
   396 
       
   397 
       
   398 class LogTable(pyviews.PyValTableView):
       
   399     __regid__ = 'cw.log.table'
       
   400     headers = [_('severity'), _('url'), _('line'), _('message')]
       
   401 
       
   402     @cachedproperty
       
   403     def domid(self):
       
   404         return make_uid('logTable')
       
   405 
       
   406     class SeverityRenderer(pyviews.PyValTableColRenderer):
       
   407         def render_cell(self, w, rownum):
       
   408             severity = self.data[rownum][0]
       
   409             w(u'<a class="internallink" href="javascript:;" title="%(title)s" '
       
   410               u'''onclick="document.location.hash='%(msg_id)s';">&#182;</a>'''
       
   411               u'&#160;%(severity)s' % {
       
   412                 'severity': self._cw._(REVERSE_SEVERITIES[int(severity)]),
       
   413                 'title': self._cw._('permalink to this message'),
       
   414                 'msg_id': 'log_msg_%i' % rownum,
       
   415             })
       
   416         def sortvalue(self, rownum):
       
   417             return int(self.data[rownum][0])
       
   418 
       
   419     class URLRenderer(pyviews.PyValTableColRenderer):
       
   420         def render_cell(self, w, rownum):
       
   421             url = self.data[rownum][1]
       
   422             w(url and tags.a(url, href=url) or u'&#160;')
       
   423 
       
   424     class LineRenderer(pyviews.PyValTableColRenderer):
       
   425         def render_cell(self, w, rownum):
       
   426             line = self.data[rownum][2]
       
   427             w(line or u'&#160;')
       
   428 
       
   429     class MessageRenderer(pyviews.PyValTableColRenderer):
       
   430         snip_over = 7
       
   431         def render_cell(self, w, rownum):
       
   432             msg = self.data[rownum][3]
       
   433             lines = msg.splitlines()
       
   434             if len(lines) <= self.snip_over:
       
   435                 w(u'<pre class="rawtext">%s</pre>' % msg)
       
   436             else:
       
   437                 # The make_uid argument has no specific meaning here.
       
   438                 div_snip_id = make_uid(u'log_snip_')
       
   439                 div_full_id = make_uid(u'log_full_')
       
   440                 divs_id = (div_snip_id, div_full_id)
       
   441                 snip = u'\n'.join((lines[0], lines[1],
       
   442                                    u'  ...',
       
   443                                    u'    %i more lines [double click to expand]' % (len(lines)-4),
       
   444                                    u'  ...',
       
   445                                    lines[-2], lines[-1]))
       
   446                 divs = (
       
   447                         (div_snip_id, snip, u'expand', "class='collapsed'"),
       
   448                         (div_full_id, msg,  u'collapse', "class='hidden'")
       
   449                 )
       
   450                 for div_id, content, button, h_class in divs:
       
   451                     text = self._cw._(button)
       
   452                     js = u"toggleVisibility('%s'); toggleVisibility('%s');" % divs_id
       
   453                     w(u'<div id="%s" %s>' % (div_id, h_class))
       
   454                     w(u'<pre class="raw_test" ondblclick="javascript: %s" '
       
   455                       u'title="%s" style="display: block;">' % (js, text))
       
   456                     w(content)
       
   457                     w(u'</pre>')
       
   458                     w(u'</div>')
       
   459 
       
   460     column_renderers = {0: SeverityRenderer(),
       
   461                         1: URLRenderer(),
       
   462                         2: LineRenderer(),
       
   463                         3: MessageRenderer(),
       
   464                         }
       
   465 
       
   466 
       
   467 class DataFeedSourceDataImport(EntityView):
       
   468     __select__ = EntityView.__select__ & match_kwargs('rtype')
       
   469     __regid__ = 'cw.formated_log'
       
   470 
       
   471     def cell_call(self, row, col, rtype, loglevel='Info', **kwargs):
       
   472         if 'dispctrl' in self.cw_extra_kwargs:
       
   473             loglevel = self.cw_extra_kwargs['dispctrl'].get('loglevel', loglevel)
       
   474         entity = self.cw_rset.get_entity(row, col)
       
   475         value = getattr(entity, rtype)
       
   476         if value:
       
   477             self._cw.view('cw.log.table', pyvalue=log_to_table(self._cw, value),
       
   478                           default_level=loglevel, w=self.w)
       
   479         else:
       
   480             self.w(self._cw._('no log to display'))
       
   481 
       
   482 
       
   483 _pvs.tag_attribute(('CWDataImport', 'log'), 'relations')
       
   484 _pvdc.tag_attribute(('CWDataImport', 'log'), {'vid': 'cw.formated_log'})
       
   485 _pvs.tag_subject_of(('CWDataImport', 'cw_import_of', '*'), 'hidden') # in breadcrumbs
       
   486 _pvs.tag_object_of(('*', 'cw_import_of', 'CWSource'), 'hidden') # in dedicated tab
       
   487 
       
   488 
       
   489 class CWDataImportIPrevNextAdapter(navigation.IPrevNextAdapter):
       
   490     __select__ = is_instance('CWDataImport')
       
   491 
       
   492     def next_entity(self):
       
   493         if self.entity.start_timestamp is not None:
       
   494             # add NOT X eid %(e)s because > may not be enough
       
   495             rset = self._cw.execute(
       
   496                 'Any X,XSTS ORDERBY 2 LIMIT 1 WHERE X is CWDataImport, '
       
   497                 'X cw_import_of S, S eid %(s)s, NOT X eid %(e)s, '
       
   498                 'X start_timestamp XSTS, X start_timestamp > %(sts)s',
       
   499                 {'sts': self.entity.start_timestamp,
       
   500                  'e': self.entity.eid,
       
   501                  's': self.entity.cwsource.eid})
       
   502             if rset:
       
   503                 return rset.get_entity(0, 0)
       
   504 
       
   505     def previous_entity(self):
       
   506         if self.entity.start_timestamp is not None:
       
   507             # add NOT X eid %(e)s because < may not be enough
       
   508             rset = self._cw.execute(
       
   509                 'Any X,XSTS ORDERBY 2 DESC LIMIT 1 WHERE X is CWDataImport, '
       
   510                 'X cw_import_of S, S eid %(s)s, NOT X eid %(e)s, '
       
   511                 'X start_timestamp XSTS, X start_timestamp < %(sts)s',
       
   512                 {'sts': self.entity.start_timestamp,
       
   513                  'e': self.entity.eid,
       
   514                  's': self.entity.cwsource.eid})
       
   515             if rset:
       
   516                 return rset.get_entity(0, 0)
       
   517 
       
   518 class CWDataImportStatusFacet(facet.AttributeFacet):
       
   519     __regid__ = 'datafeed.dataimport.status'
       
   520     __select__ = is_instance('CWDataImport')
       
   521     rtype = 'status'
   281 
   522 
   282 
   523 
   283 # breadcrumbs configuration ####################################################
   524 # breadcrumbs configuration ####################################################
   284 
   525 
   285 class CWsourceConfigIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
   526 class CWsourceConfigIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
   286     __select__ = is_instance('CWSourceHostConfig', 'CWSourceSchemaConfig')
   527     __select__ = is_instance('CWSourceHostConfig', 'CWSourceSchemaConfig')
   287     def parent_entity(self):
   528     def parent_entity(self):
   288         return self.entity.cwsource
   529         return self.entity.cwsource
       
   530 
       
   531 class CWDataImportIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
       
   532     __select__ = is_instance('CWDataImport')
       
   533     def parent_entity(self):
       
   534         return self.entity.cw_import_of[0]