Merge branch 'public-corpus' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into public-corpus

This commit is contained in:
Patrick Jentsch 2023-03-01 16:33:03 +01:00
commit 73cb566db2
10 changed files with 150 additions and 64 deletions

View File

@ -3,27 +3,24 @@ from flask_login import current_user
from functools import wraps
from app.models import Corpus, CorpusFollowerAssociation
def corpus_follower_permission_required(permissions):
def corpus_follower_permission_required(*permissions):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
corpus_id = kwargs.get('corpus_id')
corpus = Corpus.query.get_or_404(corpus_id)
if current_user == corpus.user or current_user.is_administrator():
print('user or admin')
return f(*args, **kwargs)
if not current_user.is_following_corpus(corpus):
print('not following corpus')
abort(403)
corpus_follower_association = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first_or_404()
for permission in permissions:
if not corpus_follower_association.role.has_permission(permission):
abort(403)
if not all([corpus_follower_association.role.has_permission(p) for p in permissions]):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
def owner_or_admin_required():
def corpus_owner_or_admin_required():
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):

View File

@ -16,7 +16,7 @@ from flask_login import current_user, login_required
from threading import Thread
import jwt
import os
from .decorators import corpus_follower_permission_required, owner_or_admin_required
from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required
from app import db, hashids
from app.models import (
Corpus,
@ -34,12 +34,6 @@ from .forms import (
UpdateCorpusFileForm
)
@bp.route('/<hashid:corpus_id>/test')
@login_required
@corpus_follower_permission_required(['VIEW', 'ADD_CORPUS_FILE'])
def test(corpus_id):
return 'ok'
@bp.route('/fake-add')
@login_required
def fake_add():
@ -52,7 +46,7 @@ def fake_add():
@bp.route('/<hashid:corpus_id>/is_public', methods=['POST'])
@login_required
@owner_or_admin_required()
@corpus_owner_or_admin_required()
def update_corpus_is_public(corpus_id):
is_public = request.json
if not isinstance(is_public, bool):
@ -67,7 +61,7 @@ def update_corpus_is_public(corpus_id):
@bp.route('/<hashid:corpus_id>/followers/add', methods=['POST'])
@login_required
@owner_or_admin_required()
@corpus_owner_or_admin_required()
def add_corpus_followers(corpus_id):
usernames = request.json
if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)):
@ -124,7 +118,7 @@ def current_user_unfollow_corpus(corpus_id):
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['POST'])
@corpus_follower_permission_required(['REMOVE_FOLLOWER', 'UPDATE_FOLLOWER'])
@corpus_follower_permission_required('REMOVE_FOLLOWER', 'UPDATE_FOLLOWER')
def add_permission(corpus_id, follower_id):
corpus_follower_association = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
if not (corpus_follower_association.corpus.user == current_user or current_user.is_administrator()):
@ -218,6 +212,7 @@ def generate_corpus_share_link(corpus_id):
@bp.route('/<hashid:corpus_id>', methods=['DELETE'])
@login_required
@corpus_owner_or_admin_required()
def delete_corpus(corpus_id):
def _delete_corpus(app, corpus_id):
with app.app_context():
@ -226,8 +221,6 @@ def delete_corpus(corpus_id):
db.session.commit()
corpus = Corpus.query.get_or_404(corpus_id)
if not (corpus.user == current_user or current_user.is_administrator()):
abort(403)
thread = Thread(
target=_delete_corpus,
args=(current_app._get_current_object(), corpus_id)
@ -238,12 +231,9 @@ def delete_corpus(corpus_id):
@bp.route('/<hashid:corpus_id>/analyse')
@login_required
@corpus_follower_permission_required('VIEW')
def analyse_corpus(corpus_id):
corpus = Corpus.query.get_or_404(corpus_id)
if not (corpus.user == current_user
or current_user.is_administrator()
or current_user.is_following_corpus(corpus)):
abort(403)
return render_template(
'corpora/analyse_corpus.html.j2',
corpus=corpus,
@ -253,6 +243,7 @@ def analyse_corpus(corpus_id):
@bp.route('/<hashid:corpus_id>/build', methods=['POST'])
@login_required
@corpus_owner_or_admin_required()
def build_corpus(corpus_id):
def _build_corpus(app, corpus_id):
with app.app_context():
@ -277,6 +268,7 @@ def build_corpus(corpus_id):
@bp.route('/<hashid:corpus_id>/files/create', methods=['GET', 'POST'])
@login_required
@corpus_follower_permission_required('ADD_CORPUS_FILE')
def create_corpus_file(corpus_id):
corpus = Corpus.query.get_or_404(corpus_id)
if not (corpus.user == current_user or current_user.is_administrator()):
@ -324,10 +316,9 @@ def create_corpus_file(corpus_id):
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST'])
@login_required
@corpus_follower_permission_required('ADD_CORPUS_FILE', 'UPDATE_CORPUS_FILE', 'REMOVE_CORPUS_FILE')
def corpus_file(corpus_id, corpus_file_id):
corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404()
if not (corpus_file.corpus.user == current_user or current_user.is_administrator()):
abort(403)
form = UpdateCorpusFileForm(data=corpus_file.to_json_serializeable())
if form.validate_on_submit():
form.populate_obj(corpus_file)
@ -348,6 +339,7 @@ def corpus_file(corpus_id, corpus_file_id):
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['DELETE'])
@login_required
@corpus_follower_permission_required('REMOVE_CORPUS_FILE')
def delete_corpus_file(corpus_id, corpus_file_id):
def _delete_corpus_file(app, corpus_file_id):
with app.app_context():
@ -368,6 +360,7 @@ def delete_corpus_file(corpus_id, corpus_file_id):
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/download')
@login_required
@corpus_follower_permission_required('VIEW')
def download_corpus_file(corpus_id, corpus_file_id):
corpus_file = CorpusFile.query.filter_by(corpus_id = corpus_id, id=corpus_file_id).first_or_404()
if not (corpus_file.corpus.user == current_user or current_user.is_administrator()):

View File

@ -27,20 +27,7 @@ def faq():
@bp.route('/dashboard')
@login_required
def dashboard():
# users = [
# u.to_json_serializeable(filter_by_privacy_settings=True) for u
# in User.query.filter(User.is_public == True, User.id != current_user.id).all()
# ]
# corpora = [
# c.to_json_serializeable() for c
# in Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all()
# ]
return render_template(
'main/dashboard.html.j2',
title='Dashboard',
# users=users,
# corpora=corpora
)
return render_template('main/dashboard.html.j2', title='Dashboard')
@bp.route('/dashboard2')
@ -67,3 +54,20 @@ def privacy_policy():
@bp.route('/terms_of_use')
def terms_of_use():
return render_template('main/terms_of_use.html.j2', title='Terms of Use')
@bp.route('/social-area')
def social_area():
users = [
u.to_json_serializeable(relationships=True, filter_by_privacy_settings=True,) for u
in User.query.filter(User.is_public == True, User.id != current_user.id).all()
]
corpora = [
c.to_json_serializeable() for c
in Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all()
]
return render_template(
'main/social_area.html.j2',
users=users,
corpora=corpora,
title='Social Area'
)

View File

@ -0,0 +1,14 @@
class FollowedCorpusList extends CorpusList {
get item() {
return `
<tr class="list-item clickable hoverable">
<td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td>
<td><b class="title"></b><br><i class="description"></i></td>
<td><span class="status badge new corpus-status-color corpus-status-text" data-badge-caption=""></span></td>
<td class="right-align">
<a class="list-action-trigger btn-floating service-color darken waves-effect waves-light" data-list-action="view" data-service="corpus-analysis"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim();
}
}

View File

@ -13,14 +13,14 @@ class UserList extends ResourceList {
get item() {
return `
<tr class="list-item clickable hoverable">
<td><img alt="user-image" class="circle responsive-img avatar" style="width:50%"></td>
<td><img alt="user-image" class="circle responsive-img avatar" style="width:25%"></td>
<td><b><span class="username"></span><b></td>
<td><span class="full-name"></span></td>
<td><span class="location"></span></td>
<td><span class="organization"></span></td>
<td><span class="corpora-online"></span></td>
<td class="right-align">
<a class="list-action-trigger btn-floating waves-effect waves-light" data-list-action="view"><i class="material-icons">send</i></a>
<a class="list-action-trigger btn-floating waves-effect waves-light" data-list-action="view" style="background-color:#D9A36D"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim();
@ -77,7 +77,7 @@ class UserList extends ResourceList {
'full-name': user.full_name ? user.full_name : '',
'location': user.location ? user.location : '',
'organization': user.organization ? user.organization : '',
'corpora-online': '-'
'corpora-online': Object.values(user.corpora).filter((corpus) => corpus.is_public).length
};
};

View File

@ -22,6 +22,7 @@
'js/ResourceLists/CorpusFileList.js',
'js/ResourceLists/PublicCorpusFileList.js',
'js/ResourceLists/CorpusList.js',
'js/ResourceLists/FollowedCorpusList.js',
'js/ResourceLists/PublicCorpusList.js',
'js/ResourceLists/JobList.js',
'js/ResourceLists/JobInputList.js',

View File

@ -6,11 +6,10 @@
<div class="col s4">
<a href="{{ url_for('users.user', user_id=current_user.id) }}">
{% if current_user.avatar %}
<img src="{{ url_for('users.profile_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 20px; margin-left:-15px;">
<img src="{{ url_for('users.profile_avatar', user_id=current_user.id) }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 13px; margin-left:-15px;">
{% else %}
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 20px; margin-left:-15px;">
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img" style="height:80%; margin-top: 13px; margin-left:-15px;">
{% endif %}
{# <i class="material-icons" style="color:white; font-size:3em; margin-top: 25px; margin-left:-15px;">account_circle</i></div> #}
</a>
</div>
<div class="col s8">
@ -27,6 +26,7 @@
<li><a href="{{ url_for('main.dashboard', _anchor='corpora') }}" style="padding-left: 47px;"><i class="nopaque-icons">I</i>My Corpora</a></li>
<li><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="nopaque-icons">J</i>My Jobs</a></li>
<li><a href="{{ url_for('contributions.contributions') }}"><i class="material-icons">new_label</i>Contribute</a></li>
<li><a href="{{ url_for('main.social_area') }}"><i class="material-icons">group</i>Social Area</a></li>
<li><div class="divider"></div></li>
<li><a class="subheader">Processes & Services</a></li>
<li class="service-color service-color-border border-darken" data-service="file-setup-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.file_setup_pipeline') }}"><i class="nopaque-icons service-icons" data-service="file-setup-pipeline"></i>File setup</a></li>

View File

@ -0,0 +1,70 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% block main_attribs %} style="background-color:#d8c9ba86" {% endblock main_attribs %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">{{ title }}</h1>
</div>
<div class="col s12 m3 push-m9">
<div class="center-align">
<p class="hide-on-small-only">&nbsp;</p>
<p class="hide-on-small-only">&nbsp;</p>
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light" style="background-color:#D9A36D">
<i class="left material-icons">group</i>
</a>
</div>
</div>
<div class="col s12 m9 pull-m3">
<div class="card" style="border-top: 10px solid #D9A36D;">
<div class="card-content">
<div class="row">
<div class="col s12">
<div class="card-panel z-depth-0">
<span class="card-title"><i class="left material-icons">layers</i>Your social area</span>
<p>Here you can network with your team and other users. You can find corpora that are public and request them or just see what other users are working on.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col s12">
<h3>Other Users</h3>
<p>Find other users and see what corpora they have made public.</p>
<div class="card">
<div class="card-content">
<div class="user-list no-autoinit"></div>
</div>
</div>
</div>
<div class="col s12">
<h3>Public Corpora</h3>
<p>Find public corpora.</p>
<div class="card">
<div class="card-content">
<span class="card-title">Public Corpora</span>
<div class="public-corpus-list no-autoinit"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock page_content %}
{% block scripts %}
{{ super() }}
<script>
let userList = new UserList(document.querySelector('.user-list'));
userList.add({{ users|tojson }});
let publicCorpusList = new PublicCorpusList(document.querySelector('.public-corpus-list'));
publicCorpusList.add({{ corpora|tojson }});
</script>
{% endblock scripts %}

View File

@ -26,7 +26,7 @@
</div>
<div class="col 12">
{% if user.show_last_seen %}
<div class="chip">Last seen: {{ user.last_seen }}</div>
<div class="chip">Last seen: {{ last_seen }}</div>
{% endif %}
{% if user.location %}
<p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ user.location }}</i></p>
@ -76,7 +76,7 @@
</table>
<br>
{% if user.show_member_since %}
<p><i>Member since: {{ user.member_since }}</i></p>
<p><i>Member since: {{ member_since }}</i></p>
{% endif %}
<p></p>
<br>
@ -93,7 +93,8 @@
<div class="col s6">
<div class="card">
<div class="card-content">
<h4>Groups</h4>
<h4>Followed corpora</h4>
<div class="followed-corpus-list no-autoinit"></div>
</div>
</div>
</div>
@ -101,7 +102,7 @@
<div class="card">
<div class="card-content">
<h4>Public corpora</h4>
<div class="public-corpora-list" data-user-id="{{ user.hashid }}"></div>
<div class="public-corpus-list no-autoinit"></div>
</div>
</div>
</div>
@ -127,6 +128,11 @@ if ("{{ user.id }}" == "{{ current_user.hashid }}") {
} else {
publicInformationBadge.remove();
}
let followedCorpusList = new FollowedCorpusList(document.querySelector('.followed-corpus-list'));
followedCorpusList.add({{ followed_corpora|tojson }});
let publicCorpusList = new PublicCorpusList(document.querySelector('.public-corpus-list'));
publicCorpusList.add({{ own_public_corpora|tojson }});
</script>
{% endblock scripts %}

