def hierarchy_insert(category: str) -> Union[str, Response]: form = build_form('hierarchy', code=category) form.classes.choices = Type.get_class_choices() if form.validate_on_submit(): if Type.check_hierarchy_exists(form.name.data): flash(_('error name exists'), 'error') return render_template('display_form.html', form=form) try: Transaction.begin() type_ = Entity.insert('type', sanitize(form.name.data)) Type.insert_hierarchy( type_, # type: ignore category, form.classes.data, is_multiple(form, category)) type_.update(process_form_data(form, type_)) Transaction.commit() except Exception as e: # pragma: no cover Transaction.rollback() logger.log('error', 'database', 'transaction failed', e) flash(_('error transaction'), 'error') abort(418) flash(_('entity created'), 'info') return redirect(f"{url_for('type_index')}#menu-tab-{category}") return render_template( 'display_form.html', form=form, manual_page='entity/type', title=_('types'), crumbs=[ [_('types'), url_for('type_index')], f'+ {uc_first(_(category))}'])
def type_move_entities(id_: int) -> Union[str, Response]: type_ = g.types[id_] root = g.types[type_.root[0]] if root.category == 'value': abort(403) # pragma: no cover form = build_move_form(type_) if form.validate_on_submit(): Transaction.begin() Type.move_entities(type_, getattr(form, str(root.id)).data, form.checkbox_values.data) Transaction.commit() flash(_('Entities were updated'), 'success') return redirect(f"{url_for('type_index')}" f"#menu-tab-{type_.category}_collapse-{root.id}") getattr(form, str(root.id)).data = type_.id return render_template('type/move.html', table=Table(header=['#', _('selection')], rows=[[item, item.label.text] for item in form.selection]), root=root, form=form, entity=type_, crumbs=[[_('types'), url_for('type_index')], root, type_, _('move entities')])
def hierarchy_update(id_: int) -> Union[str, Response]: hierarchy = g.types[id_] if hierarchy.category in ('standard', 'system'): abort(403) form = build_form('hierarchy', hierarchy) form.classes.choices = Type.get_class_choices(hierarchy) linked_entities = set() has_multiple_links = False for entity in get_entities_linked_to_type_recursive(id_, []): if entity.id in linked_entities: has_multiple_links = True break linked_entities.add(entity.id) if hasattr(form, 'multiple') and has_multiple_links: form.multiple.render_kw = {'disabled': 'disabled'} if form.validate_on_submit(): if form.name.data != hierarchy.name and Type.get_types(form.name.data): flash(_('error name exists'), 'error') else: Transaction.begin() try: Type.update_hierarchy(hierarchy, sanitize(form.name.data), form.classes.data, multiple=(hierarchy.category == 'value' or (hasattr(form, 'multiple') and form.multiple.data) or has_multiple_links)) hierarchy.update(process_form_data(form, hierarchy)) Transaction.commit() except Exception as e: # pragma: no cover Transaction.rollback() logger.log('error', 'database', 'transaction failed', e) flash(_('error transaction'), 'error') abort(418) flash(_('info update'), 'info') tab = 'value' if g.types[id_].category == 'value' else 'custom' return redirect( f"{url_for('type_index')}#menu-tab-{tab}_collapse-{hierarchy.id}") form.multiple = hierarchy.multiple table = Table(paging=False) for class_name in hierarchy.classes: count = Type.get_form_count(hierarchy, class_name) table.rows.append([ g.classes[class_name].label, format_number(count) if count else link( _('remove'), url_for( 'remove_class', id_=hierarchy.id, class_name=class_name)) ]) return render_template('display_form.html', form=form, table=table, manual_page='entity/type', title=_('types'), crumbs=[[_('types'), url_for('type_index')], hierarchy, _('edit')])
def remove_class(id_: int, class_name: str) -> Response: root = g.types[id_] if Type.get_form_count(root, class_name): abort(403) # pragma: no cover try: Type.remove_class_from_hierarchy(class_name, root.id) flash(_('info update'), 'info') except Exception as e: # pragma: no cover logger.log('error', 'database', 'remove class from hierarchy failed', e) flash(_('error database'), 'error') return redirect(url_for('hierarchy_update', id_=id_))
def before_request() -> None: from openatlas.models.openatlas_class import (OpenatlasClass, view_class_mapping) from openatlas.models.cidoc_property import CidocProperty from openatlas.models.cidoc_class import CidocClass from openatlas.models.type import Type from openatlas.models.settings import Settings from openatlas.models.reference_system import ReferenceSystem if request.path.startswith('/static'): # pragma: no cover return # Avoid overhead for files if not using Apache with static alias open_connection(app.config) g.settings = Settings.get_settings() session['language'] = get_locale() g.cidoc_classes = CidocClass.get_all() g.properties = CidocProperty.get_all() g.classes = OpenatlasClass.get_all() g.types = Type.get_all() g.reference_systems = ReferenceSystem.get_all() g.view_class_mapping = view_class_mapping g.class_view_mapping = OpenatlasClass.get_class_view_mapping() g.table_headers = OpenatlasClass.get_table_headers() g.file_stats = get_file_stats() # Set max file upload in MB app.config['MAX_CONTENT_LENGTH'] = \ g.settings['file_upload_max_size'] * 1024 * 1024 if request.path.startswith('/api/'): ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) if not current_user.is_authenticated \ and not g.settings['api_public'] \ and ip not in app.config['ALLOWED_IPS']: raise AccessDeniedError # pragma: no cover
def type_delete_recursive(id_: int) -> Union[str, Response]: class DeleteRecursiveTypesForm(FlaskForm): confirm_delete = BooleanField( _("I'm sure to delete this type, it's subs and links"), default=False, validators=[InputRequired()]) save = SubmitField(_('delete types and remove all links')) type_ = g.types[id_] root = g.types[type_.root[0]] if type_.root else None if type_.category in ('standard', 'system', 'place') and not root: abort(403) form = DeleteRecursiveTypesForm() if form.validate_on_submit() and form.confirm_delete.data: for sub_id in Type.get_all_sub_ids(type_): g.types[sub_id].delete() type_.delete() flash(_('types deleted'), 'info') logger.log_user(id_, 'Recursive type delete') return redirect( url_for('view', id_=root.id) if root else url_for('type_index')) tabs = { 'info': Tab('info', content=_( 'Warning: this type has subs and/or links to entities ' '(see tabs). Please check if you want to delete these subs ' 'and links too.'), form=form), 'subs': Tab('subs', entity=type_), 'entities': Tab('entities', entity=type_) } for sub_id in Type.get_all_sub_ids(type_): sub = g.types[sub_id] tabs['subs'].table.rows.append([link(sub), sub.count, sub.description]) for item in get_entities_linked_to_type_recursive(type_.id, []): data = [link(item), item.class_.label, item.description] tabs['entities'].table.rows.append(data) crumbs = [[_('types'), url_for('type_index')]] if root: crumbs += [g.types[type_id] for type_id in type_.root] crumbs += [type_, _('delete')] return render_template('tabs.html', tabs=tabs, crumbs=crumbs)
def __call__(self, field: TreeField, **kwargs: Any) -> TreeMultiSelect: data: list[int] = [] if field.data: data = ast.literal_eval(field.data) \ if isinstance(field.data, str) else field.data return super().__call__(field, **kwargs) + render_template( 'forms/tree_multi_select.html', field=field, root=g.types[int(field.id)], selection=sorted([g.types[id_].name for id_ in data]), data=Type.get_tree_data(int(field.id), data))
def __call__(self, field: TreeField, **kwargs: Any) -> TreeSelect: selection = '' selected_ids = [] if field.data: field.data = field.data[0] \ if isinstance(field.data, list) else field.data selection = g.types[int(field.data)].name selected_ids.append(g.types[int(field.data)].id) return super().__call__(field, **kwargs) + render_template( 'forms/tree_select.html', field=field, selection=selection, data=Type.get_tree_data(int(field.id), selected_ids))
def get_node_overview() -> dict[str, dict[Entity, str]]: nodes: dict[str, Any] = { 'standard': {}, 'custom': {}, 'place': {}, 'value': {}, 'system': {}, 'anthropology': {} } for node in g.types.values(): if node.root: continue nodes[node.category][node.name] = GetNodeOverview.walk_tree( Type.get_types(node.name)) return nodes
def show_untyped_entities(id_: int) -> str: hierarchy = g.types[id_] table = Table(['name', 'class', 'first', 'last', 'description']) for entity in Type.get_untyped(hierarchy.id): table.rows.append([ link(entity), entity.class_.label, entity.first, entity.last, entity.description ]) return render_template('table.html', entity=hierarchy, table=table, crumbs=[[_('types'), url_for('type_index')], link(hierarchy), _('untyped entities')])
def get_node_overview() -> dict[str, dict[Entity, str]]: nodes: dict[str, Any] = { 'standard': [], 'custom': [], 'place': [], 'value': [], 'system': [], 'anthropology': []} for node in g.types.values(): if node.root: continue nodes[node.category].append({ "id": node.id, "name": node.name, "viewClass": node.classes, "children": GetTypeOverview.walk_tree(Type.get_types(node.name))}) return nodes
def get(filename: str) -> Response: # pragma: no cover entity = Entity.get_by_id(int(Pathlib_path(filename).stem), types=True) license_ = None for node in entity.types: if node.root and node.root[0] == Type.get_hierarchy('License').id: license_ = node.name if not license_: raise AccessDeniedError parser = image.parse_args() if parser['download']: return send_file( f"{app.config['UPLOAD_DIR']}/{filename}", as_attachment=True) if parser['image_size'] and check_processed_image(filename): size = app.config['IMAGE_SIZE'][parser['image_size']] return send_from_directory( f"{app.config['RESIZED_IMAGES']}/{size}", filename) return send_from_directory(app.config['UPLOAD_DIR'], filename)
def type_index() -> str: types: dict[str, dict[Entity, str]] = { 'standard': {}, 'custom': {}, 'place': {}, 'value': {}, 'system': {} } for type_ in [type_ for type_ in g.types.values() if not type_.root]: if type_.category not in types: continue # pragma: no cover, remove after anthropology features types[type_.category][type_] = render_template( 'forms/tree_select_item.html', name=sanitize(type_.name), data=walk_tree(Type.get_types(type_.name))) return render_template('type/index.html', types=types, title=_('types'), crumbs=[_('types')])
def check_single_type_duplicates() -> list[dict[str, Any]]: from openatlas.models.type import Type from openatlas.models.entity import Entity data = [] for type_ in g.types.values(): if type_.root or type_.multiple or type_.category == 'value': continue # pragma: no cover type_ids = Type.get_all_sub_ids(type_) if not type_ids: continue # pragma: no cover for id_ in Db.check_single_type_duplicates(type_ids): offending_types = [] entity = Entity.get_by_id(id_, types=True) for entity_types in entity.types: if g.types[entity_types.root[0]].id != type_.id: continue # pragma: no cover offending_types.append(entity_types) data.append({ 'entity': entity, 'type': type_, 'offending_types': offending_types }) return data
def add_reference_systems(form: Any, class_: str) -> None: precisions = [('', '')] + [ (str(g.types[id_].id), g.types[id_].name) for id_ in Type.get_hierarchy('External reference match').subs ] systems = list(g.reference_systems.values()) systems.sort(key=lambda x: x.name.casefold()) for system in systems: if class_ not in system.classes: continue setattr( form, f'reference_system_id_{system.id}', StringField(uc_first(system.name), [OptionalValidator()], description=system.description, render_kw={ 'autocomplete': 'off', 'placeholder': system.placeholder })) setattr( form, f'reference_system_precision_{system.id}', SelectField(_('precision'), choices=precisions, default=system.precision_default_id))
def test_duplicates(self) -> None: with app.app_context(): with app.test_request_context(): app.preprocess_request() # type: ignore event = Entity.insert('acquisition', 'Event Horizon') source = Entity.insert('source', 'Tha source') source.link('P67', event) source.link('P67', event) source_type = Type.get_hierarchy('Source') source.link('P2', g.types[source_type.subs[0]]) source.link('P2', g.types[source_type.subs[1]]) rv = self.app.get(url_for('admin_check_link_duplicates')) assert b'Event Horizon' in rv.data rv = self.app.get( url_for('admin_check_link_duplicates', delete='delete'), follow_redirects=True) assert b'Remove' in rv.data rv = self.app.get( url_for( 'admin_delete_single_type_duplicate', entity_id=source.id, type_id=source_type.subs[0]), follow_redirects=True) assert b'Congratulations, everything looks fine!' in rv.data
def test_actor(self) -> None: with app.app_context(): rv: Any = self.app.get(url_for('index', view='actor')) assert b'No entries' in rv.data rv = self.app.post(url_for('insert', class_='place'), data={ 'name': 'Captain Miller', self.precision_geonames: '', self.precision_wikidata: '' }) residence_id = rv.location.split('/')[-1] with app.test_request_context(): app.preprocess_request() # type: ignore sex_type = Type.get_hierarchy('Sex') sex_type_sub_1 = g.types[sex_type.subs[0]] sex_type_sub_2 = g.types[sex_type.subs[1]] event = Entity.insert('acquisition', 'Event Horizon') source = Entity.insert('source', 'Necronomicon') # Actor insert rv = self.app.get(url_for('insert', class_='person')) assert b'+ Person' in rv.data self.app.get( url_for('insert', class_='person', origin_id=residence_id)) data = { sex_type.id: sex_type_sub_1.id, 'name': 'Sigourney Weaver', 'alias-1': 'Ripley', 'residence': residence_id, 'begins_in': residence_id, 'ends_in': residence_id, 'description': 'Susan Alexandra Weaver is an American actress.', 'begin_year_from': '-1949', 'begin_month_from': '10', 'begin_day_from': '8', 'begin_year_to': '-1948', 'end_year_from': '2049', 'end_year_to': '2050', self.precision_geonames: '', self.precision_wikidata: '' } rv = self.app.post(url_for('insert', class_='person'), data=data) actor_id = rv.location.split('/')[-1] self.app.post(url_for('insert', class_='group'), data=data) rv = self.app.post(url_for('insert', class_='person', origin_id=residence_id), data=data, follow_redirects=True) assert b'An entry has been created' in rv.data # Test actor types rv = self.app.get(url_for('view', id_=sex_type_sub_1.id)) assert b'Susan' in rv.data rv = self.app.get( url_for('type_move_entities', id_=sex_type_sub_1.id)) assert b'Sigourney' in rv.data rv = self.app.post(url_for('type_move_entities', id_=sex_type_sub_1.id), follow_redirects=True, data={ sex_type.id: sex_type_sub_2.id, 'selection': [actor_id], 'checkbox_values': str([actor_id]) }) assert b'Entities were updated' in rv.data rv = self.app.post(url_for('type_move_entities', id_=sex_type_sub_2.id), follow_redirects=True, data={ sex_type.id: '', 'selection': [actor_id], 'checkbox_values': str([actor_id]) }) assert b'Entities were updated' in rv.data self.app.post(url_for('insert', class_='person', origin_id=actor_id), data=data) self.app.post(url_for('insert', class_='person', origin_id=event.id), data=data) self.app.post(url_for('insert', class_='person', origin_id=source.id), data=data) rv = self.app.post(url_for('insert', class_='external_reference'), data={'name': 'https://openatlas.eu'}) reference_id = rv.location.split('/')[-1] rv = self.app.post(url_for('insert', class_='person', origin_id=reference_id), data=data, follow_redirects=True) assert b'An entry has been created' in rv.data data['continue_'] = 'yes' rv = self.app.post(url_for('insert', class_='person'), data=data, follow_redirects=True) assert b'An entry has been created' in rv.data rv = self.app.get(url_for('index', view='actor')) assert b'Sigourney Weaver' in rv.data # Add to actor rv = self.app.get(url_for('entity_add_source', id_=actor_id)) assert b'Link source' in rv.data rv = self.app.post(url_for('entity_add_source', id_=actor_id), data={'checkbox_values': str([source.id])}, follow_redirects=True) assert b'Necronomicon' in rv.data rv = self.app.get(url_for('entity_add_reference', id_=actor_id)) assert b'Link reference' in rv.data rv = self.app.post(url_for('entity_add_reference', id_=actor_id), data={ 'reference': reference_id, 'page': '777' }, follow_redirects=True) assert b'777' in rv.data # Actor update rv = self.app.get(url_for('update', id_=actor_id)) assert b'American actress' in rv.data data['name'] = 'Susan Alexandra Weaver' data['alias-1'] = 'Ripley1' data['end_year_from'] = '' data['end_year_to'] = '' data['begin_year_to'] = '1950' data['begin_day_from'] = '' rv = self.app.post(url_for('update', id_=actor_id), data=data, follow_redirects=True) assert b'Changes have been saved' in rv.data rv = self.app.post(url_for('ajax_bookmark'), data={'entity_id': actor_id}, follow_redirects=True) assert b'Remove bookmark' in rv.data rv = self.app.get('/') assert b'Weaver' in rv.data rv = self.app.post(url_for('ajax_bookmark'), data={'entity_id': actor_id}, follow_redirects=True) assert b'Bookmark' in rv.data rv = self.app.get(url_for('link_delete', origin_id=actor_id, id_=666), follow_redirects=True) assert b'removed' in rv.data # Actor delete rv = self.app.get( url_for('index', view='actor', delete_id=actor_id)) assert b'The entry has been deleted.' in rv.data
def get_type_tree() -> dict[int, Any]: return { id_: GetTypeTree.serialize_to_json(type_) for id_, type_ in Type.get_all().items() }
def test_type(self) -> None: with app.app_context(): with app.test_request_context(): app.preprocess_request() # type: ignore actor_type = Type.get_hierarchy('Actor actor relation') dimension_type = Type.get_hierarchy('Dimensions') historical_type = Type.get_hierarchy('Historical place') sex_type = Type.get_hierarchy('Sex') place = insert_entity('Home', 'place') place.link('P2', g.types[dimension_type.subs[0]], '46') location = place.get_linked_entity_safe('P53') location.link('P89', g.types[historical_type.subs[0]]) rv: Any = self.app.get(url_for('view', id_=historical_type.subs[0])) assert b'Historical place' in rv.data rv = self.app.get(url_for('type_index')) assert b'Actor actor relation' in rv.data rv = self.app.get( url_for('insert', class_='type', origin_id=actor_type.id)) assert b'Actor actor relation' in rv.data data = { 'name': 'My secret type', 'name_inverse': 'Do I look inverse?', 'description': 'Very important!'} rv = self.app.post( url_for('insert', class_='type', origin_id=actor_type.id), data=data) type_id = rv.location.split('/')[-1] rv = self.app.get(url_for('update', id_=type_id)) assert b'My secret type' in rv.data and b'Super' in rv.data self.app.post( url_for('insert', class_='type', origin_id=sex_type.id), data=data) rv = self.app.post( url_for('update', id_=type_id), data=data, follow_redirects=True) assert b'Changes have been saved.' in rv.data # Insert and continue data['continue_'] = 'yes' rv = self.app.post( url_for('insert', class_='type', origin_id=actor_type.id), data=data, follow_redirects=True) assert b'An entry has been created' in rv.data data['continue_'] = '' # Forbidden system type rv = self.app.post( url_for('update', id_=actor_type.id), data=data, follow_redirects=True) assert b'Forbidden' in rv.data # Update with self as root data[str(actor_type.id)] = type_id rv = self.app.post( url_for('update', id_=type_id), data=data, follow_redirects=True) assert b'Type can't have itself as super' in rv.data # Update with sub as root rv = self.app.post( url_for('insert', class_='type', origin_id=actor_type.id), data=data) sub_type_id = rv.location.split('/')[-1].replace('type#tab-', '') data[str(actor_type.id)] = sub_type_id rv = self.app.post( url_for('update', id_=type_id), data=data, follow_redirects=True) assert b'Type can't have a sub as super' in rv.data # Custom type rv = self.app.get( url_for('view', id_=sex_type.id), follow_redirects=True) assert b'Male' in rv.data # Administrative unit admin_unit_id = Type.get_hierarchy('Administrative unit').id rv = self.app.get( url_for('view', id_=admin_unit_id), follow_redirects=True) assert b'Austria' in rv.data rv = self.app.post( url_for( 'insert', class_='administrative_unit', origin_id=g.types[admin_unit_id].subs[0]), data={'name': 'admin unit'}, follow_redirects=True) assert b'An entry has been created' in rv.data # Value type rv = self.app.get( url_for('view', id_=dimension_type.id), follow_redirects=True) assert b'Height' in rv.data rv = self.app.get(url_for('view', id_=dimension_type.subs[0])) assert b'Unit' in rv.data rv = self.app.get(url_for('update', id_=dimension_type.subs[0])) assert b'Dimensions' in rv.data # Test parent value type view after creating a sub subtype rv = self.app.post( url_for( 'insert', class_='type', origin_id=dimension_type.subs[0]), data={ 'name': "Sub sub type", dimension_type.id: dimension_type.subs[0]}, follow_redirects=True) assert b'An entry has been created' in rv.data rv = self.app.get(url_for('view', id_=dimension_type.subs[0])) assert b'Sub sub type' in rv.data # Untyped entities with app.test_request_context(): app.preprocess_request() # type: ignore actor = Entity.insert('person', 'Connor MacLeod') rv = self.app.get(url_for('show_untyped_entities', id_=sex_type.id)) assert b'Connor MacLeod' in rv.data with app.test_request_context(): app.preprocess_request() # type: ignore actor.link('P2', g.types[sex_type.subs[0]]) rv = self.app.get(url_for('show_untyped_entities', id_=sex_type.id)) assert b'No entries' in rv.data # Delete rv = self.app.get( url_for('type_delete', id_=actor_type.id), follow_redirects=True) assert b'Forbidden' in rv.data rv = self.app.get( url_for('type_delete', id_=sub_type_id), follow_redirects=True) assert b'The entry has been deleted.' in rv.data
def get_sub_ids(id_: int, subs: list[Any]) -> list[Any]: new_subs = Type.get_all_sub_ids(g.types[id_]) subs.extend(new_subs) for sub in new_subs: get_sub_ids(sub, subs) return subs
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 admin_delete_single_type_duplicate(entity_id: int, type_id: int) -> Response: Type.remove_by_entity_and_type(entity_id, type_id) flash(_('link removed'), 'info') return redirect(url_for('admin_check_link_duplicates'))
def prepare_feature_types() -> None: for category_id in Type.get_types('Features for sexing'): for id_ in g.types[category_id].subs: SexEstimation.features[g.types[id_].name]['id'] = \ g.types[id_].id
def test_involvement(self) -> None: with app.app_context(): rv: Any = self.app.post(url_for('insert', class_='acquisition'), data={ 'name': 'Event Horizon', 'begin_year_from': '949', 'begin_month_from': '10', 'begin_day_from': '8', 'end_year_from': '1951', self.precision_geonames: '', self.precision_wikidata: '' }) event_id = int(rv.location.split('/')[-1]) with app.test_request_context(): app.preprocess_request() # type: ignore actor = Entity.insert('person', 'Captain Miller') involvement = Type.get_hierarchy('Involvement') # Add involvement rv = self.app.get(url_for('involvement_insert', origin_id=actor.id)) assert b'Involvement' in rv.data rv = self.app.post(url_for('involvement_insert', origin_id=actor.id), data={ 'event': str([event_id]), 'activity': 'P11', 'begin_year_from': '950', 'end_year_from': '1950', involvement.id: involvement.id }, follow_redirects=True) assert b'Event Horizon' in rv.data rv = self.app.post(url_for('involvement_insert', origin_id=event_id), data={ 'actor': str([actor.id]), 'continue_': 'yes', 'activity': 'P22' }, follow_redirects=True) assert b'Event Horizon' in rv.data rv = self.app.get(url_for('view', id_=event_id)) assert b'Event Horizon' in rv.data rv = self.app.get(url_for('view', id_=actor.id)) assert b'Appears first' in rv.data # Update involvement with app.test_request_context(): app.preprocess_request() # type: ignore link_id = Link.get_links(event_id, 'P22')[0].id rv = self.app.get( url_for('link_update', id_=link_id, origin_id=event_id)) assert b'Captain' in rv.data rv = self.app.post(url_for('link_update', id_=link_id, origin_id=actor.id), data={ 'description': 'Infinite Space - Infinite Terror', 'activity': 'P23' }, follow_redirects=True) assert b'Infinite Space - Infinite Terror' in rv.data rv = self.app.get(url_for('view', id_=actor.id)) assert b'Appears first' in rv.data rv = self.app.get(url_for('view', id_=event_id)) assert b'Infinite Space - Infinite Terror' in rv.data
def test_hierarchy(self) -> None: with app.app_context(): # Custom types data = { 'name': 'Geronimo', 'classes': ['file', 'group', 'move', 'person', 'place', 'source'], 'multiple': True, 'description': 'Very important!'} rv: Any = self.app.post( url_for('hierarchy_insert', category='custom'), follow_redirects=True, data=data) assert b'An entry has been created' in rv.data rv = self.app.post( url_for('hierarchy_insert', category='custom'), follow_redirects=True, data=data) assert b'The name is already in use' in rv.data with app.test_request_context(): hierarchy = Type.get_hierarchy('Geronimo') rv = self.app.get(url_for('hierarchy_update', id_=hierarchy.id)) assert b'Geronimo' in rv.data data['classes'] = ['acquisition'] rv = self.app.post( url_for('hierarchy_update', id_=hierarchy.id), data=data, follow_redirects=True) assert b'Changes have been saved.' in rv.data rv = self.app.get(url_for('hierarchy_insert', category='custom')) assert b'+ Custom' in rv.data data = {'name': 'My secret type', 'description': 'Very important!'} rv = self.app.post( url_for('insert', class_='type', origin_id=hierarchy.id), data=data) type_id = rv.location.split('/')[-1] rv = self.app.get( url_for('remove_class', id_=hierarchy.id, class_name='person'), follow_redirects=True) assert b'Changes have been saved.' in rv.data rv = self.app.get( url_for('type_delete', id_=type_id), follow_redirects=True) assert b'deleted' in rv.data rv = self.app.post( url_for('hierarchy_update', id_=hierarchy.id), data={'name': 'Actor actor relation'}, follow_redirects=True) assert b'The name is already in use' in rv.data rv = self.app.post( url_for('hierarchy_delete', id_=hierarchy.id), follow_redirects=True) assert b'deleted' in rv.data # Value types rv = self.app.get(url_for('hierarchy_insert', category='value')) assert b'+ Value' in rv.data rv = self.app.post( url_for('hierarchy_insert', category='value'), follow_redirects=True, data={ 'name': 'A valued value', 'classes': ['file'], 'description': ''}) assert b'An entry has been created' in rv.data with app.test_request_context(): value_type = Type.get_hierarchy('A valued value') rv = self.app.get(url_for('hierarchy_update', id_=value_type.id)) assert b'valued' in rv.data # Test checks relation_type = Type.get_hierarchy('Actor actor relation') rv = self.app.get( url_for('hierarchy_update', id_=relation_type.id), follow_redirects=True) assert b'Forbidden' in rv.data rv = self.app.get( url_for('hierarchy_delete', id_=relation_type.id), follow_redirects=True) assert b'Forbidden' in rv.data
def test_relation(self) -> None: with app.app_context(): with app.test_request_context(): app.preprocess_request() # type: ignore actor = Entity.insert('person', 'Connor MacLeod') related = Entity.insert('person', 'The Kurgan') # Add relationship rv = self.app.get(url_for('relation_insert', origin_id=actor.id)) assert b'Actor actor relation' in rv.data relation_id = Type.get_hierarchy('Actor actor relation').id relation_sub_id = g.types[relation_id].subs[0] relation_sub_id2 = g.types[relation_id].subs[1] data = { 'actor': str([related.id]), relation_id: relation_sub_id, 'inverse': None, 'begin_year_from': '-1949', 'begin_month_from': '10', 'begin_day_from': '8', 'begin_year_to': '-1948', 'end_year_from': '2049', 'end_year_to': '2050'} rv = self.app.post( url_for('relation_insert', origin_id=actor.id), data=data, follow_redirects=True) assert b'The Kurgan' in rv.data rv = self.app.get(url_for('view', id_=relation_sub_id)) assert b'Connor' in rv.data data['continue_'] = 'yes' data['inverse'] = True rv = self.app.post( url_for('relation_insert', origin_id=actor.id), data=data, follow_redirects=True) assert b'The Kurgan' in rv.data rv = self.app.get(url_for('view', id_=actor.id)) assert b'The Kurgan' in rv.data rv = self.app.post( url_for('relation_insert', origin_id=related.id), data=data, follow_redirects=True) assert b"link to itself" in rv.data # Relation types rv = self.app.get( url_for('type_move_entities', id_=relation_sub_id)) assert b'The Kurgan' in rv.data # Update relationship with app.test_request_context(): app.preprocess_request() # type: ignore link_id = Link.get_links(actor.id, 'OA7')[0].id link_id2 = Link.get_links(actor.id, 'OA7', True)[0].id rv = self.app.post( url_for('type_move_entities', id_=relation_sub_id), follow_redirects=True, data={ relation_id: relation_sub_id2, 'selection': [link_id], 'checkbox_values': str([link_id])}) assert b'Entities were updated' in rv.data rv = self.app.post( url_for('type_move_entities', id_=relation_sub_id2), data={ relation_id: '', 'selection': [link_id], 'checkbox_values': str([link_id])}, follow_redirects=True) assert b'Entities were updated' in rv.data rv = self.app.get( url_for('link_update', id_=link_id, origin_id=related.id)) assert b'Connor' in rv.data rv = self.app.post( url_for('link_update', id_=link_id, origin_id=actor.id), data={'description': 'There can be only one', 'inverse': True}, follow_redirects=True) assert b'only one' in rv.data rv = self.app.post( url_for('link_update', id_=link_id2, origin_id=actor.id), data={'description': 'There can be only one', 'inverse': None}, follow_redirects=True) assert b'only one' in rv.data
def get_all(objects: Optional[list[Entity]] = None, structure: Optional[dict[str, Any]] = None) -> dict[str, Any]: if not objects: objects = [] all_: dict[str, list[Any]] = { 'point': [], 'linestring': [], 'polygon': [] } extra: dict[str, list[Any]] = { 'supers': [], 'subs': [], 'siblings': [] } selected: dict[str, list[Any]] = { 'point': [], 'linestring': [], 'polygon': [], 'polygon_point': [] } # Include GIS of subunits which would be otherwise omitted subunit_ids = [ subunit.id for subunit in structure['subunits']] \ if structure else [] sibling_ids = [ sibling.id for sibling in structure['siblings']] \ if structure else [] extra_ids = [0] if structure: extra_ids = [ objects[0].id if objects else 0] \ + [structure['super_id']] \ + subunit_ids \ + sibling_ids object_ids = [x.id for x in objects] if objects else [] for shape in ['point', 'polygon', 'linestring']: place_root = Type.get_hierarchy('Place') for row in Db.get_by_shape(shape, extra_ids): description = row['description'].replace('"', '\"') \ if row['description'] else '' object_desc = row['object_desc'].replace('"', '\"') \ if row['object_desc'] else '' item = { 'type': 'Feature', 'geometry': json.loads(row['geojson']), 'properties': { 'objectId': row['object_id'], 'objectName': row['object_name'].replace('"', '\"'), 'objectDescription': object_desc, 'id': row['id'], 'name': row['name'].replace('"', '\"') if row['name'] else '', 'description': description, 'shapeType': row['type'] } } if 'types' in row and row['types']: type_ids = ast.literal_eval(f"[{row['types']}]") for type_id in list(set(type_ids)): type_ = g.types[type_id] if type_.root and type_.root[0] == place_root.id: item['properties']['objectType'] = \ type_.name.replace('"', '\"') break if structure and row['object_id'] == structure['super_id']: extra['supers'].append(item) elif row['object_id'] in object_ids: selected[shape].append(item) elif row['object_id'] in subunit_ids: # pragma no cover extra['subs'].append(item) elif row['object_id'] in sibling_ids: # pragma no cover extra['siblings'].append(item) else: all_[shape].append(item) if 'polygon_point' in row: polygon_point_item = dict(item) # Make a copy polygon_point_item['geometry'] = json.loads( row['polygon_point']) if row['object_id'] in object_ids: selected['polygon_point'].append(polygon_point_item) elif row['object_id'] and structure and \ row['object_id'] == structure['super_id']: extra['supers'].append(polygon_point_item) elif row['object_id'] in subunit_ids: # pragma no cover extra['subs'].append(polygon_point_item) elif row['object_id'] in sibling_ids: # pragma no cover extra['siblings'].append(polygon_point_item) else: all_['point'].append(polygon_point_item) return { 'gisPointAll': json.dumps(all_['point']), 'gisPointSelected': json.dumps(selected['point']), 'gisPointSupers': json.dumps(extra['supers']), 'gisPointSubs': json.dumps(extra['subs']), 'gisPointSibling': json.dumps(extra['siblings']), 'gisLineAll': json.dumps(all_['linestring']), 'gisLineSelected': json.dumps(selected['linestring']), 'gisPolygonAll': json.dumps(all_['polygon']), 'gisPolygonSelected': json.dumps(selected['polygon']), 'gisPolygonPointSelected': json.dumps(selected['polygon_point']), 'gisAllSelected': json.dumps(selected['polygon'] + selected['linestring'] + selected['point']) }
def add_fields(form: Any, class_: str, code: Union[str, None], entity: Union[Entity, Link, ReferenceSystem, Type, None], origin: Union[Entity, Type, None]) -> None: if class_ == 'actor_actor_relation': setattr(form, 'inverse', BooleanField(_('inverse'))) if not entity: setattr(form, 'actor', TableMultiField(_('actor'), [InputRequired()])) setattr(form, 'relation_origin_id', HiddenField()) elif class_ == 'artifact': setattr(form, 'actor', TableField(_('owned by'))) elif class_ in view_class_mapping['event']: setattr(form, 'event_id', HiddenField()) setattr(form, 'event', TableField(_('sub event of'))) setattr(form, 'event_preceding', TableField(_('preceding event'))) if class_ in ['activity', 'acquisition', 'production']: setattr(form, 'place', TableField(_('location'))) if class_ == 'acquisition': setattr(form, 'given_place', TableMultiField(_('given place'))) elif class_ == 'move': setattr(form, 'place_from', TableField(_('from'))) setattr(form, 'place_to', TableField(_('to'))) setattr(form, 'artifact', TableMultiField()) setattr(form, 'person', TableMultiField()) elif class_ == 'production': setattr(form, 'artifact', TableMultiField()) elif class_ == 'file' and not entity: setattr(form, 'file', MultipleFileField(_('file'), [InputRequired()])) if origin and origin.class_.view == 'reference': setattr(form, 'page', StringField()) elif class_ == 'group': setattr(form, 'residence', TableField(_('residence'))) setattr(form, 'begins_in', TableField(_('begins in'))) setattr(form, 'ends_in', TableField(_('ends in'))) elif class_ == 'hierarchy': if code == 'custom' or (entity and isinstance(entity, Type) and entity.category != 'value'): setattr( form, 'multiple', BooleanField(_('multiple'), description=_('tooltip hierarchy multiple'))) setattr( form, 'classes', SelectMultipleField(_('classes'), render_kw={'disabled': True}, description=_('tooltip hierarchy forms'), choices=[], option_widget=widgets.CheckboxInput(), widget=widgets.ListWidget(prefix_label=False))) elif class_ == 'involvement': if not entity and origin: involved_with = 'actor' \ if origin.class_.view == 'event' else 'event' setattr(form, involved_with, TableMultiField(_(involved_with), [InputRequired()])) setattr(form, 'activity', SelectField(_('activity'))) elif class_ == 'actor_function' and not entity: setattr(form, 'member_origin_id', HiddenField()) setattr(form, 'actor' if code == 'member' else 'group', TableMultiField(_('actor'), [InputRequired()])) elif class_ in g.view_class_mapping['type']: setattr(form, 'is_type_form', HiddenField()) type_ = entity if entity else origin if isinstance(type_, Type): root = g.types[type_.root[0]] if type_.root else type_ setattr(form, str(root.id), TreeField(str(root.id))) if root.directional: setattr(form, 'name_inverse', StringField(_('inverse'))) elif class_ == 'person': setattr(form, 'residence', TableField(_('residence'))) setattr(form, 'begins_in', TableField(_('born in'))) setattr(form, 'ends_in', TableField(_('died in'))) elif class_ == 'reference_system': setattr(form, 'website_url', StringField(_('website URL'), [OptionalValidator(), URL()])) setattr(form, 'resolver_url', StringField(_('resolver URL'), [OptionalValidator(), URL()])) setattr(form, 'placeholder', StringField(_('example ID'))) precision_id = str(Type.get_hierarchy('External reference match').id) setattr(form, precision_id, TreeField(precision_id)) if choices := ReferenceSystem.get_class_choices( entity): # type: ignore setattr( form, 'classes', SelectMultipleField( _('classes'), render_kw={'disabled': True}, choices=choices, option_widget=widgets.CheckboxInput(), widget=widgets.ListWidget(prefix_label=False)))
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 test_event(self) -> None: with app.app_context(): # Create entities for event place_name = 'Lewis and Clark' rv: Any = self.app.post(url_for('insert', class_='place'), data={ 'name': place_name, self.precision_geonames: '', self.precision_wikidata: '' }) residence_id = rv.location.split('/')[-1] actor_name = 'Captain Miller' with app.test_request_context(): app.preprocess_request() # type: ignore actor = Entity.insert('person', actor_name) file = Entity.insert('file', 'X-Files') source = Entity.insert('source', 'Necronomicon') carrier = Entity.insert('artifact', 'Artifact') reference = Entity.insert('external_reference', 'https://openatlas.eu') # Insert rv = self.app.get(url_for('insert', class_='activity')) assert b'+ Activity' in rv.data data = { 'name': 'Event Horizon', 'place': residence_id, self.precision_wikidata: '' } rv = self.app.post(url_for('insert', class_='activity', origin_id=reference.id), data=data, follow_redirects=True) assert bytes('Event Horizon', 'utf-8') in rv.data with app.test_request_context(): app.preprocess_request() # type: ignore activity_id = Entity.get_by_view('event')[0].id self.app.post(url_for('insert', class_='activity', origin_id=actor.id), data=data) self.app.post(url_for('insert', class_='activity', origin_id=file.id), data=data) self.app.post(url_for('insert', class_='activity', origin_id=source.id), data=data) rv = self.app.get( url_for('insert', class_='activity', origin_id=residence_id)) assert b'Location' in rv.data rv = self.app.get( url_for('insert', class_='move', origin_id=residence_id)) assert b'Location' not in rv.data # Acquisition event_name2 = 'Second event' wikidata = \ f"reference_system_id_" \ f"{ReferenceSystem.get_by_name('Wikidata').id}" precision = Type.get_hierarchy('External reference match').subs[0] rv = self.app.post(url_for('insert', class_='acquisition'), data={ 'name': event_name2, 'given_place': [residence_id], 'place': residence_id, 'event': activity_id, 'begin_year_from': '1949', 'begin_month_from': '10', 'begin_day_from': '8', 'end_year_from': '1951', wikidata: 'Q123', self.precision_wikidata: precision }) event_id = rv.location.split('/')[-1] rv = self.app.get(url_for('view', id_=event_id)) assert b'Event Horizon' in rv.data # Move rv = self.app.post(url_for('insert', class_='move'), data={ 'name': 'Keep it moving', 'place_to': residence_id, 'place_from': residence_id, 'artifact': carrier.id, 'person': actor.id, self.precision_wikidata: '' }) move_id = rv.location.split('/')[-1] rv = self.app.get(url_for('view', id_=move_id)) assert b'Keep it moving' in rv.data rv = self.app.get(url_for('view', id_=carrier.id)) assert b'Keep it moving' in rv.data rv = self.app.get(url_for('update', id_=move_id)) assert b'Keep it moving' in rv.data # Production rv = self.app.post(url_for('insert', class_='production'), data={ 'name': 'A very productive event', 'artifact': carrier.id, self.precision_wikidata: '' }) production_id = rv.location.split('/')[-1] rv = self.app.get(url_for('view', id_=production_id)) assert b'Artifact' in rv.data rv = self.app.get(url_for('view', id_=carrier.id)) assert b'A very productive event' in rv.data rv = self.app.get(url_for('update', id_=production_id)) assert b'A very productive event' in rv.data # Add another event and test if events are seen at place event_name3 = 'Third event' self.app.post(url_for('insert', class_='acquisition'), data={ 'name': event_name3, 'given_place': [residence_id], self.precision_geonames: '', self.precision_wikidata: '' }) rv = self.app.get(url_for('view', id_=residence_id)) assert bytes(place_name, 'utf-8') in rv.data rv = self.app.get(url_for('view', id_=actor.id)) assert bytes(actor_name, 'utf-8') in rv.data rv = self.app.post(url_for('insert', class_='acquisition'), follow_redirects=True, data={ 'name': 'Event Horizon', 'continue_': 'yes', self.precision_geonames: '', self.precision_wikidata: '' }) assert b'An entry has been created' in rv.data rv = self.app.get(url_for('index', view='event')) assert b'Event' in rv.data self.app.get(url_for('view', id_=activity_id)) # Add to event rv = self.app.get(url_for('entity_add_file', id_=event_id)) assert b'Link file' in rv.data rv = self.app.post(url_for('entity_add_file', id_=event_id), data={'checkbox_values': str([file.id])}, follow_redirects=True) assert b'X-Files' in rv.data rv = self.app.get(url_for('entity_add_reference', id_=event_id)) assert b'Link reference' in rv.data rv = self.app.post(url_for('entity_add_reference', id_=event_id), data={ 'reference': reference.id, 'page': '777' }, follow_redirects=True) assert b'777' in rv.data # Update rv = self.app.get(url_for('update', id_=activity_id)) assert b'Event Horizon' in rv.data rv = self.app.get(url_for('update', id_=event_id)) assert b'Event Horizon' in rv.data data['name'] = 'Event updated' rv = self.app.post(url_for('update', id_=event_id), data=data, follow_redirects=True) assert b'Changes have been saved' in rv.data # Test super event validation rv = self.app.post(url_for('update', id_=event_id), data={ 'name': 'Event', 'event': event_id, 'event_id': event_id }, follow_redirects=True) assert b'Self as super not allowed' in rv.data # Preceding event rv = self.app.post(url_for('update', id_=event_id), data={ 'name': 'Event', 'event_preceding': event_id, 'event_id': event_id }, follow_redirects=True) assert b'Self as preceding not allowed' in rv.data rv = self.app.post(url_for('update', id_=event_id), data={ 'name': 'Event with preceding', 'event_preceding': activity_id, 'event_id': event_id }, follow_redirects=True) assert b'Event with preceding' in rv.data rv = self.app.get(url_for('view', id_=activity_id)) assert b'Event with preceding' in rv.data # Delete rv = self.app.get( url_for('index', view='event', delete_id=event_id)) assert b'The entry has been deleted.' in rv.data