registration, charts, contracts history

master
msinkec 3 years ago
parent 540f54dd06
commit 7da73f7d6a

@ -13,6 +13,6 @@ WORKDIR /usr/src/portal-webapp
RUN apt-get update && apt-get -y install wkhtmltopdf && \ RUN apt-get update && apt-get -y install wkhtmltopdf && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN pip3 install --no-cache-dir pdfkit flask==1.1.4 flask-dropzone flask-log-request-id flask-login Flask-SQLAlchemy alembic flask-migrate==2.7.0 Flask-script psycopg2 gunicorn pdfkit Werkzeug==1.0.1 RUN pip3 install --no-cache-dir pdfkit flask==1.1.4 flask-dropzone flask-log-request-id flask-login Flask-SQLAlchemy alembic flask-migrate==2.7.0 Flask-script psycopg2 gunicorn pdfkit Werkzeug==1.0.1, PyJWT
ENTRYPOINT ["./entrypoint.sh"] ENTRYPOINT ["./entrypoint.sh"]

320
app.py

@ -5,7 +5,7 @@ import configparser
from pathlib import Path from pathlib import Path
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from flask import Flask, render_template, request, redirect, flash, safe_join, send_file from flask import Flask, render_template, request, redirect, flash, safe_join, send_file, jsonify, url_for
from flask_dropzone import Dropzone from flask_dropzone import Dropzone
from flask_migrate import Migrate, MigrateCommand from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager from flask_script import Manager
@ -15,7 +15,6 @@ from portal.model import db, RegisteredUser
import portal.base import portal.base
import portal.solar import portal.solar
import portal.regular import portal.regular
import portal.predavanja
# TODO: Implement user registration. # TODO: Implement user registration.
@ -44,12 +43,9 @@ MAX_FILES_PER_UPLOAD = int(config['MAX_FILES_PER_UPLOAD'])
CONTRACT_CLIENT_CONTACT = config['CONTRACT_CLIENT_CONTACT'] CONTRACT_CLIENT_CONTACT = config['CONTRACT_CLIENT_CONTACT']
MAIL_SUBJECT = config['MAIL_SUBJECT'] MAIL_SUBJECT = config['MAIL_SUBJECT']
MAIL_BODY = config['MAIL_BODY'] MAIL_BODY = config['MAIL_BODY']
MAIL_SUBJECT_PREDAVANJA = config['MAIL_SUBJECT_PREDAVANJA']
MAIL_BODY_PREDAVANJA = config['MAIL_BODY_PREDAVANJA']
SQL_CONN_STR = config['SQL_CONN_STR'] SQL_CONN_STR = config['SQL_CONN_STR']
DESC_PREVODI = config['DESC_PREVODI'] DESC_PREVODI = config['DESC_PREVODI']
DESC_GIGAFIDA = config['DESC_GIGAFIDA'] DESC_GIGAFIDA = config['DESC_GIGAFIDA']
DESC_PREDAVANJA = config['DESC_PREDAVANJA']
if 'UPLOADS_DIR' in config: if 'UPLOADS_DIR' in config:
UPLOADS_DIR = Path(config['UPLOADS_DIR']) UPLOADS_DIR = Path(config['UPLOADS_DIR'])
@ -83,10 +79,6 @@ if 'PORTALDS4DS1_MAIL_SUBJECT' in os.environ:
MAIL_SUBJECT = os.environ['PORTALDS4DS1_MAIL_SUBJECT'] MAIL_SUBJECT = os.environ['PORTALDS4DS1_MAIL_SUBJECT']
if 'PORTALDS4DS1_MAIL_BODY' in os.environ: if 'PORTALDS4DS1_MAIL_BODY' in os.environ:
MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY'] MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY']
if 'PORTALDS4DS1_MAIL_SUBJECT_PREDAVANJA' in os.environ:
MAIL_SUBJECT_PREDAVANJA = os.environ['PORTALDS4DS1_MAIL_SUBJECT_PREDAVANJA']
if 'PORTALDS4DS1_MAIL_BODY_PREDAVANJA' in os.environ:
MAIL_BODY_PREDAVANJA = os.environ['PORTALDS4DS1_MAIL_BODY_PREDAVANJA']
if 'PORTALDS4DS1_SQL_CONN_STR' in os.environ: if 'PORTALDS4DS1_SQL_CONN_STR' in os.environ:
SQL_CONN_STR = os.environ['PORTALDS4DS1_SQL_CONN_STR'] SQL_CONN_STR = os.environ['PORTALDS4DS1_SQL_CONN_STR']
if 'PORTALDS4DS1_DESC_PREVODI' in os.environ: if 'PORTALDS4DS1_DESC_PREVODI' in os.environ:
@ -94,7 +86,7 @@ if 'PORTALDS4DS1_DESC_PREVODI' in os.environ:
if 'PORTALDS4DS1_DESC_GIGAFIDA' in os.environ: if 'PORTALDS4DS1_DESC_GIGAFIDA' in os.environ:
DESC_GIGAFIDA = os.environ['PORTALDS4DS1_DESC_GIGAFIDA'] DESC_GIGAFIDA = os.environ['PORTALDS4DS1_DESC_GIGAFIDA']
ENABLED_CORPUSES = ['prevodi', 'gigafida', 'solar', 'predavanja'] ENABLED_CORPUSES = ['prevodi', 'gigafida', 'solar']
CORPUSES_LOGIN_REQUIRED = ['solar'] CORPUSES_LOGIN_REQUIRED = ['solar']
@ -147,25 +139,18 @@ upload_handler_solar = portal.solar.UploadHandlerSolar(
MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD
) )
upload_handler_predavanja = portal.predavanja.UploadHandlerPredavanja(
UPLOADS_DIR=UPLOADS_DIR,
MAIL_HOST=MAIL_HOST,
MAIL_LOGIN=MAIL_LOGIN,
MAIL_PASS=MAIL_PASS,
SMTP_PORT=SMTP_PORT,
IMAP_PORT=IMAP_PORT,
MAIL_SUBJECT=MAIL_SUBJECT_PREDAVANJA,
MAIL_BODY=MAIL_BODY_PREDAVANJA,
CONTRACT_CLIENT_CONTACT=CONTRACT_CLIENT_CONTACT,
MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD
)
# Use flask-login to manage user sessions where they are required. # Use flask-login to manage user sessions where they are required.
login_manager = LoginManager(app) login_manager = LoginManager(app)
login_manager.init_app(app) login_manager.init_app(app)
def redirect_url(default='/'):
return request.args.get('next') or \
request.referrer or \
url_for(default)
@app.route('/') @app.route('/')
def index(): def index():
return render_template('index.html') return render_template('index.html')
@ -180,8 +165,6 @@ def index_corpus(corpus_name):
description = DESC_PREVODI description = DESC_PREVODI
elif corpus_name == 'gigafida': elif corpus_name == 'gigafida':
description = DESC_GIGAFIDA description = DESC_GIGAFIDA
elif corpus_name == 'predavanja':
return render_template('basic-predavanja.html', description=DESC_PREDAVANJA, max_files=MAX_FILES_PER_UPLOAD)
elif corpus_name == 'solar': elif corpus_name == 'solar':
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect('/solar/oddaja') return redirect('/solar/oddaja')
@ -198,15 +181,17 @@ def load_user(user_id):
@app.route('/solar/login') @app.route('/solar/login')
def login_get(): def solar_login_get():
return render_template('login.html', corpus_name='solar', title='ŠOLAR') return render_template('solar-login.html')
@app.route('/<corpus_name>/login', methods=['POST']) @app.route('/solar/register')
def login_post(corpus_name): def solar_register_get():
if corpus_name not in ENABLED_CORPUSES or corpus_name not in CORPUSES_LOGIN_REQUIRED: return render_template('solar-register.html')
return '', 404
@app.route('/solar/login', methods=['POST'])
def solar_login_post():
email = request.form.get('email') email = request.form.get('email')
password = request.form.get('password') password = request.form.get('password')
remember = True if request.form.get('remember') else False remember = True if request.form.get('remember') else False
@ -215,23 +200,59 @@ def login_post(corpus_name):
if not user or not check_password_hash(user.pass_hash, password): if not user or not check_password_hash(user.pass_hash, password):
flash('Napačni podatki za prijavo. Poskusite ponovno.') flash('Napačni podatki za prijavo. Poskusite ponovno.')
return redirect('/{}/login'.format(corpus_name)) return redirect('/solar/login')
if not user.active: if not user.active:
flash('Vaš uporabniški račun še ni bil aktiviran.') flash('Vaš uporabniški račun še ni bil aktiviran.')
return redirect('/{}/login'.format(corpus_name)) return redirect('/solar/login')
# Check if user is authorized to log into this corpus. Admins are an exception.
if not portal.base.has_user_corpus_access(user.id, corpus_name):
flash('Nimate dostopa do tega korpusa.')
return redirect('/{}/login'.format(corpus_name))
#portal.base.add_user_session(user.id) #portal.base.add_user_session(user.id)
login_user(user, remember=remember) login_user(user, remember=remember)
if corpus_name == 'solar': return redirect('/solar/oddaja')
return redirect('/solar/oddaja')
return '', 404
@app.route('/solar/register', methods=['POST'])
def solar_register_post():
name = request.form.get('name')
email = request.form.get('email')
password = request.form.get('password')
user = RegisteredUser.query.filter_by(email=email).first()
if user:
flash('Uporabniški račun s tem emailom je že registriran.')
return redirect('/solar/register')
if not name:
flash('Prazno polje za ime.')
return redirect('/solar/register')
if len(name) > 100:
flash('Predolgo ime.')
return redirect('/solar/register')
if not email:
flash('Prazno polje za elektronsko pošto.')
return redirect('/solar/register')
if len(email) > 100:
flash('Predolgi email naslov')
return redirect('/solar/register')
elif not re.search(portal.base.REGEX_EMAIL, email):
flash('Email napačnega formata.')
return redirect('/solar/register')
if not password:
flash('Prazno polje za geslo.')
return redirect('/solar/register')
if len(password) > 100:
flash('Predolgo geslo.')
return redirect('/solar/register')
portal.base.register_new_user(name, email, password, active=False)
flash('Uspešna registracija.')
return redirect('/solar/login')
# TODO: Move solar stuff to seperate file using Flask blueprints. # TODO: Move solar stuff to seperate file using Flask blueprints.
@ -247,10 +268,15 @@ def logout():
@app.route('/solar/<path:text>') @app.route('/solar/<path:text>')
@login_required @login_required
def solar(text): def solar(text):
if not portal.base.has_user_corpus_access(current_user.id, 'solar'): is_admin = current_user.role == 'admin'
return '', 404 current_user_institution = portal.base.get_user_institution(current_user.id)
if current_user_institution:
current_user_institution_moderator = portal.base.is_institution_moderator(current_user.id, current_user_institution.id)
else:
current_user_institution_moderator = False
if text.startswith('oddaja/') or text == 'oddaja': if text.startswith('oddaja/') or text == 'oddaja':
return render_template('solar-oddaja.html') return render_template('solar-oddaja.html', is_admin=is_admin, is_institution_moderator=current_user_institution_moderator)
elif text.startswith('zgodovina/') or text == 'zgodovina': elif text.startswith('zgodovina/') or text == 'zgodovina':
upload_items = portal.solar.get_upload_history(current_user.id) upload_items = portal.solar.get_upload_history(current_user.id)
uploader_names = [] uploader_names = []
@ -263,7 +289,7 @@ def solar(text):
else: else:
institution_names.append(institution.name) institution_names.append(institution.name)
return render_template('solar-zgodovina.html', upload_history=upload_items, uploader_names=uploader_names, return render_template('solar-zgodovina.html', upload_history=upload_items, uploader_names=uploader_names,
institution_names=institution_names) institution_names=institution_names, is_admin=is_admin, is_institution_moderator=current_user_institution_moderator)
elif text.startswith('pogodbe/') or text == 'pogodbe': elif text.startswith('pogodbe/') or text == 'pogodbe':
# Check for ownload contract request. # Check for ownload contract request.
match = re.match('^pogodbe/([a-z0-9_]+\.pdf)$', text) match = re.match('^pogodbe/([a-z0-9_]+\.pdf)$', text)
@ -281,72 +307,154 @@ def solar(text):
return '', 404 return '', 404
user_obj = portal.base.get_user_obj(current_user.get_id()) user_obj = portal.base.get_user_obj(current_user.get_id())
institutions = portal.base.get_user_institutions(user_obj.id) institution = portal.base.get_user_institution(user_obj.id)
contracts_students = [] contracts_students = []
contract_school = [] contract_school = []
enable_upload_school_contract = False enable_upload_school_contract = False
show_upload_form = False show_upload_form = False
if len(institutions) > 0: collaborators = []
if institution:
collaborators = portal.base.get_all_active_institution_users(institution.id)
show_upload_form = True show_upload_form = True
institution = portal.base.get_user_institutions(user_obj.id)[0]
contracts_students = portal.solar.get_institution_student_contracts(institution.id)
contract_school = portal.solar.get_institution_contract(institution.id) contract_school = portal.solar.get_institution_contract(institution.id)
if portal.base.is_institution_moderator(user_obj.id, institution.id): if portal.base.is_institution_moderator(user_obj.id, institution.id):
contracts_students = portal.solar.get_institution_student_contracts(institution.id)
enable_upload_school_contract = True enable_upload_school_contract = True
else:
contracts_students = portal.solar.get_institution_student_contracts(institution.id, user_obj.id)
return render_template('solar-pogodbe.html', contracts_students=contracts_students, return render_template('solar-pogodbe.html', contracts_students=contracts_students,
contract_school=contract_school, contract_school=contract_school,
enable_upload_school_contract=enable_upload_school_contract, enable_upload_school_contract=enable_upload_school_contract,
show_upload_form=show_upload_form) show_upload_form=show_upload_form,
collaborators=collaborators,
is_admin=is_admin, is_institution_moderator=current_user_institution_moderator)
elif text.startswith('admin/') or text == 'admin': elif text.startswith('admin/') or text == 'admin':
solar_users = portal.base.get_all_active_users() users = portal.base.get_all_active_users_join_institutions()
inactive_users = portal.base.get_all_inactive_users()
solar_institutions = portal.solar.get_all_institutions() solar_institutions = portal.solar.get_all_institutions()
if current_user.role == 'admin': if is_admin:
return render_template('solar-admin.html', users=solar_users, institutions=solar_institutions) return render_template('solar-admin.html', users=users,
institutions=solar_institutions, inactive_users=inactive_users)
elif text.startswith('manage-institution/') or text == 'manage-institution':
institution = portal.base.get_user_institution(current_user.id)
if portal.base.is_institution_moderator(current_user.id, institution.id):
solar_users = portal.base.get_all_active_users()
institution_users = portal.base.get_all_active_institution_users(institution.id)
return render_template('solar-manage-institution.html', users=solar_users,
institution_users=institution_users)
return '', 404 return '', 404
@app.route('/solar/pogodbe', methods=['POST']) @app.route('/solar/pogodbe', methods=['POST'])
@login_required @login_required
def solar_upload_contract(): def solar_upload_contract():
if not portal.base.has_user_corpus_access(current_user.id, 'solar'):
return '', 404
return upload_handler_solar.handle_contract_upload(request, current_user.get_id()) return upload_handler_solar.handle_contract_upload(request, current_user.get_id())
@app.route('/<corpus_name>/adduser', methods=['POST']) @app.route('/solar/adduser', methods=['POST'])
@login_required @login_required
def solar_add_user(corpus_name): def solar_add_user():
if not portal.base.is_admin(current_user.id): if not portal.base.is_admin(current_user.id):
return '', 404 return '', 404
if not corpus_name in ENABLED_CORPUSES:
return '', 404
name = request.form['name'] name = request.form.get('name')
email = request.form['email'] email = request.form.get('email')
password = request.form['password'] password = request.form.get('password')
if not name: if not name:
return 'Prazno polje za ime.' flash('Prazno polje za ime.')
return redirect(redirect_url())
if len(name) > 100: if len(name) > 100:
return 'Predolgo ime.' flash('Predolgo ime.')
return redirect(redirect_url())
if not email: if not email:
return 'Prazno polje za elektronsko pošto.' flash('Prazno polje za elektronsko pošto.')
return redirect(redirect_url())
if len(email) > 100: if len(email) > 100:
return 'Predolgi email naslov' flash('Predolg email naslov.')
return redirect(redirect_url())
elif not re.search(portal.base.REGEX_EMAIL, email): elif not re.search(portal.base.REGEX_EMAIL, email):
return 'Email napačnega formata.' flash('Email napačnega formata.')
return redirect(redirect_url())
if not password: if not password:
return 'Prazno polje za geslo.' flash('Prazno polje za geslo.')
return redirect(redirect_url())
if len(password) > 100: if len(password) > 100:
return 'Predolgo geslo.' flash('Predolgo geslo.')
return redirect(redirect_url())
portal.base.register_new_user(name, email, password) portal.base.register_new_user(name, email, password)
return 'Uporabnik je bil dodan.' flash('Uporabnik je bil uspešno dodan.')
return redirect(redirect_url())
@app.route('/solar/activateuser', methods=['POST'])
@login_required
def solar_activate_user():
if not portal.base.is_admin(current_user.id):
return '', 404
user_id = request.form.get('id')
if not user_id:
flash('Prazno polje za ID uporabnika.')
return redirect(redirect_url())
rowcount = portal.base.activate_user(user_id)
if rowcount == 0:
return '', 404
flash('Uporabnik je bil aktiviran.')
return redirect(redirect_url())
@app.route('/solar/forgotpass')
def solar_forgotpass():
return render_template('solar-forgotpass.html')
@app.route('/solar/sendresetpass', methods=['POST'])
def solar_sendresetpass():
email = request.form.get('email')
portal.base.send_resetpass_mail(email, upload_handler_regular.config)
flash('Povezava za ponastavitev gesla je bila poslana na vpisan email naslov.')
return redirect(redirect_url())
@app.route('/solar/resetpass/<token>')
def solar_resetpass(token):
user = portal.base.verify_reset_token(token)
if not user:
return '', 404
return render_template('solar-resetpass.html', user=user, token=token)
@app.route('/solar/resetpass/<token>', methods=['POST'])
def solar_resetpass_post(token):
new_password = request.form.get('new_password')
user = portal.base.verify_reset_token(token)
if not user:
return '', 404
rowcount = portal.base.update_user_password(user.id, new_password)
if rowcount == 0:
return '', 404
return 'Ponastavitev gesla uspešna.'
@app.route('/solar/topuploads')
@login_required
def solar_topuploads_srednje():
return jsonify(portal.solar.get_top_uploading_institutions())
@app.route('/solar/deluser', methods=['POST']) @app.route('/solar/deluser', methods=['POST'])
@ -364,44 +472,76 @@ def add_institution(corpus_name):
if not corpus_name in ENABLED_CORPUSES: if not corpus_name in ENABLED_CORPUSES:
return '', 404 return '', 404
name = request.form['name'] name = request.form.get('name')
region = request.form['region'] region = request.form.get('region')
if not name: if not name:
return 'Prazno polje za ime.' flash('Prazno polje za ime.')
return redirect(redirect_url())
if len(name) > 100: if len(name) > 100:
return 'Predolgo ime.' flash('Predolgo ime.')
return redirect(redirect_url())
if not region: if not region:
return 'Prazno polje za regijo.' flash('Prazno polje za regijo.')
return redirect(redirect_url())
if len(region) > 100: if len(region) > 100:
return 'Predolgi niz za regijo.' flash('Predolgi niz za regijo.')
return redirect(redirect_url())
institution_id = portal.base.add_institution(name, region) institution_id = portal.base.add_institution(name, region)
portal.base.grant_institution_corpus_access(institution_id, corpus_name) portal.base.grant_institution_corpus_access(institution_id, corpus_name)
return 'Institucija je bila dodana.' flash('Institucija je bila dodana.')
return redirect(redirect_url())
@app.route('/<corpus_name>/addusertoinstitution', methods=['POST']) @app.route('/<corpus_name>/addusertoinstitution', methods=['POST'])
@login_required @login_required
def add_user_institution_mapping(corpus_name): def add_user_institution_mapping(corpus_name):
if not portal.base.is_admin(current_user.id):
return '', 404
if not corpus_name in ENABLED_CORPUSES: if not corpus_name in ENABLED_CORPUSES:
return '', 404 return '', 404
institution_id = request.form.get('institution_id')
if not institution_id:
institution = portal.base.get_user_institution(current_user.id)
if institution:
institution_id = institution.id
if not (portal.base.is_admin(current_user.id) or portal.base.is_institution_moderator(current_user.id, institution_id)):
return '', 404
user_id = request.form['user_id'] user_id = request.form['user_id']
institution_id = request.form['institution_id']
role = request.form['role'] role = request.form['role']
if role not in ['moderator', 'user']: if role not in ['moderator', 'user']:
return '', 404 return '', 404
# TODO: remove this restriction if portal.base.get_user_institution(user_id):
if len(portal.base.get_user_institutions(user_id)) > 0: flash('Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\
return 'Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\ 'zaenkrat ni implementirano.')
'zaenkrat ni implementirano.' return redirect(redirect_url())
portal.base.add_user_to_institution(user_id, institution_id, role) portal.base.add_user_to_institution(user_id, institution_id, role)
return 'Uporabnik je bil dodeljen instituciji.' flash('Uporabnik je bil dodeljen instituciji.')
return redirect(redirect_url())
@app.route('/<corpus_name>/deluserfrominstitution', methods=['POST'])
@login_required
def del_user_institution_mapping(corpus_name):
institution = portal.base.get_user_institution(current_user.id)
if not portal.base.is_admin(current_user.id) \
and not portal.base.is_institution_moderator(current_user.id, institution.id):
return '', 404
if not corpus_name in ENABLED_CORPUSES:
return '', 404
user_id = request.form['user_id']
if not portal.base.is_institution_member(user_id, institution.id):
flash('Uporabnik ni član vaše institucije.')
return redirect(redirect_url())
portal.base.del_user_from_institution(user_id, institution.id)
flash('Uporabnik je bil odstranjen iz institucije.')
return redirect(redirect_url())
@app.route('/<corpus_name>/delinstitution', methods=['POST']) @app.route('/<corpus_name>/delinstitution', methods=['POST'])
@login_required @login_required
@ -420,11 +560,9 @@ def handle_upload(corpus_name):
if corpus_name == 'solar': if corpus_name == 'solar':
if not current_user.is_authenticated: if not current_user.is_authenticated:
return '', 404 return '', 404
if not portal.base.has_user_corpus_access(current_user.id, corpus_name): #if not portal.base.has_user_corpus_access(current_user.id, corpus_name):
return '', 404 # return '', 404
return upload_handler_solar.handle_upload(request, current_user.get_id()) return upload_handler_solar.handle_upload(request, current_user.get_id())
elif corpus_name == 'predavanja':
return upload_handler_predavanja.handle_upload(request)
else: else:
return upload_handler_regular.handle_upload(request, corpus_name) return upload_handler_regular.handle_upload(request, corpus_name)

