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
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_))
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)
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")
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']}")
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)
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
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']}")
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)
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
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
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_))
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')])
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
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
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']
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">×</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
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')])
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)
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
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">×</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