コード例 #1
0
ファイル: entity_index.py プロジェクト: NinaBrundke/OpenAtlas
def get_table(view: str) -> Table:
    table = Table(g.table_headers[view])
    if view == 'file':
        table.header = ['date'] + table.header
        file_stats = get_file_stats()
        for entity in Entity.get_by_class('file', nodes=True):
            date = 'N/A'
            if entity.id in file_stats:
                date = format_date(
                    datetime.datetime.utcfromtimestamp(
                        file_stats[entity.id]['date']))
            table.rows.append([
                date,
                link(entity),
                entity.print_standard_type(),
                convert_size(file_stats[entity.id]['size']) if entity.id
                in file_stats else 'N/A', file_stats[entity.id]['ext']
                if entity.id in file_stats else 'N/A', entity.description
            ])
    elif view == 'reference_system':
        for system in g.reference_systems.values():
            table.rows.append([
                link(system), system.count if system.count else '',
                external_url(system.website_url),
                external_url(system.resolver_url), system.placeholder,
                link(g.nodes[system.precision_default_id])
                if system.precision_default_id else '', system.description
            ])
    else:
        classes = ['place'] if view == 'place' else g.view_class_mapping[view]
        entities = Entity.get_by_class(classes, nodes=True)
        table.rows = [get_base_table_data(item) for item in entities]
    return table
コード例 #2
0
def build_table_form(class_: str, linked_entities: List[Entity]) -> str:
    """ Returns a form with a list of entities with checkboxes."""
    if class_ == 'file':
        entities = Entity.get_by_class('file', nodes=True)
    elif class_ == 'place':
        entities = Entity.get_by_class('place', nodes=True, aliases=True)
    else:
        entities = Entity.get_by_view(class_)

    linked_ids = [entity.id for entity in linked_entities]
    table = Table([''] + g.table_headers[class_], order=[[1, 'asc']])
    file_stats = get_file_stats() if class_ == 'file' else None
    for entity in entities:
        if entity.id in linked_ids:
            continue  # Don't show already linked entries
        input_ = '<input id="selection-{id}" name="values" type="checkbox" value="{id}">'.format(
            id=entity.id)
        table.rows.append([input_] + get_base_table_data(entity, file_stats))
    if not table.rows:
        return uc_first(_('no entries'))
    return """
        <form class="table" id="checkbox-form" method="post">
            <input id="csrf_token" name="csrf_token" type="hidden" value="{token}">
            <input id="checkbox_values" name="checkbox_values" type="hidden">
            {table}
            <input id="save" class="{class_}" name="save" type="submit" value="{link}">
        </form>""".format(link=uc_first(_('link')),
                          token=generate_csrf(),
                          class_=app.config['CSS']['button']['primary'],
                          table=table.display(class_))
コード例 #3
0
ファイル: field.py プロジェクト: craws/OpenAtlas
 def __call__(self, field: TableField, **kwargs: Any) -> TableMultiSelect:
     if field.data and isinstance(field.data, str):
         field.data = ast.literal_eval(field.data)
     class_ = field.id if field.id != 'given_place' else 'place'
     aliases = current_user.settings['table_show_aliases']
     if class_ in ['group', 'person', 'place']:
         entities = Entity.get_by_class(class_, types=True, aliases=aliases)
     else:
         entities = Entity.get_by_view(class_, types=True, aliases=aliases)
     table = Table([''] + g.table_headers[class_],
                   order=[[0, 'desc'], [1, 'asc']],
                   defs=[{
                       'orderDataType': 'dom-checkbox',
                       'targets': 0
                   }])
     for e in entities:
         data = get_base_table_data(e, show_links=False)
         data.insert(
             0, f"""
             <input type="checkbox" id="{e.id}" value="{e.name}"
             {'checked' if field.data and e.id in field.data else ''}>""")
         table.rows.append(data)
     return super().__call__(field, **kwargs) + render_template(
         'forms/table_multi_select.html',
         field=field,
         selection=[
             e.name for e in entities if field.data and e.id in field.data
         ],
         table=table)
コード例 #4
0
ファイル: admin.py プロジェクト: acdh-oeaw/OpenAtlas
def admin_file_delete(filename: str) -> Response:  # pragma: no cover
    if filename != 'all':  # Delete one file
        try:
            (app.config['UPLOAD_DIR'] / filename).unlink()
            flash(f"{filename} {_('was deleted')}", 'info')
        except Exception as e:
            logger.log('error', 'file', f'deletion of {filename} failed', e)
            flash(_('error file delete'), 'error')
        return redirect(f"{url_for('admin_orphans')}#tab-orphaned-files")

    if is_authorized('admin'):  # Delete all files with no corresponding entity
        entity_file_ids = [entity.id for entity in Entity.get_by_class('file')]
        for file in app.config['UPLOAD_DIR'].iterdir():
            if file.name != '.gitignore' and int(
                    file.stem) not in entity_file_ids:
                try:
                    (app.config['UPLOAD_DIR'] / file.name).unlink()
                except Exception as e:
                    logger.log(
                        'error',
                        'file',
                        f'deletion of {file.name} failed',
                        e)
                    flash(_('error file delete'), 'error')
    return redirect(f"{url_for('admin_orphans')}#tab-orphaned-files")
コード例 #5
0
def create_resized_images() -> None:
    from openatlas.models.entity import Entity
    for entity in Entity.get_by_class('file'):
        if entity.id in g.file_stats \
                and g.file_stats[entity.id]['ext'] \
                in app.config['ALLOWED_IMAGE_EXT']:
            resize_image(f"{entity.id}{g.file_stats[entity.id]['ext']}")
コード例 #6
0
def get_entities_by_view_classes(codes: list[str]) -> list[Entity]:
    codes = list(g.view_class_mapping) if 'all' in codes else codes
    if not all(c in g.view_class_mapping for c in codes):
        raise InvalidCodeError
    view_classes = flatten_list_and_remove_duplicates(
        [g.view_class_mapping[view] for view in codes])
    return Entity.get_by_class(view_classes, types=True, aliases=True)