@ -12,17 +12,9 @@ UPLOADS_DIR=./uploads
CONTRACT_CLIENT_CONTACT=Testko Tester CONTRACT_CLIENT_CONTACT=Testko Tester
DESC_PREVODI=<h2 id="subtitle">Prevodi</h2><p>Strojno prevajanje je ena od uporabnih jezikovnih tehnologij, saj omogoča hitro sporazumevanje med ljudmi iz različnih kultur in jezikovnih okolij. Več o razvoju slovenskega strojnega prevajalnika lahko preberete na tej <a href="https://slovenscina.eu/strojno-prevajanje">povezavi</a>. Za kakovosten strojni prevajalnik so ključnega pomena prevodi, iz kateri se algoritmi umetne inteligence naučijo prevajati. S prispevanjem besedil v korpus prevodov boste pomembno prispevali k razvoju slovenskega strojnega prevajalnika med angleščino in slovenščino. Več informacij o prispevanju besedil najdete <a href="https://slovenscina.eu/zbiranje-besedil">tukaj</a>.</p> DESC_PREVODI=<h2 id="subtitle">Prevodi</h2><p>Strojno prevajanje je ena od uporabnih jezikovnih tehnologij, saj omogoča hitro sporazumevanje med ljudmi iz različnih kultur in jezikovnih okolij. Več o razvoju slovenskega strojnega prevajalnika lahko preberete na tej <a href="https://slovenscina.eu/strojno-prevajanje">povezavi</a>. Za kakovosten strojni prevajalnik so ključnega pomena prevodi, iz kateri se algoritmi umetne inteligence naučijo prevajati. S prispevanjem besedil v korpus prevodov boste pomembno prispevali k razvoju slovenskega strojnega prevajalnika med angleščino in slovenščino. Več informacij o prispevanju besedil najdete <a href="https://slovenscina.eu/zbiranje-besedil">tukaj</a>.</p>
DESC_GIGAFIDA=<h2 id="subtitle">Gigafida</h2><p><a href="https://viri.cjvt.si/gigafida/">Gigafida</a> je referenčni korpus pisne slovenščine. Besedila so izbrana in strojno obdelana z namenom, da bi korpus kot vzorec sodobne standardne slovenščine lahko služil za jezikoslovne in druge humanistične raziskave, izdelavo sodobnih slovarjev, slovnic, učnih gradiv in razvoj jezikovnih tehnologij za slovenščino. S prispevanjem besedil v korpus Gigafida pomembno prispevate k razvoju sodobnih jezikovnih tehnologij za slovenski jezik.</p> DESC_GIGAFIDA=<h2 id="subtitle">Gigafida</h2><p><a href="https://viri.cjvt.si/gigafida/">Gigafida</a> je referenčni korpus pisne slovenščine. Besedila so izbrana in strojno obdelana z namenom, da bi korpus kot vzorec sodobne standardne slovenščine lahko služil za jezikoslovne in druge humanistične raziskave, izdelavo sodobnih slovarjev, slovnic, učnih gradiv in razvoj jezikovnih tehnologij za slovenščino. S prispevanjem besedil v korpus Gigafida pomembno prispevate k razvoju sodobnih jezikovnih tehnologij za slovenski jezik.</p>
DESC_PREDAVANJA=<h2 id="subtitle">Predavanja</h2>
MAIL_SUBJECT=RSDO: pogodba za oddana besedila ({upload_id}) MAIL_SUBJECT=RSDO: pogodba za oddana besedila ({upload_id})
MAIL_BODY=Hvala, ker ste prispevali besedila in na ta način pomagali pri razvoju slovenskega jezika v digitalnem okolju. V prilogi vam pošiljamo pogodbo s seznamom naloženih datotek. MAIL_BODY=Hvala, ker ste prispevali besedila in na ta način pomagali pri razvoju slovenskega jezika v digitalnem okolju. V prilogi vam pošiljamo pogodbo s seznamom naloženih datotek.
Lep pozdrav, Lep pozdrav,
ekipa RSDO ekipa RSDO
MAIL_SUBJECT_PREDAVANJA=Projekt ON ({upload_id})
MAIL_BODY_PREDAVANJA=Spoštovani,
sodelavci projekta ON se vam zahvaljujemo za prispevek in sodelovanje. Morebitna vprašanja pošljite na naslov predavajalnik@cjvt.si.
Hvala in lep pozdrav,
ekipa CJVT UL

