mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
first implementation of follow mechanics
This commit is contained in:
parent
97bba2498b
commit
e2cdcd3f7c
@ -1,6 +1,7 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask_wtf.file import FileField, FileRequired
|
from flask_wtf.file import FileField, FileRequired
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
|
BooleanField,
|
||||||
StringField,
|
StringField,
|
||||||
SubmitField,
|
SubmitField,
|
||||||
TextAreaField,
|
TextAreaField,
|
||||||
@ -77,6 +78,9 @@ class UpdateCorpusFileForm(CorpusFileBaseForm):
|
|||||||
kwargs['prefix'] = 'update-corpus-file-form'
|
kwargs['prefix'] = 'update-corpus-file-form'
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class ChangeCorpusSettingsForm(FlaskForm):
|
||||||
|
is_public = BooleanField('Public Corpus')
|
||||||
|
submit = SubmitField()
|
||||||
|
|
||||||
class ImportCorpusForm(FlaskForm):
|
class ImportCorpusForm(FlaskForm):
|
||||||
pass
|
pass
|
||||||
|
@ -5,15 +5,17 @@ from flask import (
|
|||||||
Markup,
|
Markup,
|
||||||
redirect,
|
redirect,
|
||||||
render_template,
|
render_template,
|
||||||
send_from_directory
|
request,
|
||||||
|
send_from_directory,
|
||||||
|
url_for
|
||||||
)
|
)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import os
|
import os
|
||||||
from app import db
|
from app import db, hashids
|
||||||
from app.models import Corpus, CorpusFile, CorpusStatus
|
from app.models import Corpus, CorpusFile, CorpusStatus, CorpusFollowerAssociation, User
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm
|
from .forms import ChangeCorpusSettingsForm, CreateCorpusFileForm, CreateCorpusForm, UpdateCorpusFileForm
|
||||||
|
|
||||||
|
|
||||||
def user_can_read_corpus(user, corpus):
|
def user_can_read_corpus(user, corpus):
|
||||||
@ -64,19 +66,30 @@ def create_corpus():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>')
|
@bp.route('/<hashid:corpus_id>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def corpus(corpus_id):
|
def corpus(corpus_id):
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
if not user_can_read_corpus(current_user, corpus):
|
if not user_can_read_corpus(current_user, corpus):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
corpus_settings_form = ChangeCorpusSettingsForm(
|
||||||
|
data=corpus.to_json_serializeable(),
|
||||||
|
prefix='corpus-settings-form'
|
||||||
|
)
|
||||||
|
if corpus_settings_form.validate_on_submit():
|
||||||
|
corpus.is_public = corpus_settings_form.is_public.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.corpus', corpus_id=corpus.id))
|
||||||
return render_template(
|
return render_template(
|
||||||
'corpora/corpus.html.j2',
|
'corpora/corpus.html.j2',
|
||||||
|
corpus_settings_form=corpus_settings_form,
|
||||||
corpus=corpus,
|
corpus=corpus,
|
||||||
title='Corpus'
|
title='Corpus'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# @bp.route('/<hashid:corpus_id>/update')
|
# @bp.route('/<hashid:corpus_id>/update')
|
||||||
# @login_required
|
# @login_required
|
||||||
# def update_corpus(corpus_id):
|
# def update_corpus(corpus_id):
|
||||||
@ -263,3 +276,56 @@ def import_corpus():
|
|||||||
@login_required
|
@login_required
|
||||||
def export_corpus(corpus_id):
|
def export_corpus(corpus_id):
|
||||||
abort(503)
|
abort(503)
|
||||||
|
|
||||||
|
@bp.route('/<hashid:corpus_id>/follow')
|
||||||
|
@login_required
|
||||||
|
# TODO: Wenn Query Paramter genutzt wird, prüfen, ob user_id ungleich current_user.id ist und dann gucken, ob es ein Admin ist.
|
||||||
|
# Sonst 403.
|
||||||
|
def follow_corpus(corpus_id):
|
||||||
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
|
user_hashid = request.args.get('user_id')
|
||||||
|
if user_hashid is None:
|
||||||
|
user = current_user
|
||||||
|
else:
|
||||||
|
if not current_user.is_administrator():
|
||||||
|
abort(403)
|
||||||
|
else:
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
if not user.is_following_corpus(corpus):
|
||||||
|
user.follow_corpus(corpus)
|
||||||
|
db.session.commit()
|
||||||
|
return {}, 202
|
||||||
|
|
||||||
|
@bp.route('/<hashid:corpus_id>/unfollow')
|
||||||
|
@login_required
|
||||||
|
def unfollow_corpus(corpus_id):
|
||||||
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
|
user_hashid = request.args.get('user_id')
|
||||||
|
if user_hashid is None:
|
||||||
|
user = current_user
|
||||||
|
else:
|
||||||
|
if not current_user.is_administrator():
|
||||||
|
abort(403)
|
||||||
|
else:
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
if user.is_following_corpus(corpus):
|
||||||
|
user.unfollow_corpus(corpus)
|
||||||
|
db.session.commit()
|
||||||
|
return {}, 202
|
||||||
|
|
||||||
|
@bp.route('/add_permission/<hashid:corpus_id>/<hashid:user_id>/<int:permission>')
|
||||||
|
def add_permission(corpus_id, user_id, permission):
|
||||||
|
a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404()
|
||||||
|
a.add_permission(permission)
|
||||||
|
db.session.commit()
|
||||||
|
return 'ok'
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/remove_permission/<hashid:corpus_id>/<hashid:user_id>/<int:permission>')
|
||||||
|
def remove_permission(corpus_id, user_id, permission):
|
||||||
|
a = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=user_id).first_or_404()
|
||||||
|
a.remove_permission(permission)
|
||||||
|
db.session.commit()
|
||||||
|
return 'ok'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_login import current_user, login_required, login_user
|
from flask_login import current_user, login_required, login_user
|
||||||
from app.auth.forms import LoginForm
|
from app.auth.forms import LoginForm
|
||||||
from app.models import User
|
from app.models import Corpus, User
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@ -31,7 +31,11 @@ def dashboard():
|
|||||||
u.to_json_serializeable(filter_by_privacy_settings=True) for u
|
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()
|
in User.query.filter(User.is_public == True, User.id != current_user.id).all()
|
||||||
]
|
]
|
||||||
return render_template('main/dashboard.html.j2', title='Dashboard', users=users)
|
corpora = [
|
||||||
|
c.to_json_serializeable() for c
|
||||||
|
in Corpus.query.filter(Corpus.is_public == True).all()
|
||||||
|
]
|
||||||
|
return render_template('main/dashboard.html.j2', title='Dashboard', users=users, corpora=corpora)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/dashboard2')
|
@bp.route('/dashboard2')
|
||||||
|
@ -68,6 +68,11 @@ class ProfilePrivacySettings(IntEnum):
|
|||||||
SHOW_EMAIL = 1
|
SHOW_EMAIL = 1
|
||||||
SHOW_LAST_SEEN = 2
|
SHOW_LAST_SEEN = 2
|
||||||
SHOW_MEMBER_SINCE = 4
|
SHOW_MEMBER_SINCE = 4
|
||||||
|
|
||||||
|
class CorpusFollowPermission(IntEnum):
|
||||||
|
VIEW = 1
|
||||||
|
CONTRIBUTE = 2
|
||||||
|
ADMINISTRATE = 4
|
||||||
# endregion enums
|
# endregion enums
|
||||||
|
|
||||||
|
|
||||||
@ -298,6 +303,16 @@ class CorpusFollowerAssociation(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<CorpusFollowerAssociation {self.following_user.__repr__()} ~ {self.followed_corpus.__repr__()}>'
|
return f'<CorpusFollowerAssociation {self.following_user.__repr__()} ~ {self.followed_corpus.__repr__()}>'
|
||||||
|
|
||||||
|
def has_permission(self, permission):
|
||||||
|
return self.permissions & permission == permission
|
||||||
|
|
||||||
|
def add_permission(self, permission):
|
||||||
|
if not self.has_permission(permission):
|
||||||
|
self.permissions += permission
|
||||||
|
|
||||||
|
def remove_permission(self, permission):
|
||||||
|
if self.has_permission(permission):
|
||||||
|
self.permissions -= permission
|
||||||
|
|
||||||
class User(HashidMixin, UserMixin, db.Model):
|
class User(HashidMixin, UserMixin, db.Model):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
@ -576,6 +591,18 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
self.profile_privacy_settings = 0
|
self.profile_privacy_settings = 0
|
||||||
#endregion Profile Privacy settings
|
#endregion Profile Privacy settings
|
||||||
|
|
||||||
|
def follow_corpus(self, corpus):
|
||||||
|
if not self.is_following_corpus(corpus):
|
||||||
|
self.followed_corpora.append(corpus)
|
||||||
|
|
||||||
|
def unfollow_corpus(self, corpus):
|
||||||
|
if self.is_following_corpus(corpus):
|
||||||
|
self.followed_corpora.remove(corpus)
|
||||||
|
|
||||||
|
def is_following_corpus(self, corpus):
|
||||||
|
return corpus in self.followed_corpora
|
||||||
|
|
||||||
|
|
||||||
def to_json_serializeable(self, backrefs=False, relationships=False, filter_by_privacy_settings=False):
|
def to_json_serializeable(self, backrefs=False, relationships=False, filter_by_privacy_settings=False):
|
||||||
json_serializeable = {
|
json_serializeable = {
|
||||||
'id': self.hashid,
|
'id': self.hashid,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class UserList extends ResourceList {
|
class UserList extends ResourceList {
|
||||||
static autoInit() {
|
static autoInit() {
|
||||||
for (let publicUserListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) {
|
for (let userListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) {
|
||||||
new UserList(publicUserListElement);
|
new UserList(userListElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,14 +41,14 @@ class UserList extends ResourceList {
|
|||||||
|
|
||||||
initListContainerElement() {
|
initListContainerElement() {
|
||||||
if (!this.listContainerElement.hasAttribute('id')) {
|
if (!this.listContainerElement.hasAttribute('id')) {
|
||||||
this.listContainerElement.id = Utils.generateElementId('public-user-list-');
|
this.listContainerElement.id = Utils.generateElementId('user-list-');
|
||||||
}
|
}
|
||||||
let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
|
let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
|
||||||
this.listContainerElement.innerHTML = `
|
this.listContainerElement.innerHTML = `
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<i class="material-icons prefix">search</i>
|
<i class="material-icons prefix">search</i>
|
||||||
<input id="${listSearchElementId}" class="search" type="text"></input>
|
<input id="${listSearchElementId}" class="search" type="text"></input>
|
||||||
<label for="${listSearchElementId}">Search public user</label>
|
<label for="${listSearchElementId}">Search user</label>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -77,7 +77,7 @@ class UserList extends ResourceList {
|
|||||||
'full-name': user.full_name ? user.full_name : '',
|
'full-name': user.full_name ? user.full_name : '',
|
||||||
'location': user.location ? user.location : '',
|
'location': user.location ? user.location : '',
|
||||||
'organization': user.organization ? user.organization : '',
|
'organization': user.organization ? user.organization : '',
|
||||||
'corpora-online': '0'
|
'corpora-online': '-'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
<li><a href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a></li>
|
<li><a href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a></li>
|
||||||
<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='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('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="nopaque-icons">J</i>My Jobs</a></li>
|
||||||
|
<li><a href="{{ url_for('main.dashboard', _anchor='social') }}" style="padding-left: 47px;"><i class="material-icons">groups</i>Social</a></li>
|
||||||
<li><a href="{{ url_for('contributions.contributions') }}"><i class="material-icons">new_label</i>Contribute</a></li>
|
<li><a href="{{ url_for('contributions.contributions') }}"><i class="material-icons">new_label</i>Contribute</a></li>
|
||||||
<li><div class="divider"></div></li>
|
<li><div class="divider"></div></li>
|
||||||
<li><a class="subheader">Processes & Services</a></li>
|
<li><a class="subheader">Processes & Services</a></li>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{% extends "base.html.j2" %}
|
{% extends "base.html.j2" %}
|
||||||
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
@ -10,6 +11,9 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s8 m9 l10">
|
<div class="col s8 m9 l10">
|
||||||
<h1 id="title"><span class="corpus-title"></span></h1>
|
<h1 id="title"><span class="corpus-title"></span></h1>
|
||||||
|
{% if not corpus.user == current_user %}
|
||||||
|
<a class="btn waves-effect waves-light" id="follow-corpus-request"><i class="material-icons left">add</i>Follow Corpus</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col s4 m3 l2 right-align">
|
<div class="col s4 m3 l2 right-align">
|
||||||
<p> </p>
|
<p> </p>
|
||||||
@ -76,6 +80,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if current_user.can(Permission.ADMINISTRATE) or current_user.hashid == corpus.user.hashid %}
|
||||||
|
<div class="col s12">
|
||||||
|
<form method="POST">
|
||||||
|
{{ corpus_settings_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title" id="files">Corpus settings</span>
|
||||||
|
<br>
|
||||||
|
<p></p>
|
||||||
|
{{ wtf.render_field(corpus_settings_form.is_public) }}
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
{{ wtf.render_field(corpus_settings_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
@ -84,5 +106,25 @@
|
|||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script>
|
<script>
|
||||||
let corpusDisplay = new CorpusDisplay(document.querySelector('#corpus-display'));
|
let corpusDisplay = new CorpusDisplay(document.querySelector('#corpus-display'));
|
||||||
|
let followCorpusRequest = document.querySelector('#follow-corpus-request');
|
||||||
|
|
||||||
|
followCorpusRequest.addEventListener('click', function() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(`/corpora/${corpusId}/build`, {method: 'POST', headers: {Accept: 'application/json'}})
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
|
||||||
|
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
|
||||||
|
if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
|
||||||
|
app.flash(`Corpus "${corpus?.title}" marked for building`, 'corpus');
|
||||||
|
resolve(response);
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
app.flash('Something went wrong', 'error');
|
||||||
|
reject(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock scripts %}
|
{% endblock scripts %}
|
||||||
|
@ -42,6 +42,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col s12" id="social">
|
||||||
|
<h3>Social</h3>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Other users</span>
|
||||||
|
<p>Find other users and follow them to see their corpora.</p>
|
||||||
|
<div class="user-list no-autoinit"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Public corpora</span>
|
||||||
|
<p>Find public corpora</p>
|
||||||
|
<div class="public-corpus-list no-autoinit"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
@ -96,3 +113,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock modals %}
|
{% endblock modals %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script>
|
||||||
|
let userList = new UserList(document.querySelector('.user-list'));
|
||||||
|
userList.add({{ users|tojson }});
|
||||||
|
let publicCorpusList = new CorpusList(document.querySelector('.public-corpus-list'));
|
||||||
|
publicCorpusList.add({{ corpora|tojson }});
|
||||||
|
</script>
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
||||||
|
@ -89,6 +89,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<h4>Groups</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<h4>Public corpora</h4>
|
||||||
|
<div class="public-corpora-list" data-user-id="{{ user.hashid }}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user