コード例 #7
0
def get_table(view: str) -> Table:
    header = g.table_headers[view]
    if view == 'file':
        header = ['date'] + header
        if session['settings']['image_processing'] \
                and current_user.settings['table_show_icons']:
            header.insert(1, _('icon'))
    table = Table(header)
    if view == 'file':
        if not g.file_stats:
            g.file_stats = get_file_stats()
        for entity in Entity.get_by_class('file', nodes=True):
            date = 'N/A'
            if entity.id in g.file_stats:
                date = format_date(
                    datetime.datetime.utcfromtimestamp(
                        g.file_stats[entity.id]['date']))
            data = [
                date,
                link(entity),
                link(entity.standard_type), g.file_stats[entity.id]['size']
                if entity.id in g.file_stats else 'N/A',
                g.file_stats[entity.id]['ext']
                if entity.id in g.file_stats else 'N/A', entity.description
            ]
            if session['settings']['image_processing'] \
                    and current_user.settings['table_show_icons']:
                data.insert(1, file_preview(entity.id))
            table.rows.append(data)

    elif view == 'reference_system':
        for system in g.reference_systems.values():
            table.rows.append([
                link(system), system.count if system.count else '',
                external_url(system.website_url),
                external_url(system.resolver_url), system.placeholder,
                link(g.nodes[system.precision_default_id])
                if system.precision_default_id else '', system.description
            ])
    else:
        classes = 'place' if view == 'place' else g.view_class_mapping[view]
        entities = Entity.get_by_class(classes, nodes=True, aliases=True)
        table.rows = [get_base_table_data(entity) for entity in entities]
    return table
コード例 #8
0
 def create_resized_images() -> None:
     from openatlas.models.entity import Entity
     from openatlas.util.util import get_file_stats
     if not g.file_stats:
         g.file_stats = get_file_stats()
     for entity in Entity.get_by_class('file'):
         if entity.id in g.file_stats \
                 and g.file_stats[entity.id]['ext'] \
                 in app.config['ALLOWED_IMAGE_EXT']:
             ImageProcessing.resize_image(
                 f"{entity.id}{g.file_stats[entity.id]['ext']}")
コード例 #9
0
ファイル: field.py プロジェクト: craws/OpenAtlas
 def __call__(self, field: TableField, **kwargs: Any) -> TableSelect:
     selection = ''
     if field.id in ('cidoc_domain', 'cidoc_property', 'cidoc_range'):
         table = Table(['code', 'name'],
                       order=[[0, 'desc']],
                       defs=[{
                           'orderDataType': 'cidoc-model',
                           'targets': [0]
                       }, {
                           'sType': 'numeric',
                           'targets': [0]
                       }])
         for id_, entity in (g.properties if field.id == 'cidoc_property'
                             else g.cidoc_classes).items():
             table.rows.append([
                 f"""
                 <a href="#" onclick="selectFromTable(
                     this,
                     '{field.id}',
                     '{id_}',
                     '{entity.code} {entity.name}');">{entity.code}</a>""",
                 entity.name
             ])
     else:
         aliases = current_user.settings['table_show_aliases']
         if 'place' in field.id \
                 or field.id in ['begins_in', 'ends_in', 'residence']:
             class_ = 'place'
             entities = Entity.get_by_class('place',
                                            types=True,
                                            aliases=aliases)
         elif field.id.startswith('event_'):
             class_ = 'event'
             entities = Entity.get_by_view('event',
                                           types=True,
                                           aliases=aliases)
         else:
             class_ = field.id
             entities = Entity.get_by_view(class_,
                                           types=True,
                                           aliases=aliases)
         table = Table(g.table_headers[class_])
         for entity in entities:
             if field.data and entity.id == int(field.data):
                 selection = entity.name
             data = get_base_table_data(entity, show_links=False)
             data[0] = self.format_name_and_aliases(entity, field.id)
             table.rows.append(data)
     return super().__call__(field, **kwargs) + render_template(
         'forms/table_select.html',
         field=field,
         table=table.display(field.id),
         selection=selection)
コード例 #10
0
 def get_untyped(hierarchy_id: int) -> list[Entity]:
     hierarchy = g.types[hierarchy_id]
     classes = hierarchy.classes
     if hierarchy.name in ('Administrative unit', 'Historical place'):
         classes = 'object_location'  # pragma: no cover
     untyped = []
     for entity in Entity.get_by_class(classes, types=True):
         linked = False
         for type_ in entity.types:
             if type_.root[0] == hierarchy_id:
                 linked = True
                 break
         if not linked:
             if classes == 'object_location':  # pragma: no cover
                 if entity.get_linked_entity('P53', True):
                     untyped.append(entity)
             else:
                 untyped.append(entity)
     return untyped
コード例 #11
0
 def get_untyped(hierarchy_id: int) -> List[Entity]:
     hierarchy = g.nodes[hierarchy_id]
     classes = [
         class_['name'] for class_ in g.nodes[hierarchy_id].forms.values()]
     if hierarchy.name in ('Administrative unit', 'Historical place'):
         classes = 'object_location'  # pragma: no cover
     untyped = []
     for entity in Entity.get_by_class(classes, nodes=True):
         linked = False
         for node in entity.nodes:
             if node.root[-1] == hierarchy_id:
                 linked = True
                 break
         if not linked:
             if classes == 'object_location':  # pragma: no cover
                 entity = entity.get_linked_entity('P53', True)
                 if entity:
                     untyped.append(entity)
             else:
                 untyped.append(entity)
     return untyped
コード例 #12
0
ファイル: form.py プロジェクト: craws/OpenAtlas
def build_table_form(class_: str, linked_entities: list[Entity]) -> str:
    """Returns a form with a list of entities with checkboxes."""
    if class_ == 'place':
        entities = Entity.get_by_class('place', types=True, aliases=True)
    else:
        entities = Entity.get_by_view(class_, types=True, aliases=True)
    linked_ids = [entity.id for entity in linked_entities]
    table = Table([''] + g.table_headers[class_], order=[[1, 'asc']])
    for entity in entities:
        if entity.id in linked_ids:
            continue  # Don't show already linked entries
        input_ = f"""
            <input
                id="selection-{entity.id}"
                name="values"
                type="checkbox" value="{entity.id}">"""
        table.rows.append([input_] +
                          get_base_table_data(entity, show_links=False))
    if not table.rows:
        return uc_first(_('no entries'))
    return render_template('forms/form_table.html',
                           table=table.display(class_))