@ -1,3 +1,4 @@
import os
import hashlib import hashlib
import time import time
import ssl import ssl
@ -20,6 +21,8 @@ from email.mime.application import MIMEApplication
import pdfkit import pdfkit
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
import jwt
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from . model import db, UploadRegular, UploadSolar, RegisteredUser, UserInstitutionMapping, Institution, InstitutionContract, CorpusAccess from . model import db, UploadRegular, UploadSolar, RegisteredUser, UserInstitutionMapping, Institution, InstitutionContract, CorpusAccess
@ -238,8 +241,11 @@ class UploadHandler:
return None return None
def get_user_institutions(user_id): def get_user_institution(user_id):
return UserInstitutionMapping.query.filter_by(user=user_id).all() mapping = UserInstitutionMapping.query.filter_by(user=user_id).first()
if mapping:
return Institution.query.filter_by(id=mapping.institution).first()
return None
def has_user_corpus_access(user_id, corpus_name): def has_user_corpus_access(user_id, corpus_name):
@ -252,13 +258,11 @@ def has_user_corpus_access(user_id, corpus_name):
return True return True
# Check if user belongs to an institution, that has access to this corpus. # Check if user belongs to an institution, that has access to this corpus.
institutions = get_user_institutions(user_id) institution = get_user_institution(user_id)
has_access = False has_access = False
for institution in institutions: row = CorpusAccess.query.filter_by(institution=institution.id, corpus=corpus_name).first()
row = CorpusAccess.query.filter_by(institution=institution.id, corpus=corpus_name).first() if row:
if row: has_access = True
has_access = True
break
return has_access return has_access
@ -322,15 +326,86 @@ def add_user_to_institution(user_id, institution_id, role):
return model_obj.id return model_obj.id
def activate_user(user_id):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'active': True})
db.session.commit()
return rowcount
def update_user_password(user_id, new_password):
phash = generate_password_hash(new_password)
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'pass_hash': pass_hash})
db.session.commit()
return rowcount
def del_user_from_institution(user_id, institution_id):
db.session.query(UserInstitutionMapping).filter(UserInstitutionMapping.institution == institution_id).filter(UserInstitutionMapping.user == user_id).delete()
db.session.commit()
def get_all_active_users(): def get_all_active_users():
return RegisteredUser.query.filter_by(active=True).all() return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all()
def get_all_inactive_users():
return RegisteredUser.query.filter_by(active=False).order_by(RegisteredUser.id).all()
def get_all_active_users_join_institutions():
#return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all()
return db.session.query(RegisteredUser, UserInstitutionMapping).outerjoin(UserInstitutionMapping,
RegisteredUser.id == UserInstitutionMapping.user).order_by(RegisteredUser.id).all()
def get_all_active_institution_users(institution_id):
return RegisteredUser.query.filter_by(active=True).join(UserInstitutionMapping,
RegisteredUser.id == UserInstitutionMapping.user).filter(UserInstitutionMapping.institution == institution_id).all()
def is_institution_moderator(user_id, institution_id): def is_institution_moderator(user_id, institution_id):
user_inst_mapping = UserInstitutionMapping.query.filter_by(user=user_id).first() user_inst_mapping = UserInstitutionMapping.query.filter_by(user=user_id).filter_by(institution=institution_id).first()
if not user_inst_mapping: if not user_inst_mapping:
return False return False
if user_inst_mapping.role != 'moderator': if user_inst_mapping.role != 'moderator':
return False return False
return True return True
def is_institution_member(user_id, institution_id):
user_inst_mapping = UserInstitutionMapping.query.filter_by(user=user_id).filter_by(institution=institution_id).first()
if not user_inst_mapping:
return False
return True
def get_password_reset_token(email, expires=500):
return jwt.encode({'reset_password': email,
'exp': time() + expires},
key=os.getenv('APP_SECRET_KEY'), algorithm='HS256')
def verify_reset_token(token):
try:
email = jwt.decode(token,
key=os.getenv('APP_SECRET_KEY'), algorithms=["HS256"])['reset_password']
except Exception as e:
logging.error(e)
return
return RegisteredUser.query.filter_by(email=email).first()
def send_resetpass_mail(email, config):
jwt_token = get_password_reset_token(email)
text = '''
Zahtevali ste ponastavitev gesla vašega uporabniškega računa.
Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format(
'https://zbiranje.slovenscina.eu/solar/resetpass/{}'.format(jwt_token))
# Create a secure SSL context
context = ssl.create_default_context()
try:
with SMTP_SSL(config['MAIL_HOST'], config['SMTP_PORT'], context=context) as server:
server.login(config['MAIL_LOGIN'], config['MAIL_PASS'])
server.sendmail(config['MAIL_LOGIN'], email, text)
except Exception:
traceback.print_exc()

@ -1,140 +0,0 @@
import logging
import traceback
import re
from datetime import datetime
import portal.base
from portal.base import UploadHandler, ContractCreator, REGEX_EMAIL
from portal.model import db, UploadPredavanja
MAXLEN_FORM = 150
class UploadHandlerPredavanja(UploadHandler):
ENABLED_FILETYPES = None # None means all filetypes
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.contract_creator = ContractCreator(base_path=self.get_uploads_subdir('contracts'),
template_path='contract/predavanja.html')
def generate_upload_contract_pdf(self, upload_metadata):
form_data = upload_metadata['form_data']
files_table_str = []
for file_name in upload_metadata['file_names']:
files_table_str.append('<tr><td style="text-align: center;">')
files_table_str.append(file_name)
files_table_str.append('</td></tr>')
files_table_str = ''.join(files_table_str)
data = {
'ime_priimek': form_data['ime'],
'files_table_str': files_table_str
}
self.contract_creator.create_pdf(upload_metadata['contract_file'], data)
@staticmethod
def store_metadata(upload_metadata):
timestamp = datetime.fromtimestamp(upload_metadata['timestamp'])
form_data = upload_metadata['form_data']
file_hashes = upload_metadata['file_hashes_dict']
sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort()
# Normalize keywords list
keywords_list = []
for keyword in form_data['kljucne-besede'].split(','):
keyword = keyword.strip()
keywords_list.append(keyword)
keywords = ','.join(keywords_list)
try:
model_obj = UploadPredavanja(
upload_hash=upload_metadata['upload_id'],
timestamp=timestamp,
name=form_data['ime'],
address=form_data['naslov-predavanja'],
subject=form_data['predmet'],
faculty=form_data['fakulteta'],
email=form_data['email'],
phone=form_data.get('phone'),
keywords=keywords,
agree_publish_future=form_data['javna-objava-prihodnost'],
agree_machine_translation=True if 'strojno-prevajanje' in form_data else False,
agree_news_cjvt=True if 'obvestila' in form_data else False,
file_contract=upload_metadata['contract_file'],
upload_file_hashes=sorted_f_hashes,
)
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def handle_upload(self, request):
err = self.check_upload_request(request)
if err:
return err, 400
err = self.check_form(request.form)
if err:
return err, 400
# Parse request.
upload_metadata = self.extract_upload_metadata('predavanja', request)
logging.info('Upload for "predavanja" with id "{}" supplied form data: {}'.format(
upload_metadata['upload_id'], str(upload_metadata['form_data'])))
# Store uploaded files to disk.
self.store_datafiles(request.files, upload_metadata)
# Store metadata to database.
self.store_metadata(upload_metadata)
# Send confirmation mail
self.send_confirm_mail(upload_metadata)
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files))
@staticmethod
def check_form(form):
name = form.get('ime')
address = form.get('naslov-predavanja')
subject = form.get('predmet')
faculty = form.get('fakulteta')
email = form.get('email')
phone = form.get('telefon')
keywords = form.get('kljucne-besede')
agree_publish_future = form.get('javna-objava-prihodnost')
if not agree_publish_future:
return 'Manjkajoča izbrana vrednost pri polju za javno objavo.'
if not name \
or not address \
or not subject \
or not faculty \
or not email \
or not keywords:
return 'Izpolnite vsa obvezna polja.'
#for keyword in keywords.split(','):
# keyword = keyword.strip()
# if keyword.isspace() or not keyword.replace(' ', '').isalpha():
# return 'Ključna beseda "{}" ni pravilnega formata.'.format(keyword)
if not re.search(REGEX_EMAIL, email):
return 'Email napačnega formata.'
for key, val in form.items():
if key == 'kljucne-besde':
if len(val) > 500:
return 'Polje "{}" presega dolžino {} znakov.'.format(key, 500)
else:
if len(val) > MAXLEN_FORM:
return 'Polje "{}" presega dolžino {} znakov.'.format(key, MAXLEN_FORM)

@ -1,12 +1,12 @@
import logging import logging
import re import re
import traceback
import hashlib import hashlib
from datetime import datetime from datetime import datetime
from sqlalchemy import desc, exists from sqlalchemy import desc
from collections import Counter
from portal.base import UploadHandler, get_user_institutions, has_user_corpus_access from portal.base import UploadHandler, get_user_institution, has_user_corpus_access
from portal.model import db, UploadSolar, ContractsSolar, RegisteredUser, Institution, InstitutionContract, UserInstitutionMapping, CorpusAccess from portal.model import UploadSolar, ContractsSolar, RegisteredUser, Institution, InstitutionContract, UserInstitutionMapping, CorpusAccess
VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'} VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'}
@ -27,8 +27,7 @@ class UploadHandlerSolar(UploadHandler):
sorted_f_hashes = list(file_hashes.values()) sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort() sorted_f_hashes.sort()
# If user is mapped to multiple institutions, let him chose in name of which one he makes the upload. institution_id = get_user_institution(user_id).id
institution_id = get_user_institutions(user_id)[0].id
model_obj = UploadSolar( model_obj = UploadSolar(
upload_user = user_id, upload_user = user_id,
@ -147,7 +146,7 @@ class UploadHandlerSolar(UploadHandler):
if program not in VALID_PROGRAMS: if program not in VALID_PROGRAMS:
return 'Invalid program "{}"'.format(program) return 'Invalid program "{}"'.format(program)
if predmet not in VALID_SUBJECTS: if predmet not in VALID_SUBJECTS:
return 'Invalid subject "{}"'.format(premdet) return 'Invalid subject "{}"'.format(predmet)
if letnik < 1 or letnik > 9: if letnik < 1 or letnik > 9:
return 'Invalid grade: {}'.format(letnik) return 'Invalid grade: {}'.format(letnik)
if vrsta not in VALID_TEXT_TYPES: if vrsta not in VALID_TEXT_TYPES:
@ -176,14 +175,30 @@ def get_all_institutions():
return res return res
def get_institution_student_contracts(institution_id): def get_institution_student_contracts(institution_id, user_id=None):
return ContractsSolar.query.filter_by(institution=institution_id, contract_type='ucenci-starsi').all() if not user_id:
return ContractsSolar.query.filter_by(institution=institution_id, contract_type='ucenci-starsi').all()
return ContractsSolar.query.filter_by(institution=institution_id, contract_type='ucenci-starsi', upload_user=user_id).all()
def get_institution_contract(institution_id): def get_institution_contract(institution_id):
return InstitutionContract.query.filter_by(institution=institution_id, corpus='solar').order_by(desc(InstitutionContract.timestamp)).first() return InstitutionContract.query.filter_by(institution=institution_id, corpus='solar').order_by(desc(InstitutionContract.timestamp)).first()
def get_top_uploading_institutions():
res = dict()
institutions = get_all_institutions()
for institution in institutions:
uploads = UploadSolar.query.filter_by(institution=institution.id).all()
for upload in uploads:
if institution.name not in res:
res[institution.name] = 0
res[institution.name] += len(upload.upload_file_hashes)
if len(res) >= 5:
return dict(sorted(res.items(), key=lambda x:x[1], reverse=True)[:5])
return dict(sorted(res.items(), key=lambda x:x[1], reverse=True))
def get_all_active_users(): def get_all_active_users():
# TODO: do filtering purely within an SQL query # TODO: do filtering purely within an SQL query
res = [] res = []

File diff suppressed because it is too large Load Diff

@ -119,6 +119,89 @@ label {
margin-bottom: 30px; margin-bottom: 30px;
} }
.container-title {
font-family: Roboto;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 14px;
color: #46535b;
text-align: center;
margin-bottom: 30px;
}
#history-container {
height: 500px;
overflow-y: auto;
}
#contract-container {
height: 300px;
overflow-y: auto;
padding: 20px;
}
.contract-item {
height: 50px;
margin: 5px;
border: 0px;
border-bottom: 2px solid #c4c4c4;
}
.contract-item-title {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
color: #46535b;
}
.contract-item-button {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
}
.contract-item-date {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 10px;
color: #46535b;
}
#collaborators-container {
height: 370px;
background: #f5f5f5;
margin: 25px;
padding:5px;
border-radius: 10px;
}
.collaborators-item {
height: 30px;
margin: 5px;
border: 0px;
border-bottom: 2px solid #c4c4c4;
}
.collaborators-item-name {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
color: #46535b;
}
#awards-container {
height: 250px;
background: #f5f5f5;
margin: 25px;
padding:5px;
border-radius: 10px;
}
#button-submit { #button-submit {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

@ -1,199 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal za oddajanje besedil</title>
<!--{{ dropzone.load_css() }}-->
<link rel="stylesheet" href="/static/dropzone.css" type="text/css">
{{ dropzone.style('position: absolute;
top: -0.5px;
width: 388px;
height: 831px;
left: 385px;
background: linear-gradient(198.62deg, rgba(255, 255, 255, 0.49) -1.62%, rgba(255, 255, 255, 0.73) -1.61%, rgba(255, 255, 255, 0.41) 79.34%);
box-shadow: 20px 4px 40px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
border: 0px;
border-radius: 0px 20px 20px 0px;') }}
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div id="main-window" style="top: 45%;">
<div id="rect1" style="height: 831px;">
<div id="logo-container" style="top: -7.8%;">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<form id="my-dropzone" class="dropzone">
<div style="position: relative; right: 390px;">
<h1 id="title">Portal za oddajanje besedil</h1>
<div class="form-text">{{description|safe}}</div>
<label for="ime">* Ime in priimek:</label>
<input type="text" id="ime" name="ime" required="required"/>
<label for="naslov-predavanja">* Naslov predavanja:</label>
<input type="text" id="naslov-predavanja" name="naslov-predavanja" required="required"/>
<label for="predmet">* Predmet:</label>
<input type="text" id="predmet" name="predmet" required="required"/>
<label for="fakulteta">* Članica:</label>
<input type="text" id="fakulteta" name="fakulteta" required="required"/>
<label for="email">* E-Pošta:</label>
<input type="text" id="email" name="email" required="required"/>
<label for="telefon">Telefon:</label>
<input type="text" id="telefon" name="telefon"/>
<label for="kljucne-besede">* Ključne besede (ločene z vejico):</label>
<input type="text" id="kljucne-besede" name="kljucne-besede" required="required"/>
<br>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label style="width: 95%; text-transform: none; font-size: 12px;"><b>*Privolitev:</b><br>Strinjam se, da Univerza v Ljubljani uporabi posnetek naloženega predavanja v okviru projekta za strojno prevajanje predavanj ON. Dostop do posnetka bodo imeli izključno sodelavci projekta za namen transkripcije govora.</label>
<input style="width: 5%;" type="checkbox" name="strojno-prevajanje" value="strojno-prevajanje">
</div>
<div style="display: flex; flex-direction: row; justify-content: left; align-items: center; width: 310px;">
<label style="text-transform: none; font-size: 12px;"><b>Objava posnetka na portalu ON:</b><br>Ali bi se v prihodnosti strinjali z objavo posnetka na portalu sistema ON? (V primeru strinjanja bi podpisali poseben dogovor o pogojih objave.)</label>
<div style="display: inline-block;">
<input type="radio" name="javna-objava-prihodnost" value="da" style="display: inline; float: left; width: 20px;" >
<label for="da" style="display: inline; float: right; position: absolute; margin-top: 5px;">Da</label><br>
<input type="radio" name="javna-objava-prihodnost" value="morda" style="display: inline; float: left; width: 20px;">
<label for="morda" style="display: inline; float: right; position: absolute; margin-top: 10px;">Morda</label><br>
<input type="radio" name="javna-objava-prihodnost" value="ne" style="display: inline; float: left; width: 20px;">
<label for="ne" style="display: inline; float: right; position: absolute; margin-top: 18px;">Ne</label><br>
</div>
</div>
<br>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label style="width: 95%; text-transform: none; font-size: 12px;"><b>Obvestila:</b><br>Želim, da me Center za jezikovne vire in tehnologije UL obvešča o novicah v zvezi s sistemom za strojno prevajanje predavanj ON.</label>
<input style="width: 5%;" type="checkbox" name="obvestila" value="obvestila">
</div>
<br>
<a class="form-text" href="https://www.cjvt.si/obvestilo-o-obdelavi-osebnih-podatkov/" style="cursor: pointer;">Obvestilo o obdelavi osebnih podatkov</a>
<button id="button-submit" type="submit" style="top: 745px;">Oddaj</button>
</div>
<div class="dropzone-previews"></div>
</form>
</div>
</div>
<!--{{ dropzone.load_js() }}-->
<script src="/static/dropzone.js"></script>
<script>
/////////////////////////
// Dropzone //
/////////////////////////
var btnSubmit = document.getElementById("button-submit");
var form = document.forms["my-dropzone"];
function isEmptyOrSpaces(str){
return str == null || str.match(/^ *$/) !== null;
}
const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
//const reKeyword = /^[a-zA-Zščđ枊ČĐĆŽ, \-]+$/;
Dropzone.options.myDropzone = { // The camelized version of the ID of the form element
url: "/predavanja/upload",
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: {{max_files}},
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 10000, // MB
timeout: 10000000, // milliseconds
//acceptedFiles: ".txt, .csv, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx",
maxFiles: {{max_files}},
dictDefaultMessage: `Kliknite ali odložite datoteke sem.`,
dictFallbackMessage: "Vaš brskalnik ne podpira izbiranje datotek z odlaganjem (\"drag & drop\").",
dictInvalidFileType: "Datoteka je napačnega formata.",
dictFileTooBig: "Datoteke je prevelika {{filesize}}. Največja dovoljena velikost: {{maxFilesize}}MiB.",
dictResponseError: "Napaka strežnika: {{statusCode}}",
dictMaxFilesExceeded: "Največje število datotek že doseženo.",
dictCancelUpload: "Prekini prenos",
dictRemoveFile: "Odstrani datoteko",
dictCancelUploadConfirmation: "Ali res želite odstraniti to datoteko?",
dictUploadCanceled: "Prenos prekinjen",
// The setting up of the dropzone
init: function() {
var dz = this;
btnSubmit.addEventListener("click", function(e) {
// Make sure that the form isn't actually being sent.
e.preventDefault();
e.stopPropagation();
// Check form validity.
var ime = form["ime"].value;
var naslov = form["naslov-predavanja"].value;
var predmet = form["predmet"].value;
var fakulteta = form["fakulteta"].value;
var kljucneBesede = form["kljucne-besede"].value;
var email = form["email"].value;
var telefon = form["telefon"].value;
var privolitev = form["strojno-prevajanje"].checked;
var javnaObjava = form["javna-objava-prihodnost"];
if (isEmptyOrSpaces(ime) ||
isEmptyOrSpaces(naslov) ||
isEmptyOrSpaces(predmet) ||
isEmptyOrSpaces(fakulteta) ||
isEmptyOrSpaces(kljucneBesede) ||
isEmptyOrSpaces(email)) {
alert("Izpolnite vsa obvezna polja!");
} else if (!reEmail.test(email.toLowerCase())) {
alert("Email napačnega formata!");
// } else if (!reKeyword.test(kljucneBesede)) {
// alert("Ključne besede so napačnega formata! Besede ločujte z vejico. Besede naj ne vsebujejo posebnih znakov.");
} else if (ime.length > 100 ||
naslov.length > 100 ||
predmet.length > 100 ||
fakulteta.length > 100 ||
kljucneBesede.length > 100 ||
email.length > 100 ||
telefon.length > 100) {
alert("Velikost polj je omejena na 100 znakov.");
} else if (!privolitev) {
alert("Odkljukana privolitev je pogoj za oddajo.");
} else if (javnaObjava.value == "") {
alert("Izberite eno izmed možnosti pri polju za objavo na portalu ON!");
} else {
// Hand off data to dropzone
dz.processQueue();
// Clear fields and hide popup agian
btnSubmit.disabled = false;
form.reset();
}
});
// Listen to the sendingmultiple event. In this case, it's the sendingmultiple event instead
// of the sending event because uploadMultiple is set to true.
this.on("sendingmultiple", function() {
// Gets triggered when the form is actually being sent.
// Hide the success button or the complete form.
});
this.on("successmultiple", function(files, response) {
// Gets triggered when the files have successfully been sent.
// Redirect user or notify of success.
alert("Odgovor strežnika: " + response);
location.reload();
});
this.on("errormultiple", function(files, response) {
// Gets triggered when there was an error sending the files.
// Maybe show form again, and notify user of error
});
}
}
</script>
</body>
</html>

@ -6,6 +6,6 @@
<body> <body>
<a href="/prevodi">Korpus paralelnih besdil ANG-SLO</a><br> <a href="/prevodi">Korpus paralelnih besdil ANG-SLO</a><br>
<a href="/gigafida">Korpus Gigafida</a><br> <a href="/gigafida">Korpus Gigafida</a><br>
<a href="/predavanja">Korpus Predavanja</a><br> <a href="/solar">Korpus Šolar</a><br>
</body> </body>
</html> </html>

@ -6,7 +6,7 @@
<style> <style>
.tableFixHead { .tableFixHead {
overflow-y: auto; overflow-y: auto;
height: 106px; height: 306px;
} }
.tableFixHead thead th { .tableFixHead thead th {
position: sticky; position: sticky;
@ -27,6 +27,13 @@
</style> </style>
</head> </head>
<body> <body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div style="background: blue;">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<h2>Uporabniki</h2> <h2>Uporabniki</h2>
<h3>Dodaj uporabnika</h3> <h3>Dodaj uporabnika</h3>
<form action="/solar/adduser" method="post"> <form action="/solar/adduser" method="post">
@ -35,7 +42,7 @@
<label for="email">Email:</label><br> <label for="email">Email:</label><br>
<input type="text" id="email" name="email"><br> <input type="text" id="email" name="email"><br>
<label for="password">Geslo:</label><br> <label for="password">Geslo:</label><br>
<input type="text" id="password" name="password"><br> <input type="password" id="password" name="password"><br>
<input type="submit" value="Dodaj"> <input type="submit" value="Dodaj">
</form> </form>
<!--<h3>Odstrani uporabnika</h3> <!--<h3>Odstrani uporabnika</h3>
@ -52,15 +59,16 @@
<th>ID</th> <th>ID</th>
<th>Ime in priimek</th> <th>Ime in priimek</th>
<th>Email</th> <th>Email</th>
<th>ID institucije</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in users %} {% for item in users %}
<tr> <tr>
<td>{{item.id}}</td> <td>{{item[0].id}}</td>
<td>{{item.name}}</td> <td>{{item[0].name}}</td>
<td>{{item.email}}</td> <td>{{item[0].email}}</td>
</tr> <td>{{item[1].institution}}</td>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
@ -107,4 +115,30 @@
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
<h3>Seznam uporabnikov, ki niso aktivirani</h3>
<div class="tableFixHead">
<table>
<thead>
<tr>
<th>ID</th>
<th>Ime in priimek</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for item in inactive_users %}
<tr>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.email}}</td>
</tr>
{% endfor %}
</table>
</div>
<h3>Aktiviraj uporabnika</h3>
<form action="/solar/activateuser" method="post">
<label for="id">ID uporabnika:</label>
<input type="text" id="id" name="id"><br>
<input type="submit" value="Aktiviraj">
</form>
</body> </body>

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal ŠOLAR</title>
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div id="main-window">
<div id="rect1">
<div style="padding: 50px;">
<div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<h3 id="title" style="font-size: 27px; text-align: left;">Pozabljeno geslo - ŠOLAR</h3>
<div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/solar/sendresetpass">
<div>
<div>
<input type="email" name="email" placeholder="Email" autofocus="">
</div>
</div>
<button class="button-general" style="margin-top: 20px;">Pošlji povezavo za ponastavitev geslo</button>
</form>
<a href="/solar/register" class="contract-item-button">Registracija</a>
</div>
</div>
</div>
<div id="rect2" class="mock-side">
</div>
</div>
</body>

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin panel - Šolar</title>
</head>
<body>
<h3>Uporabniki</h3>
TODO: odobri registracije
<form> </form>
<div> </div>
</body>

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Portal {{title}}</title> <title>Portal ŠOLAR</title>
<link rel="stylesheet" href="/static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
</head> </head>
<body> <body>
@ -12,7 +12,7 @@
<div id="logo-container"> <div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/> <img src="/static/image/logo.svg" alt="logo"/>
</div> </div>
<h3 id="title" style="font-size: 27px; text-align: left;">Prijava - {{title}}</h3> <h3 id="title" style="font-size: 27px; text-align: left;">Prijava - ŠOLAR</h3>
<div> <div>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
@ -21,7 +21,7 @@
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<form method="POST" action="/{{corpus_name}}/login"> <form method="POST" action="/solar/login">
<div> <div>
<div> <div>
<input type="email" name="email" placeholder="Email" autofocus=""> <input type="email" name="email" placeholder="Email" autofocus="">
@ -39,6 +39,7 @@
</div> </div>
<button class="button-general" style="margin-top: 20px;">PRIJAVA</button> <button class="button-general" style="margin-top: 20px;">PRIJAVA</button>
</form> </form>
<a href="/solar/register" class="contract-item-button">Registracija</a>
</div> </div>
</div> </div>
</div> </div>

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Upravljanje institucije - Šolar</title>
<style>
.tableFixHead {
overflow-y: auto;
height: 206px;
}
.tableFixHead thead th {
position: sticky;
top: 0;
}
table {
border-collapse: collapse;
width: 100%;
}
th,
td {
padding: 8px 16px;
border: 1px solid #ccc;
}
th {
background: #eee;
}
</style>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<h3>Seznam vseh aktivnih uporabnikov</h3>
<div class="tableFixHead">
<table>
<thead>
<tr>
<th>ID</th>
<th>Ime in priimek</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for item in users %}
<tr>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.email}}</td>
</tr>
{% endfor %}
</table>
</div>
<h3>Seznam uporabnikov v vaši instituciji</h3>
<div class="tableFixHead">
<table>
<thead>
<tr>
<th>ID</th>
<th>Ime in priimek</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for item in institution_users %}
<tr>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.email}}</td>
</tr>
{% endfor %}
</table>
</div>
<br>
<h3>Dodeli uporabnika instituciji</h3>
<form action="/solar/addusertoinstitution" method="post">
<label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br>
<label for="role">Vloga v instituciji:</label>
<select name="role" id="role">
<option value="moderator">Moderator</option>
<option value="user">Uporabnik</option>
</select>
<input type="submit" value="Dodeli">
</form>
<h3>Odstrani uporabnika iz institucije</h3>
<form action="/solar/deluserfrominstitution" method="post">
<label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani">
</form>
<div> </div>
</body>

@ -19,6 +19,12 @@
</head> </head>
<body> <body>
<a href="/solar/logout">Odjavi se</a> <a href="/solar/logout">Odjavi se</a>
{% if is_institution_moderator %}
<br><a href="/solar/manage-institution">Upravljaj z institucijo</a>
{% endif %}
{% if is_admin %}
<br><a href="/solar/admin">Administracijski meni</a>
{% endif %}
<div class="bg"></div> <div class="bg"></div>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">

@ -7,6 +7,12 @@
</head> </head>
<body> <body>
<a href="/solar/logout">Odjavi se</a> <a href="/solar/logout">Odjavi se</a>
{% if is_institution_moderator %}
<br><a href="/solar/manage-institution">Upravljaj z institucijo</a>
{% endif %}
{% if is_admin %}
<br><a href="/solar/admin">Administracijski meni</a>
{% endif %}
<div class="bg"></div> <div class="bg"></div>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">
@ -21,13 +27,13 @@
<button onclick="window.location.replace('/solar/pogodbe');" class="selection-tab-button selected">POGODBE</button> <button onclick="window.location.replace('/solar/pogodbe');" class="selection-tab-button selected">POGODBE</button>
</div> </div>
</div> </div>
<div id="conract-container" style="padding: 20px;"> <div id="contract-container">
{% if contract_school %} {% if contract_school %}
<div class="contract-item" style="background-color: #ccffcc;"> <div class="contract-item" style="background-color: #ccffcc;">
<div class="contract-item-icon"></div> <div class="contract-item-icon"></div>
<div class="contract-item-title">Pogodba s šolo</div> <div class="contract-item-title">Pogodba s šolo</div>
<div class="contract-item-date">DODANO {{contract_school.date}}</div> <div class="contract-item-date">DODANO: {{contract_school.timestamp}}</div>
<a href="/solar/pogodbe/{{ contract_school.file_contract }}.pdf" class="contract-item-button">PRENESI</a> <a href="/solar/pogodbe/{{ contract_school.file_contract }}.pdf" class="contract-item-button">Prenesi</a>
</div> </div>
</br> </br>
{% endif %} {% endif %}
@ -35,8 +41,8 @@
<div class="contract-item"> <div class="contract-item">
<div class="contract-item-icon"></div> <div class="contract-item-icon"></div>
<div class="contract-item-title">Pogodba o prenosu lastništva</div> <div class="contract-item-title">Pogodba o prenosu lastništva</div>
<div class="contract-item-date">DODANO {{item.date}}</div> <div class="contract-item-date">DODANO: {{item.timestamp}}</div>
<a href="/solar/pogodbe/{{ item.file_contract }}.pdf" class="contract-item-button">PRENESI</a> <a href="/solar/pogodbe/{{ item.file_contract }}.pdf" class="contract-item-button">Prenesi</a>
</div> </div>
</br> </br>
{% endfor %} {% endfor %}
@ -69,6 +75,19 @@
{% endif %} {% endif %}
</div> </div>
<div id="rect2" class="mock-side"> <div id="rect2" class="mock-side">
<div id="collaborators-container">
<div class="container-title">Sodelujoči</div>
<div style="overflow-y: auto;">
{% for item in collaborators %}
<div class="collaborators-item">
<div class="collaborators-item-name">{{item.name}}</div>
</div>
{% endfor %}
</div>
</div>
<div id="awards-container">
<div class="container-title">Nagrade</div>
</div>
</div> </div>
</div> </div>
</body> </body>

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal ŠOLAR</title>
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div id="main-window">
<div id="rect1">
<div style="padding: 50px;">
<div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<h3 id="title" style="font-size: 27px; text-align: left;">Registracija - ŠOLAR</h3>
<div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/solar/register">
<div>
<div>
<input type="name" name="name" placeholder="Ime in priimek" autofocus="">
</div>
</div>
<div>
<div>
<input type="email" name="email" placeholder="Email" autofocus="">
</div>
</div>
<div>
<div>
<input type="password" name="password" placeholder="Geslo">
</div>
</div>
<button class="button-general" style="margin-top: 20px;">REGISTRACIJA</button>
</form>
</div>
</div>
</div>
<div id="rect2" class="mock-side">
</div>
</div>
</body>

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal ŠOLAR</title>
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div id="main-window">
<div id="rect1">
<div style="padding: 50px;">
<div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<h3 id="title" style="font-size: 27px; text-align: left;">Prijava - ŠOLAR</h3>
<div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/solar/login">
<div>
<div>
<input type="email" name="email" placeholder="Email" autofocus="">
</div>
</div>
<div>
<div>
<input type="password" name="password" placeholder="Geslo">
</div>
</div>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label>Zapomni prijavo</label>
<input style="width: 10%;" type="checkbox">
</div>
<button class="button-general" style="margin-top: 20px;">PRIJAVA</button>
</form>
<a href="/solar/register" class="contract-item-button">Registracija</a>
</div>
</div>
</div>
<div id="rect2" class="mock-side">
</div>
</div>
</body>

@ -4,9 +4,16 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Portal za oddajanje besedil</title> <title>Portal za oddajanje besedil</title>
<link rel="stylesheet" href="/static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
<script src="/static/chart.js"></script>
</head> </head>
<body> <body>
<a href="/solar/logout">Odjavi se</a> <a href="/solar/logout">Odjavi se</a>
{% if is_institution_moderator %}
<br><a href="/solar/manage-institution">Upravljaj z institucijo</a>
{% endif %}
{% if is_admin %}
<br><a href="/solar/admin">Administracijski meni</a>
{% endif %}
<div class="bg"></div> <div class="bg"></div>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">
@ -72,6 +79,40 @@
</div> </div>
</div> </div>
<div id="rect2" class="mock-side"> <div id="rect2" class="mock-side">
<canvas id="myChart" width="400" height="400"></canvas>
<script>
function drawChart(data) {
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(data),
datasets: [{
label: 'št. naloženih datotek',
data: Object.values(data),
backgroundColor: 'rgba(54, 162, 235, 1.0)',
borderWidth: 1
}]
},
options: {
plugins: {
title: {
display: true,
text: ''
}
},
scales: {
y: {
beginAtZero: true
}
},
indexAxis: 'y'
}
});
}
fetch('/solar/topuploads').then(r => r.json()).then(j => drawChart(j));
</script>
</div> </div>
</div> </div>
</body> </body>

Loading…
Cancel
Save