def admin_log() -> str: form = LogForm() form.user.choices = [(0, _('all'))] + User.get_users_for_form() table = Table(['date', 'priority', 'type', 'message', 'user', 'info'], order=[[0, 'desc']]) logs = logger.get_system_logs(form.limit.data, form.priority.data, form.user.data) for row in logs: user = None if row['user_id']: try: user = link(User.get_by_id(row['user_id'])) except AttributeError: # pragma: no cover - user already deleted user = f"id {row['user_id']}" table.rows.append([ format_datetime(row['created']), f"{row['priority']} {app.config['LOG_LEVELS'][row['priority']]}", row['type'], row['message'], user, row['info'] ]) return render_template( 'admin/log.html', table=table, form=form, title=_('admin'), crumbs=[[_('admin'), f"{url_for('admin_index')}#tab-general"], _('system log')])
def user_activity(user_id: int = 0) -> str: form = ActivityForm() form.user.choices = [(0, _('all'))] + User.get_users_for_form() if form.validate_on_submit(): activity = User.get_activities(int(form.limit.data), int(form.user.data), form.action.data) elif user_id: form.user.data = user_id activity = User.get_activities(100, user_id, 'all') else: activity = User.get_activities(100, 0, 'all') table = Table(['date', 'user', 'action', 'entity'], order=[[0, 'desc']]) for row in activity: try: entity = link(Entity.get_by_id(row['entity_id'])) except AttributeError: # pragma: no cover - entity already deleted entity = f"id {row['entity_id']}" user = User.get_by_id(row['user_id']) table.rows.append([ format_date(row['created']), link(user) if user else f"id {row['user_id']}", _(row['action']), entity ]) return render_template('user/activity.html', table=table, form=form, title=_('user'), crumbs=[[_('admin'), url_for('admin_index')], _('activity')])
def note_set_private(id_: int) -> Union[str, Response]: if not is_authorized('manager'): abort(403) # pragma: no cover note = User.get_note_by_id(id_) User.update_note(note['id'], note['text'], False) flash(_('note updated'), 'info') return redirect(f"{url_for('view', id_=note['entity_id'])}#tab-note")
def note_delete(id_: int) -> Response: note = User.get_note_by_id(id_) if not note['user_id'] == current_user.id: abort(403) # pragma: no cover User.delete_note(note['id']) flash(_('note deleted'), 'info') return redirect(f"{url_for('view', id_=note['entity_id'])}#tab-note")
def validate(self) -> bool: valid = FlaskForm.validate(self) username = '' user_email = '' if self.user_id: user = User.get_by_id(self.user_id) username = user.username user_email = user.email if username != self.username.data \ and User.get_by_username(self.username.data): self.username.errors.append(_('error username exists')) valid = False if user_email != self.email.data and User.get_by_email( self.email.data): self.email.errors.append(_('error email exists')) valid = False if getattr(self, 'password'): if self.password.data != self.password2.data: self.password.errors.append(_('error passwords must match')) self.password2.errors.append(_('error passwords must match')) valid = False if len(self.password.data) < \ session['settings']['minimum_password_length']: self.password.errors.append(_('error password too short')) valid = False return valid
def test_note(self) -> None: with app.app_context(): with app.test_request_context(): app.preprocess_request() # type: ignore actor = Entity.insert('person', 'Ripley') rv = self.app.get(url_for('note_insert', entity_id=actor.id)) assert b'Description' in rv.data rv = self.app.post(url_for('note_insert', entity_id=actor.id), data={'description': 'A nice description'}, follow_redirects=True) assert b'Note added' in rv.data rv = self.app.get(url_for('overview')) assert b'A nice description' in rv.data with app.test_request_context(): app.preprocess_request() # type: ignore user = User.get_by_username('Alice') if user: note_id = User.get_notes_by_user_id(user.id)[0]['id'] rv = self.app.get(url_for('note_update', id_=note_id)) assert b'A nice description' in rv.data rv = self.app.post(url_for('note_update', id_=note_id), data={'description': 'A very nice description'}, follow_redirects=True) assert b'Note updated' in rv.data \ and b'A very nice description' in rv.data rv = self.app.get(url_for('note_view', id_=note_id)) assert b'A very nice description' in rv.data rv = self.app.get(url_for('note_set_private', id_=note_id), follow_redirects=True) assert b'Note updated' in rv.data rv = self.app.get(url_for('note_delete', id_=note_id), follow_redirects=True) assert b'Note deleted' in rv.data
def reset_confirm(code: str) -> Response: # pragma: no cover user = User.get_by_reset_code(code) if not user or not user.username or not user.email: logger.log('info', 'auth', 'unknown reset code') flash(_('invalid password reset confirmation code'), 'error') abort(404) hours = session['settings']['reset_confirm_hours'] if datetime.datetime.now() > \ user.password_reset_date + datetime.timedelta(hours=hours): logger.log('info', 'auth', 'reset code expired') flash(_('This reset confirmation code has expired.'), 'error') abort(404) password = User.generate_password() user.password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') user.password_reset_code = None user.password_reset_date = None user.login_failed_count = 0 user.update() subject = _('New password for %(sitename)s', sitename=session['settings']['site_name']) body = _('New password for %(username)s', username=user.username) + ' ' body += f"{_('at')} {request.scheme}://{request.headers['Host']}:\n\n" body += f"{uc_first(_('username'))}: {user.username}\n" body += f"{uc_first(_('password'))}: {password}\n" if send_mail(subject, body, user.email, False): flash(_('A new password was sent to %(email)s.', email=user.email), 'info') else: flash( _('Failed to send password mail to %(email)s.', email=user.email), 'error') return redirect(url_for('login'))
def admin_newsletter() -> Union[str, Response]: class NewsLetterForm(FlaskForm): subject = StringField( '', [InputRequired()], render_kw={ 'placeholder': uc_first(_('subject')), 'autofocus': True}) body = TextAreaField( '', [InputRequired()], render_kw={'placeholder': uc_first(_('content'))}) save = SubmitField(_('send')) form = NewsLetterForm() form.save.label.text = uc_first(_('send')) if form.validate_on_submit(): # pragma: no cover count = 0 for user_id in request.form.getlist('recipient'): user = User.get_by_id(user_id) if user \ and user.settings['newsletter'] \ and user.active \ and user.email: code = User.generate_password() user.unsubscribe_code = code user.update() link_ = f"{request.scheme}://{request.headers['Host']}" link_ += url_for('index_unsubscribe', code=code) if send_mail( form.subject.data, f'{form.body.data}\n\n' f'{_("To unsubscribe use the link below.")}\n\n' f'{link_}', user.email): count += 1 flash(f"{_('Newsletter send')}: {count}", 'info') return redirect(url_for('admin_index')) table = Table(['username', 'email', 'receiver']) for user in User.get_all(): if user \ and user.settings['newsletter'] \ and user.active: # pragma: no cover table.rows.append([ user.username, user.email, f'<input value="{user.id}" name="recipient" type="checkbox" ' f'checked="checked">']) return render_template( 'admin/newsletter.html', form=form, table=table, title=_('newsletter'), crumbs=[ [_('admin'), f"{url_for('admin_index')}#tab-user"], _('newsletter')])
def get_log_for_advanced_view(entity_id: str) -> Dict[str, Any]: from openatlas.models.user import User data = Db.get_log_for_advanced_view(entity_id) return { 'creator': User.get_by_id(data['creator_id']) if data['creator_id'] else None, 'created': data['created'], 'modifier': User.get_by_id(data['modifier_id']) if data['modifier_id'] else None, 'modified': data['modified'], 'project': Import.get_project_by_id(data['project_id']) if data['project_id'] else None, 'importer': User.get_by_id(data['importer_id']) if data['importer_id'] else None, 'origin_id': data['origin_id']}
def note_insert(entity_id: int) -> Union[str, Response]: entity = Entity.get_by_id(entity_id) form = build_form('note') if form.validate_on_submit(): User.insert_note(entity_id, form.description.data, form.public.data) flash(_('note added'), 'info') return redirect(f"{url_for('view', id_=entity.id)}#tab-note") return render_template('display_form.html', form=form, entity=entity, crumbs=[[ _(entity.class_.view), url_for('index', view=entity.class_.view) ], entity, f"+ {uc_first(_('note'))}"])
def user_insert() -> Union[str, Response]: form = UserForm() form.group.choices = get_groups() if not session['settings']['mail']: del form.send_info if form.validate_on_submit(): user_id = User.insert(form) flash(_('user created'), 'info') if session['settings']['mail'] \ and form.send_info.data: # pragma: no cover subject = _('Your account information for %(sitename)s', sitename=session['settings']['site_name']) body = _('Account information for %(username)s', username=form.username.data) + \ f" {_('at')} {request.scheme}://{request.headers['Host']}\n\n" \ f"{uc_first(_('username'))}: {form.username.data}\n" \ f"{uc_first(_('password'))}: {form.password.data}\n" if send_mail(subject, body, form.email.data, False): flash( _('Sent account information mail to %(email)s.', email=form.email.data), 'info') else: flash( _('Failed to send account details to %(email)s.', email=form.email.data), 'error') if hasattr(form, 'continue_') and form.continue_.data == 'yes': return redirect(url_for('user_insert')) return redirect(url_for('user_view', id_=user_id)) return render_template( 'user/insert.html', form=form, title=_('user'), crumbs=[[_('admin'), f"{url_for('admin_index')}#tab-user"], f"+ {uc_first(_('user'))}"])
def note_view(id_: int) -> str: note = User.get_note_by_id(id_) if not note['public'] and note['user_id'] != current_user.id: abort(403) # pragma: no cover entity = Entity.get_by_id(note['entity_id']) buttons: list[str] = [manual('tools/notes')] if note['user_id'] == current_user.id: buttons += [ button(_('edit'), url_for('note_update', id_=note['id'])), button(_('delete'), url_for('note_delete', id_=note['id'])) ] elif is_authorized('manager'): # pragma: no cover buttons += [ button(_('set private'), url_for('note_set_private', id_=note['id'])) ] tabs = { 'info': Tab('info', buttons=buttons, content=f"<h1>{uc_first(_('note'))}</h1>{note['text']}") } return render_template('tabs.html', tabs=tabs, entity=entity, crumbs=[[ _(entity.class_.view), url_for('index', view=entity.class_.view) ], link(entity), _('note')])
def user_update(id_: int) -> Union[str, Response]: user = User.get_by_id(id_) if user.group == 'admin' and current_user.group != 'admin': abort(403) # pragma: no cover form = UserForm(obj=user) form.user_id = id_ del form.password, form.password2, form.send_info del form.insert_and_continue, form.show_passwords form.group.choices = get_groups() if user and form.validate_on_submit(): # Active is always true if current user to prevent self deactivation user.active = True if user.id == current_user.id else form.active.data user.real_name = form.real_name.data user.username = form.username.data user.email = form.email.data user.description = form.description.data user.group = form.group.data user.update() flash(_('info update'), 'info') return redirect(url_for('user_view', id_=id_)) if user.id == current_user.id: del form.active return render_template( 'display_form.html', form=form, title=user.username, manual_page='admin/user', crumbs=[[_('admin'), f"{url_for('admin_index')}#tab-user"], user, _('edit')])
def user_view(id_: int) -> str: user = User.get_by_id(id_) info = { _('username'): user.username, _('group'): user.group, _('full name'): user.real_name, _('email'): user.email if is_authorized('manager') or user.settings['show_email'] else '', _('language'): user.settings['language'], _('last login'): format_date(user.login_last_success), _('failed logins'): user.login_failed_count if is_authorized('manager') else '' } return render_template( 'user/view.html', user=user, info=info, title=user.username, crumbs=[[_('admin'), f"{url_for('admin_index')}#tab-user"], user.username])
def reset_password() -> Union[str, Response]: if current_user.is_authenticated: # Prevent password reset if logged in return redirect(url_for('overview')) form = PasswordResetForm() if form.validate_on_submit() \ and session['settings']['mail']: # pragma: no cover user = User.get_by_email(form.email.data) if not user: logger.log('info', 'password', f'Password reset for non existing {form.email.data}') flash(_('error non existing email'), 'error') else: code = User.generate_password() user.password_reset_code = code user.password_reset_date = datetime.datetime.now() user.update() url = url_for('reset_confirm', code=code) link = f"{request.scheme}://{request.headers['Host']}{url}" subject = _('Password reset request for %(site_name)s', site_name=session['settings']['site_name']) body = _('We received a password reset request for %(username)s', username=user.username) body += \ f" {_('at')} {request.headers['Host']}\n\n" \ f"{_('reset password link')}:\n\n" \ f"{link}\n\n" \ f"{_('The link is valid for')} " \ f"{session['settings']['reset_confirm_hours']} {_('hours')}." email = form.email.data if send_mail(subject, body, form.email.data): flash( _( 'A password reset confirmation mail was send ' 'to %(email)s.', email=email), 'info') else: flash( _( 'Failed to send password reset confirmation mail ' 'to %(email)s.', email=email), 'error') return redirect(url_for('login')) return render_template('login/reset_password.html', form=form, crumbs=[[_('login'), url_for('login')], _('Forgot your password?')])
def note_update(id_: int) -> Union[str, Response]: note = User.get_note_by_id(id_) if not note['user_id'] == current_user.id: abort(403) # pragma: no cover entity = Entity.get_by_id(note['entity_id']) form = build_form('note') if form.validate_on_submit(): User.update_note(note['id'], form.description.data, form.public.data) flash(_('note updated'), 'info') return redirect(f"{url_for('view', id_=note['entity_id'])}#tab-note") form.save.label.text = _('save') form.description.data = note['text'] form.public.data = note['public'] return render_template('display_form.html', form=form, entity=entity, crumbs=[[ _(entity.class_.view), url_for('index', view=entity.class_.view) ], entity, _('edit note')])
def index_unsubscribe(code: str) -> str: user = User.get_by_unsubscribe_code(code) text = _('unsubscribe link not valid') if user: # pragma: no cover user.settings['newsletter'] = '' user.unsubscribe_code = '' user.update() user.remove_newsletter() text = _('You have successfully unsubscribed. ' 'You can subscribe again in your Profile.') return render_template('index/unsubscribe.html', text=text, crumbs=[_('unsubscribe newsletter')])
def add_note_tab(entity: Entity) -> Tab: tab = Tab('note', entity=entity) for note in current_user.get_notes_by_entity_id(entity.id): data = [ format_date(note['created']), uc_first(_('public')) if note['public'] else uc_first(_('private')), link(User.get_by_id(note['user_id'])), note['text'], f'<a href="{url_for("note_view", id_=note["id"])}">' f'{uc_first(_("view"))}</a>'] tab.table.rows.append(data) return tab
def admin_newsletter() -> Union[str, Response]: form = NewsLetterForm() form.save.label.text = uc_first(_('send')) if form.validate_on_submit(): # pragma: no cover recipients = 0 for user_id in (request.form.getlist('recipient')): user = User.get_by_id(user_id) if user and user.settings[ 'newsletter'] and user.active and user.email: code = User.generate_password() user.unsubscribe_code = code user.update() link_ = request.scheme + '://' + request.headers['Host'] link_ += url_for('index_unsubscribe', code=code) unsubscribe = '\n\n' + _( 'To unsubscribe use the link below.') + '\n\n' + link_ if send_mail(form.subject.data, form.body.data + unsubscribe, user.email): recipients += 1 flash(_('Newsletter send') + ': ' + str(recipients), 'info') return redirect(url_for('admin_index')) table = Table(['username', 'email', 'receiver']) for user in User.get_all(): if user and user.settings[ 'newsletter'] and user.active: # pragma: no cover table.rows.append([ user.username, user.email, '<input value="{id}" name="recipient" type="checkbox" checked="checked">' .format(id=user.id) ]) return render_template( 'admin/newsletter.html', form=form, table=table, title=_('newsletter'), crumbs=[[_('admin'), url_for('admin_index') + '#tab-user'], _('newsletter')])
def login() -> Union[str, Response]: if current_user.is_authenticated: return redirect('/') form = LoginForm() if form.validate_on_submit(): user = User.get_by_username(request.form['username']) if user and user.username: if user.login_attempts_exceeded(): logger.log('notice', 'auth', 'Login attempts exceeded: ' + user.username) flash(_('error login attempts exceeded'), 'error') return render_template('login/index.html', form=form) hash_ = hashpw(request.form['password'].encode('utf-8'), user.password.encode('utf-8')) if hash_ == user.password.encode('utf-8'): if user.active: login_user(user) session['login_previous_success'] = user.login_last_success session[ 'login_previous_failures'] = user.login_failed_count if user.settings['language']: session['language'] = user.settings['language'] user.login_last_success = datetime.datetime.now() user.login_failed_count = 0 user.update() logger.log('info', 'auth', 'Login of ' + user.username) return redirect( request.args.get('next') or url_for('overview')) else: logger.log('notice', 'auth', 'Inactive login try ' + user.username) flash(_('error inactive'), 'error') else: logger.log('notice', 'auth', 'Wrong password: '******'error wrong password'), 'error') else: logger.log('notice', 'auth', 'Wrong username: '******'username']) flash(_('error username'), 'error') return render_template('login/index.html', title=_('login'), crumbs=[_('login')], form=form)
def validate(self, extra_validators=None): valid = Form.validate(self) user = UserMapper.get_by_id(self.user_id) if self.user_id else User() if user.username != self.username.data and UserMapper.get_by_username(self.username.data): self.username.errors.append(str(_('error username exists'))) valid = False if user.email != self.email.data and UserMapper.get_by_email(self.email.data): self.email.errors.append(str(_('error email exists'))) valid = False if getattr(self, 'password'): if self.password.data != self.password2.data: self.password.errors.append(_('error passwords must match')) self.password2.errors.append(_('error passwords must match')) valid = False if len(self.password.data) < session['settings']['minimum_password_length']: self.password.errors.append(_('error password too short')) valid = False return valid
def note_view(id_: int) -> str: note = User.get_note_by_id(id_) if not note['public'] and note['user_id'] != current_user.id: abort(403) # pragma: no cover entity = Entity.get_by_id(note['entity_id']) buttons: List[str] = [] if note['user_id'] == current_user.id: buttons = [ button(_('edit'), url_for('note_update', id_=note['id'])), button(_('delete'), url_for('note_delete', id_=note['id']))] elif is_authorized('manager'): # pragma: no cover buttons = [button(_('set private'), url_for('note_set_private', id_=note['id']))] return render_template( 'user/note.html', entity=entity, note=note, tabs={'info': Tab('info')}, buttons=buttons, crumbs=[ [_(entity.class_.view), url_for('index', view=entity.class_.view)], link(entity), _('note')])
def system_warnings(_context: str, _unneeded_string: str) -> str: if not is_authorized('manager'): return '' warnings = [] if app.config['DATABASE_VERSION'] != g.settings['database_version']: warnings.append( f"Database version {app.config['DATABASE_VERSION']} is needed but " f"current version is {g.settings['database_version']}") for path in app.config['WRITABLE_DIRS']: if not os.access(path, os.W_OK): warnings.append(f"{uc_first(_('directory not writable'))}: " f"{str(path).replace(app.root_path, '')}") if is_authorized('admin'): from openatlas.models.user import User user = User.get_by_username('OpenAtlas') if user and user.active: hash_ = hashpw('change_me_PLEASE!'.encode('utf-8'), user.password.encode('utf-8')) if hash_ == user.password.encode('utf-8'): warnings.append( "User OpenAtlas with default password is still active!") if warnings: return Markup(f'<p class="error">{"<br>".join(warnings)}<p>') return '' # pragma: no cover
def overview() -> str: tabs = { 'info': Tab('info'), 'bookmarks': Tab('bookmarks', table=Table(['name', 'class', 'begin', 'end'])), 'notes': Tab('notes', table=Table( ['date', _('visibility'), 'entity', 'class', _('note')])) } tables = { 'overview': Table(paging=False, defs=[{ 'className': 'dt-body-right', 'targets': 1 }]), 'latest': Table(paging=False, order=[[0, 'desc']]) } if current_user.is_authenticated and hasattr(current_user, 'bookmarks'): for entity_id in current_user.bookmarks: entity = Entity.get_by_id(entity_id) tabs['bookmarks'].table.rows.append([ link(entity), entity.class_.label, entity.first, entity.last, bookmark_toggle(entity.id, True) ]) for note in User.get_notes_by_user_id(current_user.id): entity = Entity.get_by_id(note['entity_id']) tabs['notes'].table.rows.append([ format_date(note['created']), uc_first(_('public') if note['public'] else _('private')), link(entity), entity.class_.label, note['text'], f'<a href="{url_for("note_view", id_=note["id"])}">' f'{uc_first(_("view"))}</a>' ]) for name, count in Entity.get_overview_counts().items(): if count: url = url_for('index', view=g.class_view_mapping[name]) if name == 'administrative_unit': url = f"{url_for('node_index')}#menu-tab-places" elif name == 'type': url = url_for('node_index') elif name == 'find': url = url_for('index', view='artifact') elif name in [ 'feature', 'human_remains', 'stratigraphic_unit', 'source_translation' ]: url = '' tables['overview'].rows.append([ link(g.classes[name].label, url) if url else g.classes[name].label, format_number(count) ]) for entity in Entity.get_latest(8): tables['latest'].rows.append([ format_date(entity.created), link(entity), entity.class_.label, entity.first, entity.last, link(logger.get_log_for_advanced_view(entity.id)['creator']) ]) tabs['info'].content = render_template('index/index.html', intro=get_translation('intro'), tables=tables) return render_template('tabs.html', tabs=tabs, crumbs=['overview'])
def test_user(self) -> None: data = { 'active': '', 'username': '******', 'email': '*****@*****.**', 'password': '******', 'password2': 'you_never_guess_this', 'group': 'admin', 'name': 'Ripley Weaver', 'description': '', 'send_info': '' } data2 = { 'active': '', 'username': '******', 'email': '*****@*****.**', 'password': '******', 'password2': 'you_never_guess_this', 'group': 'admin', 'name': 'Newt', 'continue_': 'yes', 'send_info': '' } with app.app_context(): # type: ignore with app.test_request_context(): app.preprocess_request() # type: ignore logged_in_user = User.get_by_username('Alice') logged_in_user.remove_newsletter() if not logged_in_user: abort(404) # pragma: no cover rv = self.app.get(url_for('user_insert')) assert b'+ User' in rv.data rv = self.app.post(url_for('user_insert'), data=data) user_id = rv.location.split('/')[-1] data['password'] = '******' rv = self.app.post(url_for('user_insert'), data=data) assert b'match' in rv.data # Test insert with continue rv = self.app.post(url_for('user_insert'), follow_redirects=True, data=data2) assert b'Newt' not in rv.data rv = self.app.get(url_for('user_view', id_=user_id)) assert b'Ripley' in rv.data rv = self.app.get(url_for('user_update', id_=logged_in_user.id)) assert b'Alice' in rv.data data['description'] = 'The warrant officer' rv = self.app.post(url_for('user_update', id_=user_id), data=data, follow_redirects=True) assert b'The warrant officer' in rv.data rv = self.app.get( url_for('admin_index', action='delete_user', id_=user_id)) assert b'User deleted' in rv.data # Test activity log data = { 'name': 'test', 'description': 'test' } # insert a reference to show something self.app.post(url_for('insert', class_='bibliography'), data=data) rv = self.app.get(url_for('user_activity')) assert b'Activity' in rv.data rv = self.app.get(url_for('user_activity', user_id=user_id)) assert b'Activity' in rv.data data = {'limit': 'all', 'action': 'all', 'user': '******'} rv = self.app.post(url_for('user_activity', data=data)) assert b'Activity' in rv.data # Test missing permission self.app.get(url_for('logout'), follow_redirects=True) rv = self.app.get(url_for('user_insert'), follow_redirects=True) assert b'Forgot your password?' not in rv.data self.app.post('/login', data={ 'username': '******', 'password': '******' }) rv = self.app.get(url_for('user_insert'), follow_redirects=True) assert b'403 - Forbidden' in rv.data
def admin_index(action: Optional[str] = None, id_: Optional[int] = None) -> Union[str, Response]: if is_authorized('manager'): if id_ and action == 'delete_user': user = User.get_by_id(id_) if not user \ or user.id == current_user.id \ or (user.group == 'admin' and not is_authorized('admin')): abort(403) User.delete(id_) flash(_('user deleted'), 'info') elif action == 'remove_logo': Settings.set_logo() return redirect(f"{url_for('admin_index')}#tab-file") tables = { 'user': Table([ 'username', 'name', 'group', 'email', 'newsletter', 'created', 'last login', 'entities' ], defs=[{ 'className': 'dt-body-right', 'targets': 7 }]), 'content': Table(['name'] + list(app.config['LANGUAGES'])) } for user in User.get_all(): count = User.get_created_entities_count(user.id) email = user.email \ if is_authorized('manager') or user.settings['show_email'] else '' tables['user'].rows.append([ link(user), user.real_name, user.group, email, _('yes') if user.settings['newsletter'] else '', format_date(user.created), format_date(user.login_last_success), format_number(count) if count else '' ]) for item, languages in get_content().items(): content = [uc_first(_(item))] for language in app.config['LANGUAGES']: content.append(sanitize(languages[language], 'text')) content.append(link(_('edit'), url_for('admin_content', item=item))) tables['content'].rows.append(content) form = None if is_authorized('admin'): form = TestMailForm() if form.validate_on_submit( ) and g.settings['mail']: # pragma: no cover subject = _('Test mail from %(site_name)s', site_name=g.settings['site_name']) body = _('This test mail was sent by %(username)s', username=current_user.username) body += f" {_('at')} '{request.headers['Host']}" if send_mail(subject, body, form.receiver.data): flash( _('A test mail was sent to %(email)s.', email=form.receiver.data), 'info') else: form.receiver.data = current_user.email tabs = { 'files': Tab(_('files'), buttons=[ manual('entity/file'), button(_('edit'), url_for('admin_settings', category='files')) if is_authorized('manager') else '', button(_('list'), url_for('index', view='file')), button(_('file'), url_for('insert', class_='file')) ], content=render_template('admin/file.html', info=get_form_settings(FilesForm()), disk_space_info=get_disk_space_info())), 'user': Tab(_('user'), table=tables['user'], buttons=[ manual('admin/user'), button(_('activity'), url_for('user_activity')), button(_('newsletter'), url_for('admin_newsletter')) if is_authorized('manager') and g.settings['mail'] else '', button(_('user'), url_for('user_insert')) if is_authorized('manager') else '' ]) } if is_authorized('admin'): tabs['general'] = Tab( 'general', content=display_info(get_form_settings(GeneralForm())), buttons=[ manual('admin/general'), button(_('edit'), url_for('admin_settings', category='general')), button(_('system log'), url_for('admin_log')) ]) tabs['email'] = Tab( 'email', content=display_info(get_form_settings(MailForm())), buttons=[ manual('admin/mail'), button(_('edit'), url_for('admin_settings', category='mail')) ]) if g.settings['mail']: tabs['email'].content += display_form(form) if is_authorized('manager'): tabs['modules'] = Tab(_('modules'), content=f""" <h1>{_('Defaults for new user')}</h1> {display_info(get_form_settings(ModulesForm()))}""", buttons=[ manual('admin/modules'), button( _('edit'), url_for('admin_settings', category='modules')) ]) tabs['map'] = Tab('map', content=display_info(get_form_settings(MapForm())), buttons=[ manual('admin/map'), button(_('edit'), url_for('admin_settings', category='map')) ]) tabs['content'] = Tab('content', content=tables['content'].display(), buttons=[manual('admin/content')]) if is_authorized('contributor'): tabs['data'] = Tab('data', content=render_template( 'admin/data.html', imports=Import.get_all_projects(), info=get_form_settings(ApiForm()))) return render_template('tabs.html', tabs=tabs, title=_('admin'), crumbs=[_('admin')])
def load_user(user_id: int) -> Optional[User]: return User.get_by_id(user_id, True)
def entity_view(id_: int) -> Union[str, Response]: if id_ in g.nodes: # Nodes have their own view entity = g.nodes[id_] if not entity.root: if entity.class_.name == 'administrative_unit': tab_hash = '#menu-tab-places_collapse-' elif entity.standard: tab_hash = '#menu-tab-standard_collapse-' elif entity.value_type: tab_hash = '#menu-tab-value_collapse-' else: tab_hash = '#menu-tab-custom_collapse-' return redirect(f"{url_for('node_index')}{tab_hash}{id_}") elif id_ in g.reference_systems: entity = g.reference_systems[id_] else: entity = Entity.get_by_id(id_, nodes=True, aliases=True) if not entity.class_.view: flash(_("This entity can't be viewed directly."), 'error') abort(400) event_links = None # Needed for actor overlays = None # Needed for place tabs = {'info': Tab('info')} if isinstance(entity, Node): tabs['subs'] = Tab('subs', entity=entity) tabs['entities'] = Tab('entities', entity=entity) root = g.nodes[entity.root[-1]] if entity.root else None if root and root.value_type: # pragma: no cover tabs['entities'].table.header = [ _('name'), _('value'), _('class'), _('info') ] for item in entity.get_linked_entities(['P2', 'P89'], inverse=True, nodes=True): if item.class_.name in ['location', 'reference_system']: continue # pragma: no cover if item.class_.name == 'object_location': # pragma: no cover item = item.get_linked_entity_safe('P53', inverse=True) data = [link(item)] if root and root.value_type: # pragma: no cover data.append(format_number(item.nodes[entity])) data.append(item.class_.label) data.append(item.description) tabs['entities'].table.rows.append(data) for sub_id in entity.subs: sub = g.nodes[sub_id] tabs['subs'].table.rows.append( [link(sub), sub.count, sub.description]) if not tabs['entities'].table.rows: # If no entities available get links with this type_id tabs['entities'].table.header = [_('domain'), _('range')] for row in Link.get_entities_by_node(entity): tabs['entities'].table.rows.append([ link(Entity.get_by_id(row['domain_id'])), link(Entity.get_by_id(row['range_id'])) ]) elif isinstance(entity, ReferenceSystem): for form_id, form in entity.get_forms().items(): tabs[form['name']] = Tab(form['name'], entity=entity) tabs[form['name']].table = \ Table([_('entity'), 'id', _('precision')]) for link_ in entity.get_links('P67'): name = link_.description if entity.resolver_url: name = \ f'<a href="{entity.resolver_url + name}"' \ f' target="_blank" rel="noopener noreferrer">{name}</a>' tab_name = link_.range.class_.name tabs[tab_name].table.rows.append( [link(link_.range), name, link_.type.name]) for form_id, form in entity.get_forms().items(): tabs[form['name']].buttons = [] if not tabs[form['name']].table.rows and is_authorized('manager'): tabs[form['name']].buttons = [ button( _('remove'), url_for('reference_system_remove_form', system_id=entity.id, form_id=form_id)) ] elif entity.class_.view == 'actor': for name in [ 'source', 'event', 'relation', 'member_of', 'member', 'artifact' ]: tabs[name] = Tab(name, entity=entity) event_links = entity.get_links(['P11', 'P14', 'P22', 'P23', 'P25'], True) for link_ in event_links: event = link_.domain places = event.get_linked_entities(['P7', 'P26', 'P27']) link_.object_ = None # Needed for first/last appearance for place in places: object_ = place.get_linked_entity_safe('P53', True) entity.linked_places.append(object_) link_.object_ = object_ first = link_.first if not link_.first and event.first: first = f'<span class="inactive">{event.first}</span>' last = link_.last if not link_.last and event.last: last = f'<span class="inactive">{event.last}</span>' data = [ link(event), event.class_.label, _('moved') if link_.property.code == 'P25' else link( link_.type), first, last, link_.description ] if link_.property.code == 'P25': data += [''] else: add_edit_link( data, url_for('involvement_update', id_=link_.id, origin_id=entity.id)) add_remove_link(data, link_.domain.name, link_, entity, 'event') tabs['event'].table.rows.append(data) for link_ in entity.get_links('OA7') + entity.get_links('OA7', True): type_ = '' if entity.id == link_.domain.id: related = link_.range if link_.type: type_ = link(link_.type.get_name_directed(), url_for('entity_view', id_=link_.type.id)) else: related = link_.domain if link_.type: type_ = link(link_.type.get_name_directed(True), url_for('entity_view', id_=link_.type.id)) data = [ type_, link(related), link_.first, link_.last, link_.description ] add_edit_link( data, url_for('relation_update', id_=link_.id, origin_id=entity.id)) add_remove_link(data, related.name, link_, entity, 'relation') tabs['relation'].table.rows.append(data) for link_ in entity.get_links('P107', True): data = [ link(link_.domain), link(link_.type), link_.first, link_.last, link_.description ] add_edit_link( data, url_for('member_update', id_=link_.id, origin_id=entity.id)) add_remove_link(data, link_.domain.name, link_, entity, 'member-of') tabs['member_of'].table.rows.append(data) if entity.class_.name != 'group': del tabs['member'] else: for link_ in entity.get_links('P107'): data = [ link(link_.range), link(link_.type), link_.first, link_.last, link_.description ] add_edit_link( data, url_for('member_update', id_=link_.id, origin_id=entity.id)) add_remove_link(data, link_.range.name, link_, entity, 'member') tabs['member'].table.rows.append(data) for link_ in entity.get_links('P52', True): data = [ link(link_.domain), link_.domain.class_.label, link(link_.domain.standard_type), link_.domain.first, link_.domain.last, link_.domain.description ] tabs['artifact'].table.rows.append(data) elif entity.class_.view == 'artifact': tabs['source'] = Tab('source', entity=entity) elif entity.class_.view == 'event': for name in ['subs', 'source', 'actor']: tabs[name] = Tab(name, entity=entity) for sub_event in entity.get_linked_entities('P117', inverse=True, nodes=True): tabs['subs'].table.rows.append(get_base_table_data(sub_event)) tabs['actor'].table.header.insert(5, _('activity')) # Activity column for link_ in entity.get_links(['P11', 'P14', 'P22', 'P23']): first = link_.first if not link_.first and entity.first: first = f'<span class="inactive">{entity.first}</span>' last = link_.last if not link_.last and entity.last: last = f'<span class="inactive">{entity.last}</span>' data = [ link(link_.range), link_.range.class_.label, link_.type.name if link_.type else '', first, last, g.properties[link_.property.code].name_inverse, link_.description ] add_edit_link( data, url_for('involvement_update', id_=link_.id, origin_id=entity.id)) add_remove_link(data, link_.range.name, link_, entity, 'actor') tabs['actor'].table.rows.append(data) entity.linked_places = [ location.get_linked_entity_safe('P53', True) for location in entity.get_linked_entities(['P7', 'P26', 'P27']) ] elif entity.class_.view == 'file': for name in [ 'source', 'event', 'actor', 'place', 'feature', 'stratigraphic_unit', 'artifact', 'human_remains', 'reference', 'type' ]: tabs[name] = Tab(name, entity=entity) entity.image_id = entity.id if get_file_path(entity.id) else None for link_ in entity.get_links('P67'): range_ = link_.range data = get_base_table_data(range_) add_remove_link(data, range_.name, link_, entity, range_.class_.name) tabs[range_.class_.view].table.rows.append(data) for link_ in entity.get_links('P67', True): data = get_base_table_data(link_.domain) data.append(link_.description) add_edit_link( data, url_for('reference_link_update', link_id=link_.id, origin_id=entity.id)) add_remove_link(data, link_.domain.name, link_, entity, 'reference') tabs['reference'].table.rows.append(data) elif entity.class_.view == 'place': tabs['source'] = Tab('source', entity=entity) if entity.class_.name == 'place': tabs['event'] = Tab('event', entity=entity) tabs['reference'] = Tab('reference', entity=entity) if entity.class_.name == 'place': tabs['actor'] = Tab('actor', entity=entity) tabs['feature'] = Tab('feature', entity=entity) elif entity.class_.name == 'feature': tabs['stratigraphic_unit'] = Tab('stratigraphic_unit', entity=entity) elif entity.class_.name == 'stratigraphic_unit': tabs['find'] = Tab('find', entity=entity) tabs['human_remains'] = Tab('human_remains', entity=entity) entity.location = entity.get_linked_entity_safe('P53', nodes=True) event_ids = [] # Keep track of inserted events to prevent doubles for event in entity.location.get_linked_entities(['P7', 'P26', 'P27'], inverse=True): tabs['event'].table.rows.append(get_base_table_data(event)) event_ids.append(event.id) for event in entity.get_linked_entities('P24', inverse=True): if event.id not in event_ids: # Don't add again if already in table tabs['event'].table.rows.append(get_base_table_data(event)) if 'actor' in tabs: for link_ in entity.location.get_links(['P74', 'OA8', 'OA9'], inverse=True): actor = Entity.get_by_id(link_.domain.id) tabs['actor'].table.rows.append([ link(actor), g.properties[link_.property.code].name, actor.class_.name, actor.first, actor.last, actor.description ]) elif entity.class_.view == 'reference': for name in [ 'source', 'event', 'actor', 'place', 'feature', 'stratigraphic_unit', 'human_remains', 'artifact', 'file' ]: tabs[name] = Tab(name, entity=entity) for link_ in entity.get_links('P67'): range_ = link_.range data = get_base_table_data(range_) data.append(link_.description) add_edit_link( data, url_for('reference_link_update', link_id=link_.id, origin_id=entity.id)) add_remove_link(data, range_.name, link_, entity, range_.class_.name) tabs[range_.class_.view].table.rows.append(data) elif entity.class_.view == 'source': for name in [ 'actor', 'artifact', 'feature', 'event', 'human_remains', 'place', 'stratigraphic_unit', 'text' ]: tabs[name] = Tab(name, entity=entity) for text in entity.get_linked_entities('P73', nodes=True): tabs['text'].table.rows.append([ link(text), next(iter(text.nodes)).name if text.nodes else '', text.description ]) for link_ in entity.get_links('P67'): range_ = link_.range data = get_base_table_data(range_) add_remove_link(data, range_.name, link_, entity, range_.class_.name) tabs[range_.class_.view].table.rows.append(data) if entity.class_.view in [ 'actor', 'artifact', 'event', 'place', 'source', 'type' ]: if entity.class_.view != 'reference' and not isinstance(entity, Node): tabs['reference'] = Tab('reference', entity=entity) if entity.class_.view == 'artifact': tabs['event'] = Tab('event', entity=entity) for link_ in entity.get_links('P25', True): data = get_base_table_data(link_.domain) tabs['event'].table.rows.append(data) tabs['file'] = Tab('file', entity=entity) entity.image_id = entity.get_profile_image_id() if entity.class_.view == 'place' and is_authorized('editor') and \ current_user.settings['module_map_overlay']: tabs['file'].table.header.append(uc_first(_('overlay'))) for link_ in entity.get_links('P67', inverse=True): domain = link_.domain data = get_base_table_data(domain) if domain.class_.view == 'file': # pragma: no cover extension = data[3] data.append( get_profile_image_table_link(domain, entity, extension, entity.image_id)) if not entity.image_id \ and extension in app.config['DISPLAY_FILE_EXTENSIONS']: entity.image_id = domain.id if entity.class_.view == 'place' \ and is_authorized('editor') \ and current_user.settings['module_map_overlay']: overlays = Overlay.get_by_object(entity) if extension in app.config['DISPLAY_FILE_EXTENSIONS']: if domain.id in overlays: add_edit_link( data, url_for('overlay_update', id_=overlays[domain.id].id)) else: data.append( link( _('link'), url_for('overlay_insert', image_id=domain.id, place_id=entity.id, link_id=link_.id))) else: # pragma: no cover data.append('') if domain.class_.view not in ['source', 'file']: data.append(link_.description) add_edit_link( data, url_for('reference_link_update', link_id=link_.id, origin_id=entity.id)) if domain.class_.view == 'reference_system': entity.reference_systems.append(link_) continue add_remove_link(data, domain.name, link_, entity, domain.class_.view) tabs[domain.class_.view].table.rows.append(data) structure = None # Needed for place gis_data = None # Needed for place if entity.class_.view in ['artifact', 'place']: structure = get_structure(entity) if structure: for item in structure['subunits']: tabs[item.class_.name].table.rows.append( get_base_table_data(item)) gis_data = Gis.get_all([entity], structure) if gis_data['gisPointSelected'] == '[]' \ and gis_data['gisPolygonSelected'] == '[]' \ and gis_data['gisLineSelected'] == '[]' \ and (not structure or not structure['super_id']): gis_data = {} if not gis_data: gis_data = Gis.get_all(entity.linked_places) \ if entity.linked_places else None entity.info_data = get_entity_data(entity, event_links=event_links) tabs['note'] = Tab('note', entity=entity) for note in current_user.get_notes_by_entity_id(entity.id): data = [ format_date(note['created']), uc_first(_('public')) if note['public'] else uc_first(_('private')), link(User.get_by_id(note['user_id'])), note['text'], f'<a href="{url_for("note_view", id_=note["id"])}">' f'{uc_first(_("view"))}</a>' ] tabs['note'].table.rows.append(data) if 'file' in tabs and current_user.settings['table_show_icons'] and \ session['settings']['image_processing']: tabs['file'].table.header.insert(1, uc_first(_('icon'))) for row in tabs['file'].table.rows: row.insert( 1, file_preview( int(row[0].replace('<a href="/entity/', '').split('"')[0]))) tabs['info'].content = render_template( 'entity/view.html', buttons=add_buttons(entity), entity=entity, gis_data=gis_data, structure=structure, # Needed for place views overlays=overlays, # Needed for place views title=entity.name) return render_template('tabs.html', tabs=tabs, gis_data=gis_data, crumbs=add_crumbs(entity, structure), entity=entity)
def admin_index(action: Optional[str] = None, id_: Optional[int] = None) -> Union[str, Response]: if is_authorized('manager'): if id_ and action == 'delete_user': user = User.get_by_id(id_) if not user \ or user.id == current_user.id \ or (user.group == 'admin' and not is_authorized('admin')): abort(403) # pragma: no cover User.delete(id_) flash(_('user deleted'), 'info') elif action == 'remove_logo': Settings.set_logo() return redirect(url_for('admin_index') + '#tab-file') dirs = { 'uploads': True if os.access(app.config['UPLOAD_DIR'], os.W_OK) else False, 'export/sql': True if os.access(app.config['EXPORT_DIR'] / 'sql', os.W_OK) else False, 'export/csv': True if os.access(app.config['EXPORT_DIR'] / 'csv', os.W_OK) else False } tables = { 'user': Table([ 'username', 'name', 'group', 'email', 'newsletter', 'created', 'last login', 'entities' ]), 'content': Table(['name'] + [language for language in app.config['LANGUAGES'].keys()]) } for user in User.get_all(): count = User.get_created_entities_count(user.id) email = user.email if is_authorized( 'manager') or user.settings['show_email'] else '' tables['user'].rows.append([ link(user), user.real_name, user.group, email, _('yes') if user.settings['newsletter'] else '', format_date(user.created), format_date(user.login_last_success), format_number(count) if count else '' ]) for item, languages in Content.get_content().items(): content = [uc_first(_(item))] for language in app.config['LANGUAGES'].keys(): content.append(sanitize(languages[language], 'text')) content.append(link(_('edit'), url_for('admin_content', item=item))) tables['content'].rows.append(content) form = None if is_authorized('admin'): form = TestMailForm() if form.validate_on_submit( ) and session['settings']['mail']: # pragma: no cover subject = _('Test mail from %(site_name)s', site_name=session['settings']['site_name']) body = _('This test mail was sent by %(username)s', username=current_user.username) body += ' ' + _('at') + ' ' + request.headers['Host'] if send_mail(subject, body, form.receiver.data): flash( _('A test mail was sent to %(email)s.', email=form.receiver.data), 'info') else: form.receiver.data = current_user.email return render_template('admin/index.html', form=form, tables=tables, settings=session['settings'], writeable_dirs=dirs, disk_space_info=get_disk_space_info(), imports=Import.get_all_projects(), title=_('admin'), crumbs=[_('admin')], info={ 'file': get_form_settings(FilesForm()), 'general': get_form_settings(GeneralForm()), 'mail': get_form_settings(MailForm()), 'map': get_form_settings(MapForm()), 'api': get_form_settings(ApiForm()), 'modules': get_form_settings(ModulesForm()) })
def ajax_bookmark() -> str: return jsonify(uc_first(User.toggle_bookmark(request.form['entity_id'])))