コード例 #13
0
def admin_orphans() -> str:
    header = [
        'name', 'class', 'type', 'system type', 'created', 'updated',
        'description'
    ]
    tabs = {
        'orphans':
        Tab('orphans', table=Table(header)),
        'unlinked':
        Tab('unlinked', table=Table(header)),
        'types':
        Tab('type',
            table=Table(
                ['name', 'root'],
                [[link(type_), link(g.types[type_.root[0]])]
                 for type_ in Type.get_type_orphans()])),
        'missing_files':
        Tab('missing_files', table=Table(header)),
        'orphaned_files':
        Tab('orphaned_files', table=Table(['name', 'size', 'date', 'ext'])),
        'circular':
        Tab('circular_dependencies',
            table=Table(['entity'],
                        [[link(e)]
                         for e in Entity.get_entities_linked_to_itself()]))
    }

    for entity in filter(lambda x: not isinstance(x, ReferenceSystem),
                         Entity.get_orphans()):
        tabs['unlinked' if entity.class_.
             view else 'orphans'].table.rows.append([
                 link(entity),
                 link(entity.class_),
                 link(entity.standard_type), entity.class_.label,
                 format_date(entity.created),
                 format_date(entity.modified), entity.description
             ])

    # Orphaned file entities with no corresponding file
    entity_file_ids = []
    for entity in Entity.get_by_class('file', types=True):
        entity_file_ids.append(entity.id)
        if not get_file_path(entity):
            tabs['missing_files'].table.rows.append([
                link(entity),
                link(entity.class_),
                link(entity.standard_type), entity.class_.label,
                format_date(entity.created),
                format_date(entity.modified), entity.description
            ])

    # Orphaned files with no corresponding entity
    for file in app.config['UPLOAD_DIR'].iterdir():
        if file.name != '.gitignore' \
                and os.path.isfile(file) \
                and int(file.stem) not in entity_file_ids:
            tabs['orphaned_files'].table.rows.append([
                file.stem,
                convert_size(file.stat().st_size),
                format_date(
                    datetime.datetime.utcfromtimestamp(file.stat().st_ctime)),
                file.suffix,
                link(_('download'), url_for('download_file',
                                            filename=file.name)),
                delete_link(file.name,
                            url_for('admin_file_delete', filename=file.name))
            ])

    for tab in tabs.values():
        tab.buttons = [manual('admin/data_integrity_checks')]
        if not tab.table.rows:
            tab.content = _('Congratulations, everything looks fine!')
    if tabs['orphaned_files'].table.rows and is_authorized('admin'):
        text = uc_first(_('delete all files without corresponding entities?'))
        tabs['orphaned_files'].buttons.append(
            button(_('delete all files'),
                   url_for('admin_file_delete', filename='all'),
                   onclick=f"return confirm('{text}')"))
    return render_template(
        'tabs.html',
        tabs=tabs,
        title=_('admin'),
        crumbs=[[_('admin'), f"{url_for('admin_index')}#tab-data"],
                _('orphans')])
コード例 #14
0
    def test_event(self) -> None:
        with app.app_context():  # type: ignore
            # Create entities for file
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                actor = Entity.insert('person', 'File keeper')
                reference = Entity.insert('edition', 'Ancient Books')
                node_id = Node.get_hierarchy('Sex').subs[0]

            # Insert
            rv = self.app.get(
                url_for('insert', class_='file', origin_id=actor.id))
            assert b'+ File' in rv.data
            logo = \
                pathlib.Path(app.root_path) \
                / 'static' / 'images' / 'layout' / 'logo.png'
            with open(logo, 'rb') as img:
                rv = self.app.post(url_for('insert',
                                           class_='file',
                                           origin_id=actor.id),
                                   data={
                                       'name': 'OpenAtlas logo',
                                       'file': img
                                   },
                                   follow_redirects=True)
            assert b'An entry has been created' in rv.data
            with open(logo, 'rb') as img1, open(logo, 'rb') as img2:
                rv = self.app.post(url_for('insert',
                                           class_='file',
                                           origin_id=actor.id),
                                   data={
                                       'name': 'OpenAtlas logo',
                                       'file': [img1, img2]
                                   },
                                   follow_redirects=True)
            assert b'An entry has been created' in rv.data
            with open(logo, 'rb') as img:
                rv = self.app.post(url_for('insert',
                                           class_='file',
                                           origin_id=reference.id),
                                   data={
                                       'name': 'OpenAtlas logo',
                                       'file': img
                                   },
                                   follow_redirects=True)
            assert b'An entry has been created' in rv.data
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                files = Entity.get_by_class('file')
                file_id = files[0].id
                file_id2 = files[1].id

            # Logo
            rv = self.app.get(url_for('admin_logo'),
                              data={'file': file_id},
                              follow_redirects=True)
            assert b'OpenAtlas logo' in rv.data
            with self.app.get(
                    url_for('display_logo', filename=str(file_id) + '.png')):
                pass  # Test logo with "with" to prevent unclosed files warning
            rv = self.app.get(url_for('admin_logo', id_=file_id),
                              follow_redirects=True)
            assert b'Remove custom logo' in rv.data
            rv = self.app.get(url_for('admin_index',
                                      action="remove_logo",
                                      id_=0),
                              follow_redirects=True)
            assert b'Logo' in rv.data

            with open(
                    pathlib.Path(app.root_path) / 'views' / 'index.py', 'rb') \
                    as invalid_file:
                rv = self.app.post(url_for('insert',
                                           class_='file',
                                           origin_id=actor.id),
                                   data={
                                       'name': 'Invalid file',
                                       'file': invalid_file
                                   },
                                   follow_redirects=True)
            assert b'File type not allowed' in rv.data

            rv = self.app.post(url_for('insert',
                                       class_='file',
                                       origin_id=actor.id),
                               follow_redirects=True,
                               data={'name': 'This is not a file'})
            assert b'This field is required' in rv.data

            # View
            rv = self.app.get(url_for('entity_view', id_=file_id))
            assert b'OpenAtlas logo' in rv.data
            rv = self.app.get(url_for('entity_view', id_=file_id2))
            assert b'OpenAtlas logo' in rv.data

            with self.app.get(
                    url_for('download_file', filename=str(file_id) + '.png')):
                pass  # Calling with "with" to prevent unclosed files warning
            with self.app.get(
                    url_for('display_file', filename=str(file_id) + '.png')):
                pass  # Calling with "with" to prevent unclosed files warning

            # Index
            rv = self.app.get(url_for('index', view='file'))
            assert b'OpenAtlas logo' in rv.data

            # Set and unset as main image
            self.app.get(url_for('set_profile_image',
                                 id_=file_id,
                                 origin_id=actor.id),
                         follow_redirects=True)
            self.app.get(
                url_for('file_remove_profile_image', entity_id=actor.id))

            # Add to reference
            rv = self.app.get(
                url_for('reference_add', id_=reference.id, view='file'))
            assert b'OpenAtlas logo' in rv.data
            rv = self.app.post(url_for('reference_add',
                                       id_=reference.id,
                                       view='file'),
                               data={
                                   'file': file_id,
                                   'page': '777'
                               },
                               follow_redirects=True)
            assert b'777' in rv.data

            # Update
            rv = self.app.get(url_for('update', id_=file_id))
            assert b'OpenAtlas logo' in rv.data
            rv = self.app.post(url_for('update', id_=file_id),
                               data={'name': 'Updated file'},
                               follow_redirects=True)
            assert b'Changes have been saved' in rv.data \
                   and b'Updated file' in rv.data
            rv = self.app.get(url_for('file_add', id_=file_id, view='actor'))
            assert b'Link actor' in rv.data
            rv = self.app.post(url_for('file_add', id_=file_id, view='actor'),
                               data={'checkbox_values': [actor.id]},
                               follow_redirects=True)
            assert b'File keeper' in rv.data
            rv = self.app.post(url_for('entity_add_file', id_=node_id),
                               data={'checkbox_values': str([file_id])},
                               follow_redirects=True)
            assert b'Updated file' in rv.data

            # Delete
            for file in files:
                rv = self.app.get(
                    url_for('index', view='file', delete_id=file.id))
                assert b'The entry has been deleted' in rv.data
