registration, charts, contracts history
This commit is contained in:
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"]
|
||||||
|
|
324
app.py
324
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):
|
if not corpus_name in ENABLED_CORPUSES:
|
||||||
|
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']
|
||||||
|
role = request.form['role']
|
||||||
|
if role not in ['moderator', 'user']:
|
||||||
|
return '', 404
|
||||||
|
|
||||||
|
if portal.base.get_user_institution(user_id):
|
||||||
|
flash('Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\
|
||||||
|
'zaenkrat ni implementirano.')
|
||||||
|
return redirect(redirect_url())
|
||||||
|
|
||||||
|
portal.base.add_user_to_institution(user_id, institution_id, role)
|
||||||
|
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
|
return '', 404
|
||||||
if not corpus_name in ENABLED_CORPUSES:
|
if not corpus_name in ENABLED_CORPUSES:
|
||||||
return '', 404
|
return '', 404
|
||||||
|
|
||||||
user_id = request.form['user_id']
|
user_id = request.form['user_id']
|
||||||
institution_id = request.form['institution_id']
|
|
||||||
role = request.form['role']
|
|
||||||
if role not in ['moderator', 'user']:
|
|
||||||
return '', 404
|
|
||||||
|
|
||||||
# TODO: remove this restriction
|
if not portal.base.is_institution_member(user_id, institution.id):
|
||||||
if len(portal.base.get_user_institutions(user_id)) > 0:
|
flash('Uporabnik ni član vaše institucije.')
|
||||||
return 'Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\
|
return redirect(redirect_url())
|
||||||
'zaenkrat ni implementirano.'
|
|
||||||
|
|
||||||
portal.base.add_user_to_institution(user_id, institution_id, role)
|
portal.base.del_user_from_institution(user_id, institution.id)
|
||||||
return 'Uporabnik je bil dodeljen instituciji.'
|
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):
|
||||||
|
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').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 = []
|
||||||
|
|
13222
static/chart.js
Normal file
13222
static/chart.js
Normal file
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>
|
||||||
|
|
40
templates/solar-forgotpass.html
Normal file
40
templates/solar-forgotpass.html
Normal file
|
@ -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>
|
97
templates/solar-manage-institution.html
Normal file
97
templates/solar-manage-institution.html
Normal file
|
@ -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>
|
||||||
|
|
50
templates/solar-register.html
Normal file
50
templates/solar-register.html
Normal file
|
@ -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>
|
49
templates/solar-resetpass.html
Normal file
49
templates/solar-resetpass.html
Normal file
|
@ -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…
Reference in New Issue
Block a user