diff -r cb8ccbef8fa5 -r a5e6acffde30 web/views/baseviews.py --- a/web/views/baseviews.py Wed Feb 18 19:19:57 2009 +0100 +++ b/web/views/baseviews.py Wed Feb 18 19:26:48 2009 +0100 @@ -14,21 +14,15 @@ __docformat__ = "restructuredtext en" from warnings import warn -from time import timezone from rql import nodes -from logilab.common.decorators import cached -from logilab.mtconverter import TransformError, html_escape, xml_escape +from logilab.mtconverter import TransformError, html_escape -from cubicweb import Unauthorized, NoSelectableObject, typed_eid -from cubicweb.selectors import (yes, empty_rset, nonempty_rset, one_line_rset, - non_final_entity, match_search_state, match_form_params) +from cubicweb import Unauthorized, NoSelectableObject +from cubicweb.selectors import yes, empty_rset from cubicweb.view import EntityView, AnyRsetView, View -from cubicweb.common.uilib import (cut, printable_value, UnicodeCSVWriter, - ajax_replace_url, rql_for_eid, simple_sgml_tag) -from cubicweb.web.httpcache import MaxAgeHTTPCacheManager -from cubicweb.web.views import vid_from_rset, linksearch_select_url +from cubicweb.common.uilib import cut, printable_value _ = unicode @@ -87,19 +81,6 @@ return self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime)) - -class EditableFinalView(FinalView): - """same as FinalView but enables inplace-edition when possible""" - id = 'editable-final' - - def cell_call(self, row, col, props=None, displaytime=False): - etype = self.rset.description[row][col] - value = self.rset.rows[row][col] - entity, rtype = self.rset.related_entity(row, col) - if entity is not None: - self.w(entity.view('reledit', rtype=rtype)) - else: - super(EditableFinalView, self).cell_call(row, col, props, displaytime) PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity', 'owned_by', 'created_by', @@ -171,9 +152,8 @@ def iter_attributes(self, entity): for rschema, targetschema in entity.e_schema.attribute_definitions(): - attr = rschema.type - if attr in self.skip_attrs: - continue + if rschema.type in self.skip_attrs: + continue yield rschema, targetschema def iter_relations(self, entity): @@ -298,36 +278,6 @@ label = display_name(self.req, rschema.type, role) self.field(label, value, show_label=show_label, w=self.w, tr=False) - -class SideBoxView(EntityView): - """side box usually displaying some related entities in a primary view""" - id = 'sidebox' - - def call(self, boxclass='sideBox', title=u''): - """display a list of entities by calling their view - """ - if title: - self.w(u'
%s
' % title) - self.w(u'
' % boxclass) - # if not too much entities, show them all in a list - maxrelated = self.req.property_value('navigation.related-limit') - if self.rset.rowcount <= maxrelated: - if len(self.rset) == 1: - self.wview('incontext', self.rset, row=0) - elif 1 < len(self.rset) < 5: - self.wview('csv', self.rset) - else: - self.wview('simplelist', self.rset) - # else show links to display related entities - else: - self.rset.limit(maxrelated) - rql = self.rset.printable_rql(encoded=False) - self.wview('simplelist', self.rset) - self.w(u'[%s]' % (self.build_url(rql=rql), - self.req._('see them all'))) - self.w(u'
\n
\n') - - class SecondaryView(EntityView): id = 'secondary' @@ -341,6 +291,7 @@ self.w(u' ') self.wview('oneline', self.rset, row=row, col=col) + class OneLineView(EntityView): id = 'oneline' title = _('oneline') @@ -353,6 +304,7 @@ self.w(html_escape(self.view('text', self.rset, row=row, col=col))) self.w(u'') + class TextView(EntityView): """the simplest text view for an entity""" id = 'text' @@ -415,7 +367,8 @@ def cell_call(self, row, col): entity = self.entity(row, col) self.w(entity.dc_title()) - + + class OutOfContextTextView(InContextTextView): id = 'textoutofcontext' @@ -443,8 +396,9 @@ self.w(u'' % self.entity(row, col).absolute_url()) self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col))) self.w(u'') + -# list and table related views ################################################ +# list views ################################################################## class ListView(EntityView): id = 'list' @@ -545,387 +499,7 @@ def cell_call(self, row, col): self.wview('incontext', self.rset, row=row, col=col) - -# xml and xml/rss views ####################################################### - -class XmlView(EntityView): - id = 'xml' - title = _('xml') - templatable = False - content_type = 'text/xml' - xml_root = 'rset' - item_vid = 'xmlitem' - - def cell_call(self, row, col): - self.wview(self.item_vid, self.rset, row=row, col=col) - - def call(self): - """display a list of entities by calling their view""" - self.w(u'\n' % self.req.encoding) - self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.rset))) - for i in xrange(self.rset.rowcount): - self.cell_call(i, 0) - self.w(u'\n' % self.xml_root) - - -class XmlItemView(EntityView): - id = 'xmlitem' - - def cell_call(self, row, col): - """ element as an item for an xml feed """ - entity = self.complete_entity(row, col) - self.w(u'<%s>\n' % (entity.e_schema)) - for rschema, attrschema in entity.e_schema.attribute_definitions(): - attr = rschema.type - try: - value = entity[attr] - except KeyError: - # Bytes - continue - if value is not None: - if attrschema == 'Bytes': - from base64 import b64encode - value = '' % b64encode(value.getvalue()) - elif isinstance(value, basestring): - value = xml_escape(value) - self.w(u' <%s>%s\n' % (attr, value, attr)) - self.w(u'\n' % (entity.e_schema)) - - - -class XMLRsetView(AnyRsetView): - """dumps xml in CSV""" - id = 'rsetxml' - title = _('xml export') - templatable = False - content_type = 'text/xml' - xml_root = 'rset' - - def call(self): - w = self.w - rset, descr = self.rset, self.rset.description - eschema = self.schema.eschema - labels = self.columns_labels(False) - w(u'\n' % self.req.encoding) - w(u'<%s query="%s">\n' % (self.xml_root, html_escape(rset.printable_rql()))) - for rowindex, row in enumerate(self.rset): - w(u' \n') - for colindex, val in enumerate(row): - etype = descr[rowindex][colindex] - tag = labels[colindex] - attrs = {} - if '(' in tag: - attrs['expr'] = tag - tag = 'funccall' - if val is not None and not eschema(etype).is_final(): - attrs['eid'] = val - # csvrow.append(val) # val is eid in that case - val = self.view('textincontext', rset, - row=rowindex, col=colindex) - else: - val = self.view('final', rset, displaytime=True, - row=rowindex, col=colindex, format='text/plain') - w(simple_sgml_tag(tag, val, **attrs)) - w(u' \n') - w(u'\n' % self.xml_root) - - -class RssView(XmlView): - id = 'rss' - title = _('rss') - templatable = False - content_type = 'text/xml' - http_cache_manager = MaxAgeHTTPCacheManager - cache_max_age = 60*60*2 # stay in http cache for 2 hours by default - - def cell_call(self, row, col): - self.wview('rssitem', self.rset, row=row, col=col) - - def call(self): - """display a list of entities by calling their view""" - req = self.req - self.w(u'\n' % req.encoding) - self.w(u'''''') - self.w(u' \n' % html_escape(req.url())) - self.w(u' %s RSS Feed\n' % html_escape(self.page_title())) - self.w(u' %s\n' % html_escape(req.form.get('vtitle', ''))) - params = req.form.copy() - params.pop('vid', None) - self.w(u' %s\n' % html_escape(self.build_url(**params))) - self.w(u' \n') - self.w(u' \n') - for entity in self.rset.entities(): - self.w(u' \n' % html_escape(entity.absolute_url())) - self.w(u' \n') - self.w(u' \n') - self.w(u' \n') - for i in xrange(self.rset.rowcount): - self.cell_call(i, 0) - self.w(u'') - - -class RssItemView(EntityView): - id = 'rssitem' - date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600) - - def cell_call(self, row, col): - entity = self.complete_entity(row, col) - self.w(u'\n' % html_escape(entity.absolute_url())) - self._marker('title', entity.dc_long_title()) - self._marker('link', entity.absolute_url()) - self._marker('description', entity.dc_description()) - self._marker('dc:date', entity.dc_date(self.date_format)) - if entity.creator: - self.w(u'') - self._marker('name', entity.creator.name()) - email = entity.creator.get_email() - if email: - self._marker('email', email) - self.w(u'') - self.w(u'\n') - - def _marker(self, marker, value): - if value: - self.w(u' <%s>%s\n' % (marker, html_escape(value), marker)) - - -class CSVMixIn(object): - """mixin class for CSV views""" - templatable = False - content_type = "text/comma-separated-values" - binary = True # avoid unicode assertion - csv_params = {'dialect': 'excel', - 'quotechar': '"', - 'delimiter': ';', - 'lineterminator': '\n'} - - def set_request_content_type(self): - """overriden to set a .csv filename""" - self.req.set_content_type(self.content_type, filename='cubicwebexport.csv') - - def csvwriter(self, **kwargs): - params = self.csv_params.copy() - params.update(kwargs) - return UnicodeCSVWriter(self.w, self.req.encoding, **params) - - -class CSVRsetView(CSVMixIn, AnyRsetView): - """dumps rset in CSV""" - id = 'csvexport' - title = _('csv export') - - def call(self): - writer = self.csvwriter() - writer.writerow(self.columns_labels()) - rset, descr = self.rset, self.rset.description - eschema = self.schema.eschema - for rowindex, row in enumerate(rset): - csvrow = [] - for colindex, val in enumerate(row): - etype = descr[rowindex][colindex] - if val is not None and not eschema(etype).is_final(): - # csvrow.append(val) # val is eid in that case - content = self.view('textincontext', rset, - row=rowindex, col=colindex) - else: - content = self.view('final', rset, - displaytime=True, format='text/plain', - row=rowindex, col=colindex) - csvrow.append(content) - writer.writerow(csvrow) - - -class CSVEntityView(CSVMixIn, EntityView): - """dumps rset's entities (with full set of attributes) in CSV""" - id = 'ecsvexport' - title = _('csv entities export') - - def call(self): - """ - the generated CSV file will have a table per entity type - found in the resultset. ('table' here only means empty - lines separation between table contents) - """ - req = self.req - rows_by_type = {} - writer = self.csvwriter() - rowdef_by_type = {} - for index in xrange(len(self.rset)): - entity = self.complete_entity(index) - if entity.e_schema not in rows_by_type: - rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions() - if at != 'Bytes'] - rows_by_type[entity.e_schema] = [[display_name(req, rschema.type) - for rschema in rowdef_by_type[entity.e_schema]]] - rows = rows_by_type[entity.e_schema] - rows.append([entity.printable_value(rs.type, format='text/plain') - for rs in rowdef_by_type[entity.e_schema]]) - for etype, rows in rows_by_type.items(): - writer.writerows(rows) - # use two empty lines as separator - writer.writerows([[], []]) - - -## Work in progress ########################################################### - -class SearchForAssociationView(EntityView): - """view called by the edition view when the user asks - to search for something to link to the edited eid - """ - id = 'search-associate' - __select__ = (one_line_rset() & match_search_state('linksearch') - & non_final_entity()) - - title = _('search for association') - - def cell_call(self, row, col): - rset, vid, divid, paginate = self.filter_box_context_info() - self.w(u'
' % divid) - self.pagination(self.req, rset, w=self.w) - self.wview(vid, rset, 'noresult') - self.w(u'
') - - @cached - def filter_box_context_info(self): - entity = self.entity(0, 0) - role, eid, rtype, etype = self.req.search_state[1] - assert entity.eid == typed_eid(eid) - # the default behaviour is to fetch all unrelated entities and display - # them. Use fetch_order and not fetch_unrelated_order as sort method - # since the latter is mainly there to select relevant items in the combo - # box, it doesn't give interesting result in this context - rql = entity.unrelated_rql(rtype, etype, role, - ordermethod='fetch_order', - vocabconstraints=False) - rset = self.req.execute(rql, {'x' : entity.eid}, 'x') - #vid = vid_from_rset(self.req, rset, self.schema) - return rset, 'list', "search-associate-content", True - - -class OutOfContextSearch(EntityView): - id = 'outofcontext-search' - def cell_call(self, row, col): - entity = self.entity(row, col) - erset = entity.as_rset() - if self.req.match_search_state(erset): - self.w(u'%s [...]' % ( - html_escape(linksearch_select_url(self.req, erset)), - self.req._('select this entity'), - html_escape(entity.view('textoutofcontext')), - html_escape(entity.absolute_url(vid='primary')), - self.req._('view detail for this entity'))) - else: - entity.view('outofcontext', w=self.w) - - -class EditRelationView(EntityView): - """Note: This is work in progress - - This view is part of the edition view refactoring. - It is still too big and cluttered with strange logic, but it's a start - - The main idea is to be able to call an edition view for a specific - relation. For example : - self.wview('editrelation', person_rset, rtype='firstname') - self.wview('editrelation', person_rset, rtype='works_for') - """ - id = 'editrelation' - - __select__ = match_form_params('rtype') - - # TODO: inlineview, multiple edit, (widget view ?) - def cell_call(self, row, col, rtype=None, role='subject', targettype=None, - showlabel=True): - self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') ) - entity = self.entity(row, col) - rtype = self.req.form.get('rtype', rtype) - showlabel = self.req.form.get('showlabel', showlabel) - assert rtype is not None, "rtype is mandatory for 'edirelation' view" - targettype = self.req.form.get('targettype', targettype) - role = self.req.form.get('role', role) - category = entity.rtags.get_category(rtype, targettype, role) - if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final(): - if hasattr(entity, '%s_format' % rtype): - formatwdg = entity.get_widget('%s_format' % rtype, role) - self.w(formatwdg.edit_render(entity)) - self.w(u'
') - wdg = entity.get_widget(rtype, role) - if showlabel: - self.w(u'%s' % wdg.render_label(entity)) - self.w(u'%s %s %s' % - (wdg.render_error(entity), wdg.edit_render(entity), - wdg.render_help(entity),)) - else: - self._render_generic_relation(entity, rtype, role) - - def _render_generic_relation(self, entity, relname, role): - text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role)) - # pending operations - operations = self.req.get_pending_operations(entity, relname, role) - if operations['insert'] or operations['delete'] or 'unfold' in self.req.form: - self.w(u'

%s

' % text) - self._render_generic_relation_form(operations, entity, relname, role) - else: - divid = "%s%sreledit" % (relname, role) - url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation', - {'unfold' : 1, 'relname' : relname, 'role' : role}) - self.w(u'%s' % (url, text)) - self.w(u'
' % divid) - - - def _build_opvalue(self, entity, relname, target, role): - if role == 'subject': - return '%s:%s:%s' % (entity.eid, relname, target) - else: - return '%s:%s:%s' % (target, relname, entity.eid) - - - def _render_generic_relation_form(self, operations, entity, relname, role): - rqlexec = self.req.execute - for optype, targets in operations.items(): - for target in targets: - self._render_pending(optype, entity, relname, target, role) - opvalue = self._build_opvalue(entity, relname, target, role) - self.w(u'- ' - % (opvalue, entity.eid)) - rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x') - self.wview('oneline', rset) - # now, unrelated ones - self._render_unrelated_selection(entity, relname, role) - - def _render_pending(self, optype, entity, relname, target, role): - opvalue = self._build_opvalue(entity, relname, target, role) - self.w(u'' - % (optype, opvalue)) - if optype == 'insert': - checktext = '-' - else: - checktext = '+' - rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x') - self.w(u"""[%s""" - % (optype.capitalize(), relname, target, role, - self.view('oneline', rset))) - - def _render_unrelated_selection(self, entity, relname, role): - rschema = self.schema.rschema(relname) - if role == 'subject': - targettypes = rschema.objects(entity.e_schema) - else: - targettypes = rschema.subjects(entity.e_schema) - self.w(u'