コード例 #15
0
ファイル: test_import.py プロジェクト: craws/OpenAtlas
    def test_import(self) -> None:
        with app.app_context():
            # Projects
            rv: Any = self.app.get(url_for('import_project_insert'))
            assert b'Name *' in rv.data
            rv = self.app.post(url_for('import_project_insert'),
                               data={'name': 'Project Import'})
            project_id = rv.location.split('/')[-1]
            rv = self.app.get(url_for('import_project_update', id_=project_id))
            assert b'Name *' in rv.data
            rv = self.app.post(url_for('import_project_update',
                                       id_=project_id),
                               data={
                                   'name': 'Yup',
                                   'description': 'whoa!'
                               },
                               follow_redirects=True)
            assert b'whoa!' in rv.data
            rv = self.app.post(url_for('import_project_insert'),
                               data={'name': 'Yup'},
                               follow_redirects=True)
            assert b'The name is already in use.' in rv.data
            rv = self.app.get(url_for('import_index'))
            assert b'Yup' in rv.data

            # Import data
            rv = self.app.get(
                url_for('import_data', class_='person', project_id=project_id))
            assert b'File *' in rv.data
            csv = Path(app.root_path) / 'static' / 'import' / 'example.csv'
            with open(csv, 'rb') as file:
                rv = self.app.post(url_for('import_data',
                                           class_='place',
                                           project_id=project_id),
                                   data={
                                       'file': file,
                                       'duplicate': True
                                   },
                                   follow_redirects=True)
            assert b'Vienna' in rv.data
            with open(csv, 'rb') as file:
                rv = self.app.post(url_for('import_data',
                                           class_='place',
                                           project_id=project_id),
                                   data={
                                       'file': file,
                                       'duplicate': True
                                   },
                                   follow_redirects=True)
            assert b'IDs already in database' in rv.data
            with open(Path(app.root_path) / 'static' / 'favicon.ico',
                      'rb') as file:
                rv = self.app.post(url_for('import_data',
                                           class_='place',
                                           project_id=project_id),
                                   data={'file': file},
                                   follow_redirects=True)
            assert b'File type not allowed' in rv.data
            rv = self.app.get(url_for('import_project_view', id_=project_id))
            assert b'London' in rv.data

            # View an imported entity
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                place_id = Entity.get_by_class('place')[0].id
            rv = self.app.get(url_for('view', id_=place_id))
            assert b'Yup' in rv.data

            rv = self.app.get(url_for('import_project_delete', id_=project_id),
                              follow_redirects=True)
            assert b'Project deleted' in rv.data
