dataobject.py 14.8 KB
Newer Older
1
# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# contact http://www.logilab.fr -- mailto:contact@logilab.fr
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""cubicweb-seda views for data objects (BinaryDataObject / PhysicalDataObject)"""

18
19
import json

20
21
from six import text_type

22
23
from logilab.mtconverter import xml_escape

Sylvain Thénault's avatar
Sylvain Thénault committed
24
from cubicweb import tags, view, _
25
from cubicweb.predicates import match_form_params, is_instance
26
from cubicweb.web.views import uicfg, tabs, ibreadcrumbs
27
28

from cubes.relationwidget import views as rwdg
29
from cubes.skos.views import widgets as skos
30

31
32
33
34
35
from ..xsd2yams import SCHEME_FROM_CONTAINER
from ..entities import parent_and_container, simplified_profile, full_seda2_profile
from . import rtags_from_xsd_element, add_subobject_link
from . import viewlib
from . import uicfg as sedauicfg  # noqa - ensure those rules are defined first
36

37

38
pvs = uicfg.primaryview_section
39
pvdc = uicfg.primaryview_display_ctrl
40
41
rec = uicfg.reledit_ctrl
afs = uicfg.autoform_section
42
43
affk = uicfg.autoform_field_kwargs

44

45
46
47
48
49
50
51
52
53
54
55
56
def scheme_rql_expr(container, etype, rtype):
    """Return RQL expression (right part of the WHERE) to retrieve the scheme (mapped as 'CS'
    variable) for some (entity type / relation type) couple, in the context of the given
    `container`.
    """
    try:
        scheme_from_container = SCHEME_FROM_CONTAINER[(etype, rtype)]
    except KeyError:
        scheme_from_container = SCHEME_FROM_CONTAINER[rtype]
    return scheme_from_container[container.cw_etype]


57
class ContainedRelationFacetWidget(rwdg.RelationFacetWidget):
58

59
    def _render_triggers(self, w, domid, form, field, rtype):
60
        req = form._cw
61
        parent, container = parent_and_container(form.edited_entity)
62
        assert container is not None
63
64
65
        rql_expr = scheme_rql_expr(container, form.edited_entity.cw_etype, field.name)
        if not req.execute('Any CS WHERE ' + rql_expr, {'container': container.eid}):
            scheme_relations = [x for x in rql_expr.split() if x.startswith('seda_')]
66
67
68
69
            if not scheme_relations:
                w(req._('there is no scheme available for this relation. '
                        'Contact the site administrator.'))
                return
70
71
            if len(scheme_relations) == 1:
                scheme_relation = req._(scheme_relations[0])
72
            else:
73
74
75
76
77
78
                scheme_relation = req.__(scheme_relations[0] + '_object')
            w(req._('you must specify a scheme for {0} to select a value').format(
                scheme_relation))
        else:
            return super(ContainedRelationFacetWidget, self)._render_triggers(
                w, domid, form, field, rtype)
79
80
81
82
83
84
85

    def trigger_search_url(self, entity, url_params):
        """Overriden to add information about who is the container

        This information will be used later for proper vocabulary computation.
        """
        # first retrieve the container entity
86
        _, container = parent_and_container(entity)
87
        assert container is not None
88
        # and put it as an extra url param
89
        url_params['container'] = text_type(container.eid)
90
91
92
        return super(ContainedRelationFacetWidget, self).trigger_search_url(entity, url_params)


93
94
95
96
97
98
99
100
101
102
for key in SCHEME_FROM_CONTAINER:
    try:
        etype, rtype = key
    except ValueError:
        etype = '*'
        rtype = key
    affk.tag_subject_of((etype, rtype, '*'),
                        {'widget': ContainedRelationFacetWidget(dialog_options={'width': 800})})


103
class ContainedSearchForRelatedEntitiesView(skos.SearchForRelatedConceptsView):
104

105
    __select__ = skos.SearchForRelatedConceptsView.__select__ & match_form_params('container')
106

107
    def constrained_rql(self):
108
109
110
        container = self._cw.entity_from_eid(int(self._cw.form['container']))
        rql_expr = scheme_rql_expr(container, self.schema_rdef.subject, self.schema_rdef.rtype)
        return rql_expr + ', O in_scheme CS', {'container': container.eid}
111
112


113
pvs.tag_object_of(('*', 'seda_data_object_reference_id', '*'),
114
                  'relations')
115
pvdc.tag_object_of(('*', 'seda_data_object_reference_id', '*'),
116
117
118
                   {'vid': 'autolimited',
                    'subvid': 'seda.object-ref.archive-unit',
                    'label': _('referenced by:')})
119

120
pvs.tag_subject_of(('*', 'seda_algorithm', '*'), 'attributes')
121
pvs.tag_object_of(('*', 'seda_target', '*'), 'hidden')  # in the relationship tab
122
123
# hide file_category from the main tab, it lives in the format tab
pvs.tag_subject_of(('SEDABinaryDataObject', 'file_category', '*'), 'hidden')
124

125
126
127
128
129
130
for rtype in ('seda_compressed', 'seda_data_object_version_from'):
    # hide relation from autoform because of limitation of _container_eid
    afs.tag_object_of(('*', rtype, '*'), 'main', 'hidden')
    pvs.tag_object_of(('*', rtype, '*'), 'attributes')
    rec.tag_object_of(('*', rtype, '*'),
                      {'rvid': 'seda.reledit.complexlink',
131
                       'novalue_label': ' '})
132
133
134

rec.tag_subject_of(('SEDABinaryDataObject', 'seda_alt_binary_data_object_attachment', '*'),
                   {'rvid': 'seda.reledit.alternative',
135
                    'novalue_label': ' '})
136

137
138
rec.tag_object_of(('*', 'seda_compressed', '*'),
                  {'rvid': 'seda.reledit.text',
139
                   'novalue_label': ' '})
140

141

142
143
def uri_cardinality_vocabulary(form, field):
    req = form._cw
144
145
146
147
148
149
150
151
152
153
    if form.edited_entity.has_eid():
        parent_type = form.edited_entity.cw_adapt_to('IContained').parent.cw_etype
    else:
        try:
            # inlined creation form
            parent_type = json.loads(req.form['arg'][1])
        except KeyError:
            # edition through reledit
            parent_eid = req.form['eid']
            parent_type = req.describe(int(parent_eid))[0]
154
155
156
157
    if parent_type in ('SEDABinaryDataObject', 'SEDAAltBinaryDataObjectAttachment'):
        return [u'1']
    return [u'0..1', u'1']

158

159
160
161
162
affk.tag_attribute(('SEDAUri', 'user_cardinality'),
                   {'choices': uri_cardinality_vocabulary})


163
164
165
166
bdo_ordered_fields = [
    ('user_cardinality', 'subject'),
    ('user_annotation', 'subject'),
    ('filename', 'subject'),
167
    ('seda_date_created_by_application', 'object'),
168
169
170
171
172
173
174
175
    ('seda_compressed', 'object'),
    ('seda_data_object_version_from', 'object'),
    ('seda_algorithm', 'object'),
]
affk.set_fields_order('SEDABinaryDataObject', bdo_ordered_fields)
pvdc.set_fields_order('SEDABinaryDataObject', bdo_ordered_fields)


176
class BinaryDataObjectTabbedPrimaryView(tabs.TabbedPrimaryView):
177

178
    __select__ = is_instance('SEDABinaryDataObject')
179
180
    tabs = [
        'main_tab',
181
        _('seda_bdo_format_identification'),
182
        _('seda_bdo_file_information'),
183
        _('seda_do_relations'),
184
185
186
    ]


187
188
189
190
191
192
193
194
195
196
197
def _setup_format_tab(rsection, display_ctrl):
    # hide format_id / mime_type, in favor of custom 'file_category'. Actual
    # format_id and mime_type will be derived from that value (XXX hook or profile
    # gen time ?)
    rsection.tag_object_of(('*', 'seda_format_id_from', 'SEDABinaryDataObject'), 'hidden')
    rsection.tag_object_of(('*', 'seda_mime_type_from', 'SEDABinaryDataObject'), 'hidden')
    rsection.tag_subject_of(('SEDABinaryDataObject', 'file_category', '*'), 'attributes')
    # dunno why it crashes without this
    display_ctrl.tag_subject_of(('SEDABinaryDataObject', 'file_category', '*'), {'order': 0})


198
class BinaryDataObjectFormatIdentificationTab(viewlib.PrimaryTabWithoutBoxes):
199
    """Display format identification information of a binary data object"""
200

201
    __regid__ = 'seda_bdo_format_identification'
202
    __select__ = is_instance('SEDABinaryDataObject')
203
204

    rsection, display_ctrl = rtags_from_xsd_element('SEDABinaryDataObject', 'FormatIdentification')
205
    _setup_format_tab(rsection, display_ctrl)
206
207


208
209
210
211
212
class SimplifiedBinaryDataObjectFormatIdentificationTab(BinaryDataObjectFormatIdentificationTab):

    __select__ = BinaryDataObjectFormatIdentificationTab.__select__ & simplified_profile()

    rsection, display_ctrl = rtags_from_xsd_element('SEDABinaryDataObject', 'FormatIdentification')
213
    _setup_format_tab(rsection, display_ctrl)
214
    rsection.tag_object_of(('*', 'seda_format_litteral', 'SEDABinaryDataObject'), 'hidden')
215
216


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def file_category_vocabulary(form, field):
    rset = form._cw.execute(
        'Any C,CLL,E,ELL ORDERBY CLL,ELL WHERE '
        'C in_scheme CS, CS scheme_relation_type CR, CR name "file_category", '
        'NOT C broader_concept SC, E broader_concept C, '
        'CL label_of C, CL label CLL,'
        'EL label_of E, EL label ELL'
    )
    values = []
    current_category = None
    for category, category_label, extension, extension_label in rset:
        if current_category is None or current_category != category:
            current_category = category
            values.append((category_label, str(category)))
        values.append((u'{} > {}'.format(category_label, extension_label),
                       str(extension)))
    return values


affk.tag_subject_of(('SEDABinaryDataObject', 'file_category', '*'),
                    {'choices': file_category_vocabulary})
238
239
rec.tag_subject_of(('SEDABinaryDataObject', 'file_category', '*'),
                   {'novalue_label': ' '})
240
241


242
class BinaryDataObjectFileInfoTab(viewlib.PrimaryTabWithoutBoxes):
243
    """Display file information of a binary data object"""
244

245
    __regid__ = 'seda_bdo_file_information'
246
    __select__ = is_instance('SEDABinaryDataObject') & full_seda2_profile()
247
248
249
250

    rsection, display_ctrl = rtags_from_xsd_element('SEDABinaryDataObject', 'FileInfo')


251
class PhysicalDataObjectTabbedPrimaryView(tabs.TabbedPrimaryView):
252

253
    __select__ = is_instance('SEDAPhysicalDataObject')
254
255
    tabs = [
        'main_tab',
256
        _('seda_pdo_dimensions'),
257
        _('seda_do_relations'),
258
    ]
259
260


261
class PhysicalDataObjectDimensionsTab(viewlib.PrimaryTabWithoutBoxes):
262
    """Display physical dimensions of a physical data object"""
263

264
    __regid__ = 'seda_pdo_dimensions'
265
    __select__ = is_instance('SEDAPhysicalDataObject')
266
267
268
269

    rsection, display_ctrl = rtags_from_xsd_element('SEDAPhysicalDataObject', 'PhysicalDimensions')


270
class DataObjectRelationsTab(viewlib.PrimaryTabWithoutBoxes):
271
    """Display relations of a binary or physical data object"""
272

273
    __regid__ = 'seda_do_relations'
274
275
    __select__ = (is_instance('SEDABinaryDataObject', 'SEDAPhysicalDataObject')
                  & full_seda2_profile())
276

277
278
279
280
281
    _('creating SEDARelationship (SEDARelationship seda_relationship '
      'SEDABinaryDataObject %(linkto)s)')
    _('creating SEDARelationship (SEDARelationship seda_relationship '
      'SEDAPhysicalDataObject %(linkto)s)')

282
283
284
285
286
287
288
289
290
291
292
293
    def entity_call(self, entity):
        rschema = self._cw.vreg.schema.rschema('seda_relationship')
        if rschema.has_perm(self._cw, 'add', toeid=entity.eid):
            urlparams = {'__redirectparams': 'tab=' + self.__regid__}
            self.w(add_subobject_link(entity, 'seda_relationship', 'object', urlparams,
                                      msg=self._cw._('add a SEDARelationship'),
                                      klass='btn btn-success pull-right'))
            self.w(tags.div(klass='clearfix'))
        rset = entity.related('seda_relationship', 'object')
        if rset:
            self._cw.view('list', rset=rset, parent=entity, w=self.w, tabid=self.__regid__,
                          subvid='seda.listitem')
294
295
296
297
298
299
300
        rset = entity.related('seda_target', 'object')
        if rset:
            self.w(u'<h2>{0}</h2>'.format(self._cw._('Relationship target of')))
            self.w(u'<div>{0}</div>'.format(
                self._cw._('This object is used as a relationship target of the following '
                           'entities')))
            self._cw.view('list', rset=rset, subvid='seda.relationship.reverse', w=self.w)
301
302
303


class RelationshipBusinessValueLinkEntityView(viewlib.BusinessValueLinkEntityView):
304

305
    __select__ = is_instance('SEDARelationship')
306

307
308
309
310
311
312
    def entity_value(self, entity):
        target = entity.seda_target[0] if entity.seda_target else None
        if target:
            value = tags.a(target.dc_title(), href=target.absolute_url())
        else:
            value = xml_escape(self._cw._('<no data-object specified>'))
313
314
        if entity.seda_type_relationship:
            concept = entity.seda_type_relationship[0]
315
            msg = self._cw._(', of relationship type %s') % concept.label()
316
        else:
317
318
319
            msg = self._cw._(', no relationship type specified')
        value += xml_escape(msg)
        return value
320
321


322
323
324
325
326
327
328
329
330
331
332
333
334
335
class CompressedBusinessValueEntityView(viewlib.BusinessValueEntityView):
    __select__ = is_instance('SEDACompressed')

    def entity_value(self, entity):
        if entity.compressed is None:
            value = self._cw.__('indifferent')
        else:
            value = self._cw.__('yes' if entity.compressed else 'no')
        if entity.seda_algorithm:
            algorithm = entity.seda_algorithm[0].label()
            value += self._cw._(u', using {algorithm}').format(algorithm=algorithm)
        return value


336
class RelationshipReverseEntityView(view.EntityView):
337

338
339
340
341
342
343
344
345
346
347
348
349
350
351
    __regid__ = 'seda.relationship.reverse'
    __select__ = is_instance('SEDARelationship')

    def entity_call(self, entity):
        target = entity.seda_relationship[0]
        self.w(tags.a(target.dc_title(), href=target.absolute_url()))
        if entity.seda_type_relationship:
            concept = entity.seda_type_relationship[0]
            msg = self._cw._(', of relationship type %s') % concept.label()
        else:
            msg = self._cw._(', no relationship type specified')
        self.w(xml_escape(msg))


352
class UnitBusinessValueEntityView(viewlib.BusinessValueEntityView):
353

354
    __select__ = is_instance('SEDAWidth', 'SEDAHeight', 'SEDADepth',
355
356
357
358
359
360
361
362
363
                             'SEDADiameter', 'SEDALength', 'SEDAThickness', 'SEDAWeight')

    def entity_call(self, entity):
        super(UnitBusinessValueEntityView, self).entity_call(entity)
        if entity.seda_unit:
            unit = self._cw._('unit: {0}').format(entity.seda_unit[0].label())
        else:
            unit = self._cw._('<no unit specified>')
        self.w(u' (%s)' % xml_escape(unit))
364
365
366
367
368
369
370
371
372
373


class IBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
    """Override adapter from compound when BDO is within a simplified profile to display the archive
    unit as parent.
    """
    __select__ = is_instance('SEDABinaryDataObject') & simplified_profile()

    def parent_entity(self):
        return self.entity.cw_adapt_to('ITreeBase').parent()