View File

@ -1,3 +1,4 @@
from datetime import datetime
from flask import (
abort,
current_app,
@ -12,7 +13,7 @@ from flask_login import current_user, login_required
from threading import Thread
import os
from app import db
from app.models import Avatar, ProfilePrivacySettings, User
from app.models import Avatar, Corpus, ProfilePrivacySettings, User
from . import bp
from .forms import (
EditPrivacySettingsForm,
@ -29,10 +30,23 @@ def before_request():
@login_required
def user(user_id):
user = User.query.get_or_404(user_id)
last_seen = user.last_seen.strftime('%Y-%m-%d %H:%M')
member_since = user.member_since.strftime('%Y-%m-%d')
followed_corpora = [
c.to_json_serializeable() for c in user.followed_corpora
]
own_public_corpora = [
c.to_json_serializeable() for c
in Corpus.query.filter_by(is_public = True, user = user).all()
]
if not user.is_public and user != current_user:
abort(403)
return render_template(
'users/profile.html.j2',
'users/profile.html.j2',
followed_corpora=followed_corpora,
last_seen=last_seen,
member_since=member_since,
own_public_corpora=own_public_corpora,
user=user.to_json_serializeable(),
user_id=user_id
)
@ -56,18 +70,6 @@ def delete_user(user_id):
thread.start()
return {}, 202
@bp.route('/<hashid:user_id>')
def profile(user_id):
user = User.query.get_or_404(user_id)
if not user.is_public and user != current_user:
abort(403)
return render_template(
'users/profile.html.j2',
user=user.to_json_serializeable(),
user_id=user_id
)
@bp.route('/<hashid:user_id>/avatar')
def profile_avatar(user_id):
user = User.query.get_or_404(user_id)
@ -91,7 +93,6 @@ def delete_profile_avatar(user_id):
avatar = Avatar.query.get(avatar_id)
avatar.delete()
db.session.commit()
user = User.query.get_or_404(user_id)
if user.avatar is None:
abort(404)