コード例 #16
0
ファイル: test_image.py プロジェクト: craws/OpenAtlas
    def test_image(self) -> None:
        app.config['IMAGE_SIZE']['tmp'] = '1'
        with app.app_context():
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                place = insert_entity('Nostromos',
                                      'place',
                                      description='That is the Nostromos')
                logo = \
                    pathlib.Path(app.root_path) \
                    / 'static' / 'images' / 'layout' / 'logo.png'

            # Resizing through UI insert
            with open(logo, 'rb') as img:
                rv = self.app.post(url_for('insert',
                                           class_='file',
                                           origin_id=place.id),
                                   data={
                                       'name': 'OpenAtlas logo',
                                       'file': img
                                   },
                                   follow_redirects=True)
            assert b'An entry has been created' in rv.data

            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                files = Entity.get_by_class('file')
                file_id = files[0].id

            # Set and unset as main image
            self.app.get(url_for('set_profile_image',
                                 id_=file_id,
                                 origin_id=place.id),
                         follow_redirects=True)

            # Delete through UI
            rv = self.app.get(url_for('index', view='file', delete_id=file_id))
            assert b'The entry has been deleted' in rv.data

            # Create entities for file
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                file_pathless = insert_entity('Pathless_File', 'file')

                file = insert_entity('Test_File', 'file')
                file.link('P2', g.types[Type.get_hierarchy('License').subs[0]])
                file_name = f'{file.id}.jpeg'
                src_png = \
                    pathlib.Path(app.root_path) \
                    / 'static' / 'images' / 'layout' / 'logo.png'
                dst_png = \
                    pathlib.Path(app.config['UPLOAD_DIR'] / file_name)
                copyfile(src_png, dst_png)

                file2 = insert_entity('Test_File2', 'file')
                file2.link('P2',
                           g.types[Type.get_hierarchy('License').subs[0]])
                file2_name = f'{file2.id}.jpeg'
                src2_png = \
                    pathlib.Path(app.root_path) \
                    / 'static' / 'images' / 'layout' / 'logo.png'
                dst2_png = pathlib.Path(app.config['UPLOAD_DIR'] / file2_name)
                copyfile(src2_png, dst2_png)

                file_py = insert_entity('Test_Py', 'file')
                file_name_py = f'{file_py.id}.py'
                src_py = pathlib.Path(app.root_path) / 'views' / 'index.py'
                dst_py = pathlib.Path(app.config['UPLOAD_DIR'] / file_name_py)
                copyfile(src_py, dst_py)

                # Exception
                safe_resize_image(file2.id, '.png', size="???")
                display_profile_image(file_pathless)

            # Resizing images (don't change order!)
            rv = self.app.get(url_for('view', id_=file.id))
            assert b'Test_File' in rv.data
            rv = self.app.get(url_for('view', id_=file_py.id))
            assert b'No preview available' in rv.data
            rv = self.app.get(url_for('view', id_=file_pathless.id))
            assert b'Missing file' in rv.data
            rv = self.app.get(url_for('index', view='file'))
            assert b'Test_File' in rv.data

            # Display file
            rv = self.app.get(url_for('display_file', filename=file_name))
            assert b'\xff' in rv.data
            rv = self.app.get(
                url_for('display_file',
                        filename=file_name,
                        size=app.config['IMAGE_SIZE']['thumbnail']))
            assert b'\xff' in rv.data
            rv = self.app.get(
                url_for('display_file',
                        filename=file_name,
                        size=app.config['IMAGE_SIZE']['table']))
            assert b'\xff' in rv.data
            rv = self.app.get(
                url_for('display_file',
                        filename=file_name_py,
                        size=app.config['IMAGE_SIZE']['table']))
            assert b'404' in rv.data

            # Make directory if not exist
            rv = self.app.get(url_for('view', id_=file.id))
            assert b'Test_File' in rv.data

            # Exception
            app.config['IMAGE_SIZE']['tmp'] = '<'
            rv = self.app.get(url_for('view', id_=file.id))
            assert b'Test_File' in rv.data
            app.config['IMAGE_SIZE']['tmp'] = '1'

            rv = self.app.get(url_for('admin_resize_images'),
                              follow_redirects=True)
            assert b'Images were created' in rv.data
            rv = self.app.get(url_for('admin_delete_orphaned_resized_images'),
                              follow_redirects=True)
            assert b'Resized orphaned images were deleted' in rv.data

            rv = self.app.get(url_for('index', view='file', delete_id=file.id))
            assert b'The entry has been deleted' in rv.data
            rv = self.app.get(url_for('index', view='file',
                                      delete_id=file2.id))
            assert b'The entry has been deleted' in rv.data

            shutil.rmtree(
                pathlib.Path(app.config['RESIZED_IMAGES'] /
                             app.config['IMAGE_SIZE']['tmp']))

            dst_py.unlink()
            del app.config['IMAGE_SIZE']['tmp']
コード例 #17
0
    def __call__(self, field: TableField, **kwargs: Any) -> TableMultiSelect:
        if field.data and isinstance(field.data, str):
            field.data = ast.literal_eval(field.data)
        class_ = field.id if field.id != 'given_place' else 'place'

        # Make checkbox column sortable and show selected on top
        table = Table([''] + g.table_headers[class_],
                      order=[[0, 'desc'], [1, 'asc']])

        # Table definitions (ordering and aligning)
        table.defs = [{'orderDataType': 'dom-checkbox', 'targets': 0}]
        if class_ == 'event':
            table.defs.append({
                'className': 'dt-body-right',
                'targets': [4, 5]
            })
        elif class_ in ['actor', 'group', 'feature', 'place']:
            table.defs.append({
                'className': 'dt-body-right',
                'targets': [3, 4]
            })

        if class_ in ['group', 'person', 'place']:
            entities = Entity.get_by_class(
                class_,
                nodes=True,
                aliases=current_user.settings['table_show_aliases'])
        else:
            entities = Entity.get_by_view(class_)

        for entity in entities:
            data = get_base_table_data(entity)
            data[0] = re.sub(re.compile('<a.*?>'), '', data[0])  # Remove links
            data.insert(
                0, """<input type="checkbox" id="{id}" {checked} value="{name}"
                class="multi-table-select">""".format(
                    id=str(entity.id),
                    name=entity.name,
                    checked='checked = "checked"'
                    if field.data and entity.id in field.data else ''))
            table.rows.append(data)
        selection = [
            entity.name for entity in entities
            if field.data and entity.id in field.data
        ]
        html = """
            <span
                id="{name}-button"
                class="{button_class}"
                onclick="$('#{name}-modal').modal('show')">
                    {change_label}
            </span><br>
            <div id="{name}-selection" class="selection" style="text-align:left;">{selection}</div>
            <div
                id="{name}-modal"
                class="modal fade"
                tabindex="-1"
                role="dialog"
                aria-hidden="true">
                <div class="modal-dialog" role="document" style="max-width: 100%!important;">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">{title}</h5>
                            <button
                                type="button"
                                class="btn btn-outline-primary btn-sm"
                                data-dismiss="modal"
                                aria-label="Close">
                                    <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">{table}</div>
                        <div class="modal-footer">
                            <button
                                type="button"
                                class="btn btn-outline-primary btn-sm"
                                data-dismiss="modal"
                                onclick="selectFromTableMulti('{name}')">
                                    {close_label}
                            </button>
                        </div>
                    </div>
                </div>
            </div>
            <script>
            </script>""".format(
            name=field.id,
            button_class=app.config['CSS']['button']['secondary'],
            change_label=uc_first(_('change')),
            close_label=uc_first(_('close')),
            title=uc_first(_(field.id.replace('_', ' '))),
            selection='<br>'.join(selection),
            table=table.display(field.id))
        return super(TableMultiSelect, self).__call__(field, **kwargs) + html
コード例 #18
0
ファイル: admin.py プロジェクト: NinaBrundke/OpenAtlas
def admin_orphans() -> str:
    header = [
        'name', 'class', 'type', 'system type', 'created', 'updated',
        'description'
    ]
    tables = {
        'orphans': Table(header),
        'unlinked': Table(header),
        'missing_files': Table(header),
        'circular': Table(['entity']),
        'nodes': Table(['name', 'root']),
        'orphaned_files': Table(['name', 'size', 'date', 'ext'])
    }
    tables['circular'].rows = [[link(entity)]
                               for entity in Entity.get_circular()]
    for entity in Entity.get_orphans():
        if isinstance(entity, ReferenceSystem):
            continue
        name = 'unlinked' if entity.class_.view else 'orphans'
        tables[name].rows.append([
            link(entity),
            link(entity.class_),
            entity.print_standard_type(), entity.class_.label,
            format_date(entity.created),
            format_date(entity.modified), entity.description
        ])
    for node in Node.get_node_orphans():
        tables['nodes'].rows.append([link(node), link(g.nodes[node.root[-1]])])

    # Get orphaned file entities with no corresponding file
    entity_file_ids = []
    for entity in Entity.get_by_class('file', nodes=True):
        entity_file_ids.append(entity.id)
        if not get_file_path(entity):
            tables['missing_files'].rows.append([
                link(entity),
                link(entity.class_),
                entity.print_standard_type(), entity.class_.label,
                format_date(entity.created),
                format_date(entity.modified), entity.description
            ])

    # Get orphaned files with no corresponding entity
    for file in app.config['UPLOAD_DIR'].iterdir():
        if file.name != '.gitignore' and int(file.stem) not in entity_file_ids:
            tables['orphaned_files'].rows.append([
                file.stem,
                convert_size(file.stat().st_size),
                format_date(
                    datetime.datetime.utcfromtimestamp(file.stat().st_ctime)),
                file.suffix,
                link(_('download'), url_for('download_file',
                                            filename=file.name)),
                delete_link(file.name,
                            url_for('admin_file_delete', filename=file.name))
            ])
    return render_template(
        'admin/check_orphans.html',
        tables=tables,
        title=_('admin'),
        crumbs=[[_('admin'), url_for('admin_index') + '#tab-data'],
                _('orphans')])
コード例 #19
0
def get_entities_by_system_classes(system_classes: list[str]) -> list[Entity]:
    system_classes = list(g.classes) \
        if 'all' in system_classes else system_classes
    if not all(sc in g.classes for sc in system_classes):
        raise InvalidSystemClassError
    return Entity.get_by_class(system_classes, types=True, aliases=True)
コード例 #20
0
ファイル: test_place.py プロジェクト: nhmvienna/OpenAtlas
    def test_place(self) -> None:
        with app.app_context():  # type: ignore
            rv = self.app.get(url_for('insert', class_='place'))
            assert b'+ Place' in rv.data
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                unit_node = Node.get_hierarchy('Administrative unit')
                unit_sub1 = g.nodes[unit_node.subs[0]]
                unit_sub2 = g.nodes[unit_node.subs[1]]
                reference = Entity.insert('external_reference',
                                          'https://openatlas.eu')
                place_node = Node.get_hierarchy('Place')
                source = Entity.insert('source', 'Necronomicon')
            geonames = \
                f"reference_system_id_" \
                f"{ReferenceSystem.get_by_name('GeoNames').id}"
            precision = Node.get_hierarchy('External reference match').subs[0]
            data = {
                'name': 'Asgard',
                'alias-0': 'Valhöll',
                unit_node.id: str([unit_sub1.id, unit_sub2.id]),
                geonames: '123456',
                self.precision_geonames: precision,
                self.precision_wikidata: ''
            }
            rv = self.app.post(url_for('insert',
                                       class_='place',
                                       origin_id=reference.id),
                               data=data,
                               follow_redirects=True)
            assert b'Asgard' in rv.data \
                   and b'An entry has been created' in rv.data
            rv = self.app.get(url_for('entity_view', id_=precision))
            assert b'Asgard' in rv.data
            rv = self.app.get(
                url_for('entity_view',
                        id_=ReferenceSystem.get_by_name('GeoNames').id))
            assert b'Asgard' in rv.data

            data['gis_points'] = """[{
                "type": "Feature",
                "geometry": {"type":"Point","coordinates":[9,17]},
                "properties": {
                    "name": "Valhalla",
                    "description": "",
                    "shapeType": "centerpoint"}}]"""
            data['gis_lines'] = """[{
                "type": "Feature",
                "geometry":{
                    "type": "LineString",
                    "coordinates": [
                        [9.75307425847859,17.8111792731339],
                        [9.75315472474904,17.8110005175436],
                        [9.75333711496205,17.8110873417098]]},
                "properties": {
                    "name": "",
                    "description": "",
                    "shapeType": "line"}}]"""
            data['gis_polygons'] = """[{
                "type": "Feature",
                "geometry": {
                    "type": "Polygon",
                    "coordinates": [[
                        [9.75307425847859,17.8111792731339],
                        [9.75315472474904,17.8110005175436],
                        [9.75333711496205,17.8110873417098],
                        [9.75307425847859,17.8111792731339]]]},
                "properties":{
                    "name": "",
                    "description": "",
                    "shapeType": "shape"}}]"""
            data[place_node.id] = place_node.subs
            data['continue_'] = 'yes'
            rv = self.app.post(url_for('insert',
                                       class_='place',
                                       origin_id=source.id),
                               data=data,
                               follow_redirects=True)
            assert b'Necronomicon' in rv.data
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                places = Entity.get_by_class('place')
                place = places[0]
                place2 = places[1]
                location = place2.get_linked_entity_safe('P53')
                actor = Entity.insert('person', 'Milla Jovovich')
                actor.link('P74', location)
            assert b'Necronomicon' in rv.data
            rv = self.app.get(url_for('index', view='place'))
            assert b'Asgard' in rv.data
            rv = self.app.get(url_for('update', id_=place.id))
            assert b'Valhalla' in rv.data
            data['continue_'] = ''
            data['alias-1'] = 'Val-hall'
            data['geonames_id'] = '321'
            rv = self.app.post(url_for('update', id_=place.id),
                               data=data,
                               follow_redirects=True)
            assert b'Val-hall' in rv.data

            # Test error when viewing the corresponding location
            rv = self.app.get(url_for('entity_view', id_=place.id + 1))
            assert b'be viewed directly' in rv.data

            # Test with same GeoNames id
            rv = self.app.post(url_for('update', id_=place.id),
                               data=data,
                               follow_redirects=True)
            assert b'Val-hall' in rv.data

            # Test with same GeoNames id but different precision
            data['geonames_precision'] = ''
            rv = self.app.post(url_for('update', id_=place.id),
                               data=data,
                               follow_redirects=True)
            assert b'Val-hall' in rv.data

            # Test update without the previous GeoNames id
            data['geonames_id'] = ''
            rv = self.app.post(url_for('update', id_=place.id),
                               data=data,
                               follow_redirects=True)
            assert b'Val-hall' in rv.data

            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                event = Entity.insert('acquisition', 'Valhalla rising')
                event.link('P7', location)
                event.link('P24', location)
            rv = self.app.get(url_for('entity_view', id_=place2.id))
            assert rv.data and b'Valhalla rising' in rv.data

            # Test invalid geom
            data['gis_polygons'] = """[{
                "type": "Feature", 
                "geometry": {
                    "type": "Polygon", 
                    "coordinates": [[
                        [298.9893436362036, -5.888919049309554], 
                        [299.00444983737543, -5.9138487869408545],
                        [299.00650977389887, -5.893358673645309], 
                        [298.9848804404028, -5.9070188333813585],
                        [298.9893436362036, -5.888919049309554]]]},
                "properties": {
                "name": "", 
                "description": "", 
                "shapeType": "shape"}}]"""
            rv = self.app.post(url_for('insert',
                                       class_='place',
                                       origin_id=source.id),
                               data=data,
                               follow_redirects=True)
            assert b'An invalid geometry was entered' in rv.data

            # Test Overlays
            path = \
                pathlib.Path(app.root_path) \
                / 'static' / 'images' / 'layout' / 'logo.png'
            with open(path, 'rb') as img:
                rv = self.app.post(url_for('insert',
                                           class_='file',
                                           origin_id=place.id),
                                   data={
                                       'name': 'X-Files',
                                       'file': img
                                   },
                                   follow_redirects=True)
            assert b'An entry has been created' in rv.data
            with app.test_request_context():
                app.preprocess_request()  # type: ignore
                file = Entity.get_by_class('file')[0]
                link_id = Link.insert(file, 'P67', place)[0]
            rv = self.app.get(
                url_for('overlay_insert',
                        image_id=file.id,
                        place_id=place.id,
                        link_id=link_id))
            assert b'X-Files' in rv.data
            data = {
                'top_left_easting': 42,
                'top_left_northing': 12,
                'top_right_easting': 43,
                'top_right_northing': 13,
                'bottom_left_easting': 10,
                'bottom_left_northing': 20
            }
            rv = self.app.post(url_for('overlay_insert',
                                       image_id=file.id,
                                       place_id=place.id,
                                       link_id=link_id),
                               data=data,
                               follow_redirects=True)
            assert b'Edit' in rv.data
            if os.name == "posix":  # Ignore for other OS e.g. Windows
                with app.test_request_context():
                    app.preprocess_request()  # type: ignore
                    overlay = Overlay.get_by_object(place)
                    overlay_id = overlay[list(overlay.keys())[0]].id
                rv = self.app.get(
                    url_for('overlay_update',
                            id_=overlay_id,
                            place_id=place.id,
                            link_id=link_id))
                assert b'42' in rv.data
                rv = self.app.post(url_for('overlay_update',
                                           id_=overlay_id,
                                           place_id=place.id,
                                           link_id=link_id),
                                   data=data,
                                   follow_redirects=True)
                assert b'Changes have been saved' in rv.data
                self.app.get(url_for('overlay_remove',
                                     id_=overlay_id,
                                     place_id=place.id),
                             follow_redirects=True)

            # Add to place
            rv = self.app.get(url_for('entity_add_file', id_=place.id))
            assert b'Link file' in rv.data

            rv = self.app.post(url_for('entity_add_file', id_=place.id),
                               data={'checkbox_values': str([file.id])},
                               follow_redirects=True)
            assert b'X-Files' in rv.data

            rv = self.app.get(
                url_for('reference_add', id_=reference.id, view='place'))
            assert b'Val-hall' in rv.data
            rv = self.app.get(url_for('entity_add_reference', id_=place.id))
            assert b'Link reference' in rv.data
            rv = self.app.post(url_for('entity_add_reference', id_=place.id),
                               data={
                                   'reference': reference.id,
                                   'page': '777'
                               },
                               follow_redirects=True)
            assert b'777' in rv.data

            # Place types
            rv = self.app.get(url_for('node_move_entities', id_=unit_sub1.id))
            assert b'Asgard' in rv.data

            # Test move entities of multiple node if link to new node exists
            rv = self.app.post(url_for('node_move_entities', id_=unit_sub1.id),
                               follow_redirects=True,
                               data={
                                   unit_node.id: unit_sub2.id,
                                   'selection': location.id,
                                   'checkbox_values': str([location.id])
                               })
            assert b'Entities were updated' in rv.data

            # Test move entities of multiple node
            rv = self.app.post(url_for('node_move_entities', id_=unit_sub2.id),
                               follow_redirects=True,
                               data={
                                   unit_node.id: unit_sub1.id,
                                   'selection': location.id,
                                   'checkbox_values': str([location.id])
                               })
            assert b'Entities were updated' in rv.data

            # Subunits
            data = {
                'name': "Try continue",
                'continue_': 'sub',
                self.precision_geonames: precision,
                self.precision_wikidata: ''
            }
            self.app.get(url_for('insert', class_='place'))
            rv = self.app.post(url_for('insert', class_='place'),
                               data=data,
                               follow_redirects=True)
            assert b'Insert and add strati' in rv.data
            data['name'] = "It's not a bug, it's a feature!"
            rv = self.app.get(
                url_for('insert',
                        class_='stratigraphic_unit',
                        origin_id=place.id))
            assert b'Insert and add find' in rv.data
            rv = self.app.post(url_for('insert',
                                       class_='place',
                                       origin_id=place.id),
                               data=data)
            feat_id = rv.location.split('/')[-1]
            self.app.get(url_for('insert', class_='place', origin_id=feat_id))
            self.app.get(url_for('update', id_=feat_id))
            self.app.post(url_for('update', id_=feat_id), data=data)
            data['name'] = "I'm a stratigraphic unit"
            rv = self.app.post(url_for('insert',
                                       class_='place',
                                       origin_id=feat_id),
                               data=data)
            stratigraphic_id = rv.location.split('/')[-1]
            self.app.get(
                url_for('insert', class_='place', origin_id=stratigraphic_id))
            self.app.get(url_for('update', id_=stratigraphic_id))
            self.app.post(url_for('update', id_=stratigraphic_id),
                          data={'name': "I'm a stratigraphic unit"})
            dimension_node_id = Node.get_hierarchy('Dimensions').subs[0]
            data = {
                'name': 'You never find me',
                dimension_node_id: 50,
                self.precision_geonames: precision,
                self.precision_wikidata: ''
            }
            rv = self.app.post(url_for('insert',
                                       class_='find',
                                       origin_id=stratigraphic_id),
                               data=data)
            find_id = rv.location.split('/')[-1]
            rv = self.app.post(url_for('update', id_=find_id),
                               data=data,
                               follow_redirects=True)
            assert b'50' in rv.data
            self.app.get(url_for('update', id_=find_id))
            data = {
                'name': 'My human remains',
                self.precision_geonames: precision,
                self.precision_wikidata: ''
            }
            rv = self.app.post(url_for('insert',
                                       class_='human_remains',
                                       origin_id=stratigraphic_id),
                               data=data)
            human_remains_id = rv.location.split('/')[-1]
            rv = self.app.get(url_for('update', id_=human_remains_id))
            assert b'My human remains' in rv.data
            rv = self.app.get('/')
            assert b'My human remains' in rv.data

            rv = self.app.get(url_for('entity_view', id_=feat_id))
            assert b'not a bug' in rv.data
            rv = self.app.get(url_for('entity_view', id_=stratigraphic_id))
            assert b'a stratigraphic unit' in rv.data
            rv = self.app.get(url_for('entity_view', id_=find_id))
            assert b'You never' in rv.data
            rv = self.app.get(url_for('index',
                                      view='place',
                                      delete_id=place.id),
                              follow_redirects=True)
            assert b'not possible if subunits' in rv.data
            rv = self.app.get(url_for('index', view='place',
                                      delete_id=find_id),
                              follow_redirects=True)
            assert b'The entry has been deleted.' in rv.data
            rv = self.app.get(
                url_for('index', view='place', delete_id=place2.id))
            assert b'The entry has been deleted.' in rv.data
コード例 #21
0
    def __call__(self, field: TableField, **kwargs: Any) -> TableSelect:
        file_stats = None
        place_fields = [
            'residence', 'begins_in', 'ends_in', 'place_to', 'place_from'
        ]
        class_ = 'place' if field.id in place_fields else field.id
        if class_ == 'place':
            aliases = current_user.settings['table_show_aliases']
            entities = Entity.get_by_class('place',
                                           nodes=True,
                                           aliases=aliases)
        elif class_ == 'reference':
            entities = Entity.get_by_class('bibliography') + \
                       Entity.get_by_class('edition') + \
                       Entity.get_by_class('external_reference')
        elif class_ == 'file':
            entities = Entity.get_by_class('file')
        else:
            entities = Entity.get_by_view(class_)
        table = Table([''] + g.table_headers[class_])

        selection = ''
        for entity in entities:
            if field.data and entity.id == int(field.data):
                selection = entity.name
            data = get_base_table_data(entity, file_stats)
            if len(entity.aliases) > 0:
                data[0] = """
                    <p>
                        <a onclick="selectFromTable(this, '{name}', {entity_id}, '{entity_name_clean}')"
                            href="#">{entity_name}</a>
                    </p>""".format(name=field.id,
                                   entity_id=entity.id,
                                   entity_name=entity.name,
                                   entity_name_clean=entity.name.replace(
                                       "'", ''))
            else:
                data[0] = """
                    <a
                        onclick="selectFromTable(this, '{name}', {entity_id}, '{entity_name_clean}')"
                        href="#">{entity_name}</a>
                    """.format(name=field.id,
                               entity_id=entity.id,
                               entity_name=entity.name,
                               entity_name_clean=entity.name.replace("'", ''))
            for i, (id_, alias) in enumerate(entity.aliases.items()):
                if i == len(entity.aliases) - 1:
                    data[0] = ''.join([data[0]] + [alias])
                else:
                    data[0] = ''.join([data[0]] + ['<p>' + alias + '</p>'])
            data.insert(
                0, """
                <div style="position: relative; top: 10px;">
                    <div
                        class="btn btn-outline-primary btn-xsm"
                        style="position: absolute; top: -22px;"
                        onclick="selectFromTable(this,'{name}', {entity_id}, '{entity_name_clean}')">
                            {label}
                    </div>
                </div>
                """.format(name=field.id,
                           entity_id=entity.id,
                           entity_name=entity.name,
                           entity_name_clean=entity.name.replace("'", ''),
                           label=uc_first(_('select'))))
            table.rows.append(data)
        html = """
            <input
                id="{name}-button"
                name="{name}-button"
                class="table-select {required}"
                type="text"
                placeholder="{change_label}"
                onfocus="this.blur()"
                readonly="readonly"
                value="{selection}"
                onclick="$('#{name}-modal').modal('show');">
            <a href="#"
                id="{name}-clear"
                class="{button_class}"
                {clear_style}
                onclick="clearSelect('{name}');">
                    {clear_label}
            </a>
            <div
                id="{name}-modal"
                class="modal fade"
                tabindex="-1"
                role="dialog"
                aria-hidden="true">
                <div class="modal-dialog" role="document" style="max-width: 100%!important;">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">{title}</h5>
                            <button
                                type="button"
                                class="btn btn-outline-primary btn-sm"
                                data-dismiss="modal"
                                aria-label="Close">
                                    <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">{table}</div>
                        <div class="modal-footer">
                            <button
                                type="button"
                                class="btn btn-outline-primary btn-sm"
                                data-dismiss="modal">
                                    {close_label}
                            </button>
                        </div>
                    </div>
                </div>
            </div>
            """.format(
            name=field.id,
            title=uc_first(_(field.id.replace('_', ' '))),
            button_class=app.config['CSS']['button']['secondary'],
            change_label=uc_first(_('change')),
            clear_label=uc_first(_('clear')),
            close_label=uc_first(_('close')),
            table=table.display(field.id),
            selection=selection,
            clear_style='' if selection else ' style="display: none;" ',
            required=' required' if field.flags.required else '')
        return super(TableSelect, self).__call__(field, **kwargs) + html