Separated ŠOLAR from other portals.

master
msinkec 3 years ago
parent 7d9d2b175c
commit cead80ed79

318
app.py

@ -12,16 +12,10 @@ from flask_script import Manager
from flask_login import LoginManager, login_required, login_user, current_user, logout_user from flask_login import LoginManager, login_required, login_user, current_user, logout_user
from portal.model import db, RegisteredUser from portal.model import db, RegisteredUser
import portal.base
import portal.solar import portal.solar
import portal.regular
# TODO: Implement user registration.
# TODO: Integrate Shibboleth login. # TODO: Integrate Shibboleth login.
# TODO: make logging level configurable # TODO: make logging level configurable
logging.basicConfig(level=logging.DEBUG, format='[APP LOGGER] %(asctime)s %(levelname)s: %(message)s') logging.basicConfig(level=logging.DEBUG, format='[APP LOGGER] %(asctime)s %(levelname)s: %(message)s')
@ -44,8 +38,6 @@ 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']
SQL_CONN_STR = config['SQL_CONN_STR'] SQL_CONN_STR = config['SQL_CONN_STR']
DESC_PREVODI = config['DESC_PREVODI']
DESC_GIGAFIDA = config['DESC_GIGAFIDA']
if 'UPLOADS_DIR' in config: if 'UPLOADS_DIR' in config:
UPLOADS_DIR = Path(config['UPLOADS_DIR']) UPLOADS_DIR = Path(config['UPLOADS_DIR'])
@ -81,13 +73,6 @@ if 'PORTALDS4DS1_MAIL_BODY' in os.environ:
MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY'] MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY']
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:
DESC_PREVODI = os.environ['PORTALDS4DS1_DESC_PREVODI']
if 'PORTALDS4DS1_DESC_GIGAFIDA' in os.environ:
DESC_GIGAFIDA = os.environ['PORTALDS4DS1_DESC_GIGAFIDA']
ENABLED_CORPUSES = ['prevodi', 'gigafida', 'solar']
CORPUSES_LOGIN_REQUIRED = ['solar']
###################### ######################
@ -113,20 +98,6 @@ manager.add_command('db', MigrateCommand)
# Set up dropzone.js to serve all the stuff for "file dropping" on the web interface. # Set up dropzone.js to serve all the stuff for "file dropping" on the web interface.
dropzone = Dropzone(app) dropzone = Dropzone(app)
upload_handler_regular = portal.regular.UploadHandlerRegular(
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,
MAIL_BODY=MAIL_BODY,
CONTRACT_CLIENT_CONTACT=CONTRACT_CLIENT_CONTACT,
MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD,
APP_SECRET_KEY=APP_SECRET_KEY
)
upload_handler_solar = portal.solar.UploadHandlerSolar( upload_handler_solar = portal.solar.UploadHandlerSolar(
UPLOADS_DIR=UPLOADS_DIR, UPLOADS_DIR=UPLOADS_DIR,
MAIL_HOST=MAIL_HOST, MAIL_HOST=MAIL_HOST,
@ -155,26 +126,9 @@ def redirect_url(default='/'):
@app.route('/') @app.route('/')
def index(): def index():
return render_template('index.html') if current_user.is_authenticated:
return redirect('/oddaja')
return redirect('/login')
@app.route('/<corpus_name>')
def index_corpus(corpus_name):
if corpus_name not in ENABLED_CORPUSES:
return 'Korpus "{}" ne obstaja.'.format(corpus_name), 404
description = ""
if corpus_name == 'prevodi':
description = DESC_PREVODI
elif corpus_name == 'gigafida':
description = DESC_GIGAFIDA
elif corpus_name == 'solar':
if current_user.is_authenticated:
return redirect('/solar/oddaja')
return redirect('/solar/login')
return render_template('basic.html',
corpus_name=corpus_name, description=description, max_files=MAX_FILES_PER_UPLOAD)
@login_manager.user_loader @login_manager.user_loader
@ -183,120 +137,116 @@ def load_user(user_id):
return user return user
@app.route('/solar/login') @app.route('/login')
def solar_login_get(): def solar_login_get():
return render_template('solar-login.html') return render_template('solar-login.html')
@app.route('/solar/register') @app.route('/register')
def solar_register_get(): def solar_register_get():
return render_template('solar-register.html') return render_template('solar-register.html')
@app.route('/solar/login', methods=['POST']) @app.route('/login', methods=['POST'])
def solar_login_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
user = portal.base.get_user_obj_by_email(email) user = portal.solar.get_user_obj_by_email(email)
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('/solar/login') return redirect('/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('/solar/login') return redirect('/login')
#portal.base.add_user_session(user.id) #portal.solar.add_user_session(user.id)
login_user(user, remember=remember) login_user(user, remember=remember)
return redirect('/solar/oddaja') return redirect('/oddaja')
@app.route('/solar/register', methods=['POST']) @app.route('/register', methods=['POST'])
def solar_register_post(): def solar_register_post():
name = request.form.get('name') name = request.form.get('name')
email = request.form.get('email') email = request.form.get('email')
password = request.form.get('password') password = request.form.get('password')
institution_name = request.form.get('institution') institution_name = request.form.get('institution')
institution_role = request.form.get('role') institution_role = request.form.get('role')
institution = portal.base.get_institution_obj_by_name(institution_name) institution = portal.solar.get_institution_obj_by_name(institution_name)
user = RegisteredUser.query.filter_by(email=email).first() user = RegisteredUser.query.filter_by(email=email).first()
if user: if user:
flash('Uporabniški račun s tem emailom je že registriran.') flash('Uporabniški račun s tem emailom je že registriran.')
return redirect('/solar/register') return redirect('/register')
if not name: if not name:
flash('Prazno polje za ime.') flash('Prazno polje za ime.')
return redirect('/solar/register') return redirect('/register')
if len(name) > 100: if len(name) > 100:
flash('Predolgo ime.') flash('Predolgo ime.')
return redirect('/solar/register') return redirect('/register')
if not email: if not email:
flash('Prazno polje za elektronsko pošto.') flash('Prazno polje za elektronsko pošto.')
return redirect('/solar/register') return redirect('/register')
if len(email) > 100: if len(email) > 100:
flash('Predolgi email naslov') flash('Predolgi email naslov')
return redirect('/solar/register') return redirect('/register')
elif not re.search(portal.base.REGEX_EMAIL, email): elif not re.search(portal.solar.REGEX_EMAIL, email):
flash('Email napačnega formata.') flash('Email napačnega formata.')
return redirect('/solar/register') return redirect('/register')
if not password: if not password:
flash('Prazno polje za geslo.') flash('Prazno polje za geslo.')
return redirect('/solar/register') return redirect('/register')
if len(password) > 8: if len(password) > 8:
flash('Geslo mora biti vsaj 8 znakov dolgo.') flash('Geslo mora biti vsaj 8 znakov dolgo.')
return redirect('/solar/register') return redirect('/register')
if len(password) > 100: if len(password) > 100:
flash('Predolgo geslo.') flash('Predolgo geslo.')
return redirect('/solar/register') return redirect('/register')
if institution_role not in ['coordinator', 'mentor', 'other']: if institution_role not in ['coordinator', 'mentor', 'other']:
flash('Neveljavna vloga v instituciji.') flash('Neveljavna vloga v instituciji.')
return redirect('/solar/register') return redirect('/register')
if not institution: if not institution:
institution_id = portal.base.add_institution(institution_name, "") institution_id = portal.solar.add_institution(institution_name, "")
portal.base.grant_institution_corpus_access(institution_id, "solar") portal.solar.grant_institution_corpus_access(institution_id, "solar")
else: else:
institution_id = institution.id institution_id = institution.id
user_id = portal.base.register_new_user(name, email, password, active=False) user_id = portal.solar.register_new_user(name, email, password, active=False)
portal.base.add_user_to_institution(user_id, institution_id, institution_role) portal.solar.add_user_to_institution(user_id, institution_id, institution_role)
portal.base.send_admins_new_user_notification_mail(user_id, upload_handler_solar.config) portal.solar.send_admins_new_user_notification_mail(user_id, upload_handler_solar.config)
flash('Podatki so bili poslani v potrditev. Ko bo registracija potrjena, boste o tem obveščeni po e-mailu, ki ste ga posredovali zgoraj.') flash('Podatki so bili poslani v potrditev. Ko bo registracija potrjena, boste o tem obveščeni po e-mailu, ki ste ga posredovali zgoraj.')
return redirect('/solar/login') return redirect('/login')
# TODO: Move solar stuff to seperate file using Flask blueprints. @app.route('/logout')
# TODO: Better routing logic.
@app.route('/solar/logout')
@login_required @login_required
def logout(): def logout():
logout_user() logout_user()
return redirect('/solar/login') return redirect('/login')
@app.route('/solar/<path:text>') @app.route('/<path:text>')
@login_required @login_required
def solar(text): def solar(text):
is_admin = current_user.role == 'admin' is_admin = current_user.role == 'admin'
current_user_institution = portal.base.get_user_institution(current_user.id) current_user_institution = portal.solar.get_user_institution(current_user.id)
current_user_obj = portal.base.get_user_obj(current_user.get_id()) current_user_obj = portal.solar.get_user_obj(current_user.get_id())
institution_contract = None institution_contract = None
if current_user_institution: if current_user_institution:
current_user_institution_coordinator = portal.base.is_institution_coordinator(current_user.id, current_user_institution.id) current_user_institution_coordinator = portal.solar.is_institution_coordinator(current_user.id, current_user_institution.id)
institution_contract = portal.base.get_institution_contract(current_user_institution.id) institution_contract = portal.solar.get_institution_contract(current_user_institution.id)
else: else:
current_user_institution_coordinator = False current_user_institution_coordinator = False
@ -313,8 +263,8 @@ def solar(text):
uploader_names = [] uploader_names = []
institution_names = [] institution_names = []
for item in upload_items: for item in upload_items:
uploader_names.append(portal.base.get_user_obj(item.upload_user).name) uploader_names.append(portal.solar.get_user_obj(item.upload_user).name)
institution = portal.base.get_institution_obj(item.institution) institution = portal.solar.get_institution_obj(item.institution)
if not institution: if not institution:
institution_names.append(None) institution_names.append(None)
else: else:
@ -334,9 +284,9 @@ def solar(text):
f_hash = filename.split('.')[0] f_hash = filename.split('.')[0]
if contract_type == 'institucije': if contract_type == 'institucije':
actual_filename = portal.base.get_actual_institution_contract_filename(f_hash) actual_filename = portal.solar.get_actual_institution_contract_filename(f_hash)
else: else:
actual_filename = portal.base.get_actual_studentparent_contract_filename(f_hash) actual_filename = portal.solar.get_actual_studentparent_contract_filename(f_hash)
safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), prefix, suffix) safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), prefix, suffix)
try: try:
@ -351,11 +301,11 @@ def solar(text):
collaborators = [] collaborators = []
cooperation_history = dict() cooperation_history = dict()
if current_user_institution: if current_user_institution:
collaborators = portal.base.get_all_active_institution_users(current_user_institution.id) collaborators = portal.solar.get_all_active_institution_users(current_user_institution.id)
show_upload_form = True show_upload_form = True
contract_school = portal.solar.get_institution_contract(current_user_institution.id) contract_school = portal.solar.get_institution_contract(current_user_institution.id)
cooperation_history = portal.base.get_institution_cooperation_history(current_user_institution.id) cooperation_history = portal.solar.get_institution_cooperation_history(current_user_institution.id)
if portal.base.is_institution_coordinator(current_user_obj.id, current_user_institution.id): if portal.solar.is_institution_coordinator(current_user_obj.id, current_user_institution.id):
contracts_students = portal.solar.get_institution_student_contracts(current_user_institution.id) contracts_students = portal.solar.get_institution_student_contracts(current_user_institution.id)
enable_upload_school_contract = True enable_upload_school_contract = True
else: else:
@ -370,23 +320,23 @@ def solar(text):
user_id=current_user.id, user_id=current_user.id,
is_admin=is_admin, is_institution_coordinator=current_user_institution_coordinator) is_admin=is_admin, is_institution_coordinator=current_user_institution_coordinator)
elif text.startswith('admin/') or text == 'admin': elif text.startswith('admin/') or text == 'admin':
users = portal.base.get_all_users_join_institutions() users = portal.solar.get_all_users_join_institutions()
inactive_users = portal.base.get_all_users_join_institutions(active=False) inactive_users = portal.solar.get_all_users_join_institutions(active=False)
solar_institutions = portal.solar.get_all_institutions() solar_institutions = portal.solar.get_all_institutions()
cooperation_history = portal.base.get_cooperation_history() cooperation_history = portal.solar.get_cooperation_history()
uploads = portal.solar.get_all_upload_history(-1) uploads = portal.solar.get_all_upload_history(-1)
if is_admin: if is_admin:
return render_template('solar-admin.html', users=users, user_cooperation_history=cooperation_history, return render_template('solar-admin.html', users=users, user_cooperation_history=cooperation_history,
institutions=solar_institutions, inactive_users=inactive_users, uploads=uploads) institutions=solar_institutions, inactive_users=inactive_users, uploads=uploads)
elif text.startswith('manage-institution/') or text == 'manage-institution': elif text.startswith('manage-institution/') or text == 'manage-institution':
if portal.base.is_institution_coordinator(current_user.id, current_user_institution.id): if portal.solar.is_institution_coordinator(current_user.id, current_user_institution.id):
solar_users = portal.base.get_all_active_users() solar_users = portal.solar.get_all_active_users()
institution_users = portal.base.get_all_active_institution_users(current_user_institution.id) institution_users = portal.solar.get_all_active_institution_users(current_user_institution.id)
return render_template('solar-manage-institution.html', users=solar_users, return render_template('solar-manage-institution.html', users=solar_users,
institution_users=institution_users) institution_users=institution_users)
return '', 404 return '', 404
@app.route('/solar/pogodbe', methods=['POST']) @app.route('/pogodbe', methods=['POST'])
@login_required @login_required
def solar_upload_contract(): def solar_upload_contract():
msg = upload_handler_solar.handle_contract_upload(request, current_user.get_id()) msg = upload_handler_solar.handle_contract_upload(request, current_user.get_id())
@ -394,11 +344,11 @@ def solar_upload_contract():
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/adduser', methods=['POST']) @app.route('/adduser', methods=['POST'])
@login_required @login_required
def solar_add_user(): def solar_add_user():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
name = request.form.get('name') name = request.form.get('name')
@ -418,7 +368,7 @@ def solar_add_user():
if len(email) > 100: if len(email) > 100:
flash('Predolg email naslov.') flash('Predolg email naslov.')
return redirect(redirect_url()) return redirect(redirect_url())
elif not re.search(portal.base.REGEX_EMAIL, email): elif not re.search(portal.solar.REGEX_EMAIL, email):
flash('Email napačnega formata.') flash('Email napačnega formata.')
return redirect(redirect_url()) return redirect(redirect_url())
@ -429,22 +379,22 @@ def solar_add_user():
flash('Predolgo geslo.') flash('Predolgo geslo.')
return redirect(redirect_url()) return redirect(redirect_url())
user = portal.base.get_user_obj_by_email(email) user = portal.solar.get_user_obj_by_email(email)
if user: if user:
#portal.base.undo_remove_user(user.id) #portal.solar.undo_remove_user(user.id)
flash('Uporabnik s tem emailom je že vnešen v sistem.') flash('Uporabnik s tem emailom je že vnešen v sistem.')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.register_new_user(name, email, password) portal.solar.register_new_user(name, email, password)
flash('Uporabnik je bil uspešno dodan.') flash('Uporabnik je bil uspešno dodan.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/activateuser', methods=['POST']) @app.route('/activateuser', methods=['POST'])
@login_required @login_required
def solar_activate_user(): def solar_activate_user():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('id') user_id = request.form.get('id')
@ -452,34 +402,34 @@ def solar_activate_user():
flash('Prazno polje za ID uporabnika.') flash('Prazno polje za ID uporabnika.')
return redirect(redirect_url()) return redirect(redirect_url())
rowcount = portal.base.activate_user(user_id) rowcount = portal.solar.activate_user(user_id)
if rowcount == 0: if rowcount == 0:
return '', 404 return '', 404
portal.base.send_user_activation_mail(user_id, upload_handler_solar.config) portal.solar.send_user_activation_mail(user_id, upload_handler_solar.config)
flash('Uporabnik je bil aktiviran.') flash('Uporabnik je bil aktiviran.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/forgotpass') @app.route('/forgotpass')
def solar_forgotpass(): def solar_forgotpass():
return render_template('solar-forgotpass.html') return render_template('solar-forgotpass.html')
@app.route('/solar/sendresetpass', methods=['POST']) @app.route('/sendresetpass', methods=['POST'])
def solar_sendresetpass(): def solar_sendresetpass():
email = request.form.get('email') email = request.form.get('email')
portal.base.send_resetpass_mail(email, upload_handler_solar.config) portal.solar.send_resetpass_mail(email, upload_handler_solar.config)
flash('Povezava za ponastavitev gesla je bila poslana na vpisan email naslov.') flash('Povezava za ponastavitev gesla je bila poslana na vpisan email naslov.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/resetpass/<token>') @app.route('/resetpass/<token>')
def solar_resetpass(token): def solar_resetpass(token):
user = portal.base.verify_reset_token(token, upload_handler_solar.config['APP_SECRET_KEY']) user = portal.solar.verify_reset_token(token, upload_handler_solar.config['APP_SECRET_KEY'])
if not user: if not user:
return '', 404 return '', 404
@ -487,42 +437,42 @@ def solar_resetpass(token):
return render_template('solar-resetpass.html', user=user, token=token) return render_template('solar-resetpass.html', user=user, token=token)
@app.route('/solar/resetpass/<token>', methods=['POST']) @app.route('/resetpass/<token>', methods=['POST'])
def solar_resetpass_post(token): def solar_resetpass_post(token):
new_password = request.form.get('new_password') new_password = request.form.get('new_password')
user = portal.base.verify_reset_token(token, upload_handler_solar.config['APP_SECRET_KEY']) user = portal.solar.verify_reset_token(token, upload_handler_solar.config['APP_SECRET_KEY'])
if not user: if not user:
return '', 404 return '', 404
rowcount = portal.base.update_user_password(user.id, new_password) rowcount = portal.solar.update_user_password(user.id, new_password)
if rowcount == 0: if rowcount == 0:
return '', 404 return '', 404
flash('Ponastavitev gesla je bila uspešna.') flash('Ponastavitev gesla je bila uspešna.')
return redirect('/solar/login') return redirect('/login')
@app.route('/solar/topuploads') @app.route('/topuploads')
@login_required @login_required
def solar_topuploads_srednje(): def solar_topuploads_srednje():
return jsonify(portal.solar.get_top_uploading_institutions()) return jsonify(portal.solar.get_top_uploading_institutions())
@app.route('/solar/deluser', methods=['POST']) @app.route('/deluser', methods=['POST'])
@login_required @login_required
def solar_del_user(): def solar_del_user():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('user_id') user_id = request.form.get('user_id')
portal.base.remove_user(user_id) portal.solar.remove_user(user_id)
flash('Uporabnik je bil odstranjen.') flash('Uporabnik je bil odstranjen.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/addinstitution', methods=['POST']) @app.route('/addinstitution', methods=['POST'])
@login_required @login_required
def add_institution(): def add_institution():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
name = request.form.get('name') name = request.form.get('name')
@ -539,15 +489,15 @@ def add_institution():
flash('Neveljavna vrednost za regijo.') flash('Neveljavna vrednost za regijo.')
return redirect(redirect_url()) return redirect(redirect_url())
institution_id = portal.base.add_institution(name, region) institution_id = portal.solar.add_institution(name, region)
portal.base.grant_institution_corpus_access(institution_id, "solar") # TODO: throw out portal.solar.grant_institution_corpus_access(institution_id, "solar") # TODO: throw out
flash('Institucija je bila dodana.') flash('Institucija je bila dodana.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/mergeinstitutions', methods=['POST']) @app.route('/mergeinstitutions', methods=['POST'])
@login_required @login_required
def merge_institutions(): def merge_institutions():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
id_from = request.form.get('id-from') id_from = request.form.get('id-from')
@ -557,8 +507,8 @@ def merge_institutions():
flash('Prazno polje.') flash('Prazno polje.')
return redirect(redirect_url()) return redirect(redirect_url())
institution_from = portal.base.get_institution_obj(id_from) institution_from = portal.solar.get_institution_obj(id_from)
institution_to = portal.base.get_institution_obj(id_to) institution_to = portal.solar.get_institution_obj(id_to)
if not institution_from: if not institution_from:
flash('Institucija z ID "{}" ne obstaja.'.format(id_from)) flash('Institucija z ID "{}" ne obstaja.'.format(id_from))
@ -569,18 +519,18 @@ def merge_institutions():
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.transfer_users_institution(institution_from.id, institution_to.id) portal.solar.transfer_users_institution(institution_from.id, institution_to.id)
portal.base.transfer_uploads_institution(institution_from.id, institution_to.id) portal.solar.transfer_uploads_institution(institution_from.id, institution_to.id)
portal.base.transfer_contracts_institution(institution_from.id, institution_to.id) portal.solar.transfer_contracts_institution(institution_from.id, institution_to.id)
portal.base.remove_institution(institution_from.id) portal.solar.remove_institution(institution_from.id)
flash('Instituciji uspešno združeni') flash('Instituciji uspešno združeni')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/addcooperationhistoryitem', methods=['POST']) @app.route('/addcooperationhistoryitem', methods=['POST'])
@login_required @login_required
def add_cooperation_history_item(): def add_cooperation_history_item():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('user') user_id = request.form.get('user')
@ -589,8 +539,8 @@ def add_cooperation_history_item():
school_year = request.form.get('school-year') school_year = request.form.get('school-year')
badge_text = request.form.get('badge-text') badge_text = request.form.get('badge-text')
user = portal.base.get_user_obj(user_id) user = portal.solar.get_user_obj(user_id)
institution = portal.base.get_institution_obj(institution_id) institution = portal.solar.get_institution_obj(institution_id)
if not user: if not user:
flash('Uporabnik s tem ID-jem ne obstaja.') flash('Uporabnik s tem ID-jem ne obstaja.')
@ -608,15 +558,15 @@ def add_cooperation_history_item():
flash('Šolsko leto mora biti formata "2021/22".') flash('Šolsko leto mora biti formata "2021/22".')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.add_cooperation_history_item(user_id, institution_id, role, school_year, badge_text) portal.solar.add_cooperation_history_item(user_id, institution_id, role, school_year, badge_text)
flash('Vnos dodan.') flash('Vnos dodan.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/updateuploaditem', methods=['POST']) @app.route('/updateuploaditem', methods=['POST'])
@login_required @login_required
def update_upload_item(): def update_upload_item():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
err_msg = portal.solar.UploadHandlerSolar.check_form(request.form) err_msg = portal.solar.UploadHandlerSolar.check_form(request.form)
@ -652,22 +602,22 @@ def update_upload_item():
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/delcooperationhistoryitem', methods=['POST']) @app.route('/delcooperationhistoryitem', methods=['POST'])
@login_required @login_required
def del_cooperation_history_item(): def del_cooperation_history_item():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
entry_id = request.form.get('entry-id') entry_id = request.form.get('entry-id')
portal.base.del_cooperation_history_item(entry_id) portal.solar.del_cooperation_history_item(entry_id)
flash('Vnos odstranjen.') flash('Vnos odstranjen.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/changeinstitutiondata', methods=['POST']) @app.route('/changeinstitutiondata', methods=['POST'])
@login_required @login_required
def change_institution_data(): def change_institution_data():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
institution_id = request.form.get('id') institution_id = request.form.get('id')
@ -685,34 +635,34 @@ def change_institution_data():
flash('Neveljavna vrednost za regijo.') flash('Neveljavna vrednost za regijo.')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.update_institution_data(institution_id, new_name, new_region) portal.solar.update_institution_data(institution_id, new_name, new_region)
flash('Podatki institucije so bili spremenjeni.') flash('Podatki institucije so bili spremenjeni.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/changeuseremail', methods=['POST']) @app.route('/changeuseremail', methods=['POST'])
@login_required @login_required
def change_user_email(): def change_user_email():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('user-id') user_id = request.form.get('user-id')
email = request.form.get('email') email = request.form.get('email')
if not re.search(portal.base.REGEX_EMAIL, email): if not re.search(portal.solar.REGEX_EMAIL, email):
flash('Email napačnega formata.') flash('Email napačnega formata.')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.update_user_email(user_id, email) portal.solar.update_user_email(user_id, email)
flash('Email spremenjen.') flash('Email spremenjen.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/changeuserrole', methods=['POST']) @app.route('/changeuserrole', methods=['POST'])
@login_required @login_required
def change_user_role(): def change_user_role():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('user-id') user_id = request.form.get('user-id')
@ -722,38 +672,35 @@ def change_user_role():
flash('Neveljavna vloga.') flash('Neveljavna vloga.')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.update_user_role(user_id, role) portal.solar.update_user_role(user_id, role)
flash('Vloga spremenjena.') flash('Vloga spremenjena.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/changeusername', methods=['POST']) @app.route('/changeusername', methods=['POST'])
@login_required @login_required
def change_user_name(): def change_user_name():
if not portal.base.is_admin(current_user.id): if not portal.solar.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('user-id') user_id = request.form.get('user-id')
name = request.form.get('name') name = request.form.get('name')
portal.base.update_user_name(user_id, name) portal.solar.update_user_name(user_id, name)
flash('Ime in priimek spremenjena.') flash('Ime in priimek spremenjena.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/<corpus_name>/addusertoinstitution', methods=['POST']) @app.route('/addusertoinstitution', methods=['POST'])
@login_required @login_required
def add_user_institution_mapping(corpus_name): def add_user_institution_mapping():
if not corpus_name in ENABLED_CORPUSES:
return '', 404
institution_id = request.form.get('institution_id') institution_id = request.form.get('institution_id')
if not institution_id: if not institution_id:
institution = portal.base.get_user_institution(current_user.id) institution = portal.solar.get_user_institution(current_user.id)
if institution: if institution:
institution_id = institution.id institution_id = institution.id
if not (portal.base.is_admin(current_user.id) or portal.base.is_institution_coordinator(current_user.id, institution_id)): if not (portal.solar.is_admin(current_user.id) or portal.solar.is_institution_coordinator(current_user.id, institution_id)):
return '', 404 return '', 404
user_id = request.form['user_id'] user_id = request.form['user_id']
@ -761,54 +708,37 @@ def add_user_institution_mapping(corpus_name):
if role not in ['coordinator', 'mentor', 'other']: if role not in ['coordinator', 'mentor', 'other']:
return '', 404 return '', 404
if portal.base.get_user_institution(user_id): if portal.solar.get_user_institution(user_id):
flash('Uporabnik je že dodeljen instituciji.') flash('Uporabnik je že dodeljen instituciji.')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.add_user_to_institution(user_id, institution_id, role) portal.solar.add_user_to_institution(user_id, institution_id, role)
flash('Uporabnik je bil dodeljen instituciji.') flash('Uporabnik je bil dodeljen instituciji.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/deluserfrominstitution', methods=['POST']) @app.route('/deluserfrominstitution', methods=['POST'])
@login_required @login_required
def del_user_institution_mapping(): def del_user_institution_mapping():
user_id = request.form['user_id'] user_id = request.form['user_id']
institution = portal.base.get_user_institution(user_id) institution = portal.solar.get_user_institution(user_id)
if not institution: if not institution:
flash('Uporabnik ni član nobene institucije.') flash('Uporabnik ni član nobene institucije.')
return redirect(redirect_url()) return redirect(redirect_url())
if not portal.base.is_admin(current_user.id) \ if not portal.solar.is_admin(current_user.id) \
and not portal.base.is_institution_coordinator(current_user.id, institution.id): and not portal.solar.is_institution_coordinator(current_user.id, institution.id):
flash('Nimate ustreznih pravic za odstranitev uporabnika iz institucije.') flash('Nimate ustreznih pravic za odstranitev uporabnika iz institucije.')
return redirect(redirect_url()) return redirect(redirect_url())
portal.base.del_user_from_institution(user_id, institution.id) portal.solar.del_user_from_institution(user_id, institution.id)
flash('Uporabnik je bil odstranjen iz institucije.') flash('Uporabnik je bil odstranjen iz institucije.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/<corpus_name>/delinstitution', methods=['POST']) @app.route('/upload', methods=['POST'])
@login_required def handle_upload():
def del_institution(corpus_name): if not current_user.is_authenticated:
# TODO: check if valid corpus_name
# TODO: check if user is admin
# TODO: delete cascade - institution, user_institution_mapping, corpus_access, institution_contract
return '', 404
@app.route('/<corpus_name>/upload', methods=['POST'])
def handle_upload(corpus_name):
if corpus_name not in ENABLED_CORPUSES:
return '', 404 return '', 404
return upload_handler_solar.handle_upload(request, current_user.get_id())
if corpus_name == 'solar':
if not current_user.is_authenticated:
return '', 404
#if not portal.base.has_user_corpus_access(current_user.id, corpus_name):
# return '', 404
return upload_handler_solar.handle_upload(request, current_user.get_id())
else:
return upload_handler_regular.handle_upload(request, corpus_name)
if __name__ == '__main__': if __name__ == '__main__':

@ -0,0 +1,41 @@
"""Removed non-solar stuff.
Revision ID: 44dae32b13af
Revises: 84168f439c55
Create Date: 2021-10-05 13:22:23.539298
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '44dae32b13af'
down_revision = '84168f439c55'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('upload_regular')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('upload_regular',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('upload_hash', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('timestamp', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('org', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('address', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('zipcode', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('email', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('file_contract', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('upload_file_hashes', postgresql.ARRAY(sa.VARCHAR()), autoincrement=False, nullable=True),
sa.Column('corpus_name', sa.TEXT(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint('id', name='upload_regular_pkey')
)
# ### end Alembic commands ###

@ -1,604 +0,0 @@
import os
import hashlib
import time
import ssl
import traceback
import re
import logging
from pathlib import Path
from datetime import datetime
import imaplib
from smtplib import SMTP_SSL
import email
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
import pdfkit
from jinja2 import Environment, FileSystemLoader
import jwt
from werkzeug.security import generate_password_hash
from . model import *
#REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$')
REGEX_EMAIL = re.compile('^(?:[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$')
MAX_FNAME_LEN = 100
class ContractCreator:
def __init__(self, base_path, template_path):
self.base = base_path
template_loader = FileSystemLoader(searchpath="./")
template_env = Environment(loader=template_loader)
self.template = template_env.get_template(template_path)
self.pdfkit_options = {
'page-size': 'A4',
'margin-top': '0.75in',
'margin-right': '0.75in',
'margin-bottom': '0.75in',
'margin-left': '0.75in',
'encoding': "UTF-8",
'custom-header' : [
('Accept-Encoding', 'gzip')
]
}
def fill_template(self, **kwargs):
return self.template.render(**kwargs)
def create_pdf(self, f_name, fields_dict):
sub_dir = self.base / Path(f_name[:2])
if not sub_dir.exists():
sub_dir.mkdir()
out_f = sub_dir / Path(f_name[2:])
html_str = self.fill_template(**fields_dict)
pdfkit.from_string(html_str, out_f, options=self.pdfkit_options)
class UploadHandler:
ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'xml', 'mxliff', 'tmx', 'jpg', 'jpeg', 'png']
def __init__(self, **kwargs):
self.config = kwargs
def set_contract_creator(self, contract_creator):
assert isinstance(contract_creator, ContractCreator)
self._contract_creator = contract_creator
def get_uploads_subdir(self, dir_name):
subdir = Path(self.config['UPLOADS_DIR']) / dir_name
if not subdir.exists():
subdir.mkdir(parents=True)
return subdir
@staticmethod
def extract_upload_metadata(corpus_name, request):
upload_metadata = dict()
file_hashes = UploadHandler.create_file_hashes(request.files)
file_names = file_hashes.keys()
form_data = request.form.copy()
upload_timestamp = int(time.time())
upload_id = UploadHandler.create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes)
# Strip form fieds.
for key, val in form_data.items():
form_data[key] = val.strip()
upload_metadata['corpus_name'] = corpus_name
upload_metadata['form_data'] = form_data
upload_metadata['upload_id'] = upload_id
upload_metadata['timestamp'] = upload_timestamp
upload_metadata['file_hashes_dict'] = file_hashes
upload_metadata['file_names'] = file_names
upload_metadata['contract_file'] = upload_id + '.pdf'
return upload_metadata
@staticmethod
def create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes):
# Order is important while hashing, hence the sorting.
val_buff = [str(upload_timestamp)]
for key in sorted(form_data):
val_buff.append(form_data[key])
# This hash serves as an unique identifier for the whole upload.
metahash = hashlib.md5((''.join(val_buff)).encode())
# Include file hashes to avoid metafile name collisions if they have the same form values,
# but different data files. Sort hashes first so upload order doesn't matter.
sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort()
metahash.update(''.join(sorted_f_hashes).encode())
metahash = metahash.hexdigest()
return metahash
@staticmethod
def create_file_hashes(files):
res = dict()
for key, f in files.items():
if key.startswith('file'):
h = hashlib.md5(f.filename.encode())
h.update(f.stream.read())
res[f.filename] = h.hexdigest()
f.seek(0)
return res
@staticmethod
def store_model(model_obj):
try:
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def store_datafiles(self, files, upload_metadata):
base = self.get_uploads_subdir('files')
file_hashes = upload_metadata['file_hashes_dict']
for key, f in files.items():
if key.startswith('file'):
f_hash = file_hashes[f.filename]
# First byte used for indexing, similarly like git does for example.
sub_dir = base / f_hash[:2]
if not sub_dir.exists():
sub_dir.mkdir()
path = sub_dir / f_hash[2:]
if not path.exists():
path.mkdir()
f.save(path / f.filename)
def send_confirm_mail(self, upload_metadata, attach_contract_file=False):
upload_id = upload_metadata['upload_id']
message = MIMEMultipart()
message['From'] = self.config['MAIL_LOGIN']
message['To'] = upload_metadata['form_data']['email']
message['Subject'] = self.config['MAIL_SUBJECT'].format(upload_id=upload_id)
body = self.config['MAIL_BODY'].format(upload_id=upload_id)
message.attach(MIMEText(body, "plain"))
if attach_contract_file:
contracts_dir = self.contract_creator.base
f_name = upload_metadata['contract_file']
sub_dir = contracts_dir / Path(f_name[:2])
contract_file = sub_dir / Path(f_name[2:])
with open(contract_file, "rb") as f:
part = MIMEApplication(
f.read(),
Name = f_name
)
part['Content-Disposition'] = 'attachment; filename="%s"' % f_name
message.attach(part)
text = message.as_string()
# Create a secure SSL context
context = ssl.create_default_context()
# TODO: Implement timeout.
try:
with SMTP_SSL(self.config['MAIL_HOST'], self.config['SMTP_PORT'], context=context) as server:
server.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
server.sendmail(message['From'], message['To'], text)
# Save copy of sent mail in Sent mailbox
#imap = imaplib.IMAP4_SSL(self.config['MAIL_HOST'], self.config['IMAP_PORT'])
#imap.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
#imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
#imap.logout()
except Exception:
traceback.print_exc()
def check_suffixes(self, files):
for key, f in files.items():
if key.startswith('file'):
suffix = f.filename.split('.')[-1]
if self.ENABLED_FILETYPES and suffix.lower() not in self.ENABLED_FILETYPES:
return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename)
return None
@staticmethod
def check_fname_lengths(files):
for key, f in files.items():
if key.startswith('file'):
if len(f.filename) > MAX_FNAME_LEN:
return 'Ime datoteke presega dolžino {} znakov.'.format(MAX_FNAME_LEN)
return None
def check_upload_request(self, request):
files = request.files
max_files = self.config['MAX_FILES_PER_UPLOAD']
if len(files) > max_files:
return 'Naložite lahko do {} datotek hkrati.'.format(max_files), 400
elif len(files) < 1:
return 'Priložena ni bila nobena datoteka.', 400
err = self.check_suffixes(files)
if err:
return err, 400
err = UploadHandler.check_fname_lengths(files)
if err:
return err, 400
return None
def get_user_institution(user_id):
mapping = UserInstitutionMapping.query.filter_by(user=user_id).first()
if mapping:
return Institution.query.filter_by(id=mapping.institution).first()
return None
def get_institution_contract(institution_id):
return InstitutionContract.query.filter_by(institution=institution_id).first()
def get_institution_cooperation_history(institution_id):
#return CooperationToken.query.join(UserCooperationTokenMapping,
# UserCooperationTokenMapping.cooperation_token == CooperationToken.id).filter(UserCooperationTokenMapping.user == user_id).all()
#
res = dict()
uch_rows = UserCooperationHistory.query.filter_by(institution=institution_id).order_by(UserCooperationHistory.school_year.desc()).all()
for row in uch_rows:
if row.user not in res:
res[row.user] = {
'coordinator': [],
'mentor': [],
'other': []
}
res[row.user][row.role].append((row.school_year, row.badge_text))
return res
def get_cooperation_history():
return UserCooperationHistory.query.all()
def add_cooperation_history_item(user_id, institution_id, role, school_year, badge_text):
model_obj = UserCooperationHistory(
user=user_id,
institution=institution_id,
role=role,
school_year=school_year,
badge_text=badge_text
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def del_cooperation_history_item(entry_id):
db.session.query(UserCooperationHistory).filter_by(id=entry_id).delete()
db.session.commit()
def has_user_corpus_access(user_id, corpus_name):
user = RegisteredUser.query.filter_by(id=user_id).first()
# TODO: check if user even is active?
# Admins always have access to everything.
if user.role == 'admin':
return True
# Check if user belongs to an institution, that has access to this corpus.
institution = get_user_institution(user_id)
has_access = False
row = CorpusAccess.query.filter_by(institution=institution.id, corpus=corpus_name).first()
if row:
has_access = True
return has_access
def is_admin(user_id):
user = RegisteredUser.query.filter_by(id=user_id).first()
if user.role == 'admin':
return True
return False
def get_user_obj(user_id):
return RegisteredUser.query.filter_by(id=user_id).first()
def get_user_obj_by_email(email):
return RegisteredUser.query.filter_by(email=email).first()
def get_institution_obj(institution_id):
return Institution.query.filter_by(id=institution_id).first()
def get_institution_obj_by_name(institution_name):
return Institution.query.filter_by(name=institution_name).first()
def register_new_user(name, email, password, active=True, admin=False):
model_obj = RegisteredUser(
name=name,
email=email,
role='admin' if admin else 'user',
pass_hash=generate_password_hash(password),
active=active,
registered=datetime.now()
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def add_institution(name, region):
model_obj = Institution(
name=name,
region=region
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def grant_institution_corpus_access(institution_id, corpus_name):
model_obj = CorpusAccess(
institution=institution_id,
corpus=corpus_name
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def add_user_to_institution(user_id, institution_id, role):
model_obj = UserInstitutionMapping(
user=user_id,
institution=institution_id,
role=role
)
db.session.add(model_obj)
db.session.commit()
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': phash})
db.session.commit()
return rowcount
def update_user_role(user_id, role):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'role': role})
db.session.commit()
return rowcount
def update_user_email(user_id, new_email):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'email': new_email})
db.session.commit()
return rowcount
def update_user_name(user_id, new_name):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'name': new_name})
db.session.commit()
return rowcount
def update_institution_data(institution_id, new_name, new_region):
rowcount = db.session.query(Institution).filter_by(id=institution_id).update({'name': new_name, 'region': new_region})
db.session.commit()
return rowcount
def remove_user(user_id):
db.session.query(UserCooperationHistory).filter(UserCooperationHistory.user == user_id).delete()
db.session.query(UserInstitutionMapping).filter(UserInstitutionMapping.user == user_id).delete()
db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).delete()
db.session.commit()
#db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).update({'is_removed': True})
#db.session.commit()
#def undo_remove_user(user_id):
# db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).update({'is_removed': False})
# db.session.commit()
def remove_institution(institution_id):
db.session.query(CorpusAccess).filter(CorpusAccess.institution == institution_id).delete()
db.session.query(Institution).filter(Institution.id == institution_id).delete()
db.session.commit()
#db.session.query(Institution).filter(Institution.id == institution_id).update({'is_removed': True})
#db.session.commit()
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():
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_users_join_institutions(active=True):
#return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all()
return db.session.query(RegisteredUser, UserInstitutionMapping).outerjoin(UserInstitutionMapping,
RegisteredUser.id == UserInstitutionMapping.user).filter(RegisteredUser.active == active).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_coordinator(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
if user_inst_mapping.role != 'coordinator':
return False
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_actual_institution_contract_filename(f_hash):
contract = InstitutionContract.query.filter_by(file_contract=f_hash).first()
if contract:
return contract.original_filename
return None
def get_actual_studentparent_contract_filename(f_hash):
contract = ContractsSolar.query.filter_by(file_contract=f_hash).first()
if contract:
return contract.original_filename
return None
def get_password_reset_token(email, key, expires=600):
return jwt.encode({'reset_password': email,
'exp': int(time.time()) + expires},
key=key, algorithm='HS256')
def transfer_users_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(UserInstitutionMapping).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def transfer_uploads_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(UploadSolar).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def transfer_contracts_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(ContractsSolar).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def verify_reset_token(token, key):
try:
message = jwt.decode(token,
key=key, algorithms=["HS256"])
email = message['reset_password']
exp = message['exp']
if int(time.time()) >= exp:
# Token timed out
return None
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, config['APP_SECRET_KEY'])
body = '''
Zahtevali ste ponastavitev gesla vašega uporabniškega računa.
Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format(jwt_token)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
# 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()
def send_admins_new_user_notification_mail(user_id, config):
user = RegisteredUser.query.filter_by(id=user_id).first()
body = '''
Nov uporabnik "{}" je ustvaril uporabniški račun na portalu za oddajanje besedil Šolar in čaka na odobritev.
'''.format(user.name)
admins = RegisteredUser.query.filter_by(role="admin").all()
# Create a secure SSL context
context = ssl.create_default_context()
for admin in admins:
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = admin.email
message['Subject'] = 'Nova registracija'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
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'], admin.email, text)
except Exception:
traceback.print_exc()
def send_user_activation_mail(user_id, config):
user = RegisteredUser.query.filter_by(id=user_id).first()
body = '''Vaš uporabniški račun "{}" na portalu Šolar je bil odobren.'''.format(user.name)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = user.email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
# 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'], user.email, text)
except Exception:
traceback.print_exc()

@ -12,22 +12,6 @@ from flask_login import UserMixin
db = SQLAlchemy() db = SQLAlchemy()
# "prevodi" or "gigafida".
class UploadRegular(db.Model):
__tablename__ = 'upload_regular'
id = db.Column(db.Integer, primary_key=True)
upload_hash = db.Column(db.String, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
name = db.Column(db.String, nullable=False)
org = db.Column(db.String, nullable=True)
address = db.Column(db.String, nullable=True)
zipcode = db.Column(db.String, nullable=True)
email = db.Column(db.String, nullable=False)
file_contract = db.Column(db.String, nullable=True)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String), nullable=True)
corpus_name = db.Column(db.String, nullable=False)
class UploadSolar(db.Model): class UploadSolar(db.Model):
__tablename__ = 'upload_solar' __tablename__ = 'upload_solar'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -78,18 +62,6 @@ class UserInstitutionMapping(db.Model):
role = db.Column(db.String, nullable=False) role = db.Column(db.String, nullable=False)
#class CooperationToken(db.Model):
# __tablename__ = 'cooperation_token'
# id = db.Column(db.Integer, primary_key=True)
# name = db.Column(db.String, nullable=False)
#
#
#class UserCooperationTokenMapping(db.Model):
# __tablename__ = 'user_cooperation_token_mapping'
# id = db.Column(db.Integer, primary_key=True)
# user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=False)
# cooperation_token = db.Column(db.Integer, sqlalchemy.ForeignKey('cooperation_token.id'), nullable=False)
class UserCooperationHistory(db.Model): class UserCooperationHistory(db.Model):
__tablename__ = 'user_cooperation' __tablename__ = 'user_cooperation'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

@ -1,130 +0,0 @@
import logging
import re
import traceback
from datetime import datetime
from portal.base import UploadHandler, ContractCreator, REGEX_EMAIL
from portal.model import db, UploadRegular
class UploadHandlerRegular(UploadHandler):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.contract_creator = ContractCreator(base_path=self.get_uploads_subdir('contracts'),
template_path='contract/regular.html')
def generate_upload_contract_pdf(self, upload_metadata):
base = self.get_uploads_subdir('contracts')
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'],
'naslov': form_data.get('naslov', ''),
'posta': form_data.get('posta', ''),
'kontakt_narocnik': self.config['CONTRACT_CLIENT_CONTACT'],
'kontakt_imetnikpravic': 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, corpus_name):
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()
try:
model_obj = UploadRegular(
upload_hash=upload_metadata['upload_id'],
timestamp=timestamp,
name=form_data['ime'],
org=form_data.get('podjetje'),
address=form_data.get('naslov'),
zipcode=form_data.get('posta'),
email=form_data['email'],
file_contract=upload_metadata['contract_file'],
upload_file_hashes=sorted_f_hashes,
corpus_name=corpus_name
)
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def handle_upload(self, request, corpus_name):
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(corpus_name, request)
logging.info('Upload with id "{}" supplied form data: {}'.format(upload_metadata['upload_id'],
str(upload_metadata['form_data'])))
# Generate contract PDF file based on the uploads metadata.
self.generate_upload_contract_pdf(upload_metadata)
# Store uploaded files to disk.
self.store_datafiles(request.files, upload_metadata)
# Store metadata to database.
self.store_metadata(upload_metadata, corpus_name)
# Send confirmation mail along with the contract to the submitted email address.
self.send_confirm_mail(upload_metadata, attach_contract_file=True)
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files))
@staticmethod
def check_form(form):
ime = form.get('ime')
podjetje = form.get('podjetje')
naslov = form.get('naslov')
posta = form.get('posta')
email = form.get('email')
telefon = form.get('telefon')
if not ime:
return 'Prazno polje za ime.'
if len(ime) > 100:
return 'Predolgo ime.'
if podjetje and len(podjetje) > 100:
return 'Predolgo ime institucije.'
if not email:
return 'Prazno polje za elektronsko pošto.'
if len(email) > 100:
return 'Predolgi email naslov'
elif not re.search(REGEX_EMAIL, email):
return 'Email napačnega formata.'
if telefon and len(telefon) > 100:
return 'Predolga telefonska št.'
if naslov and len(naslov) > 100:
return 'Predolg naslov.'
if posta and len(posta) > 100:
return 'Predolga pošta'
return None

@ -1,11 +1,31 @@
import logging import logging
import re import re
import hashlib import hashlib
import time
import traceback
import ssl
from datetime import datetime from datetime import datetime
from sqlalchemy import desc from sqlalchemy import desc
from collections import Counter from collections import Counter
from pathlib import Path
import imaplib
from smtplib import SMTP_SSL
import email
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
import pdfkit
from jinja2 import Environment, FileSystemLoader
import jwt
from werkzeug.security import generate_password_hash
from portal.base import UploadHandler, get_user_institution, has_user_corpus_access
from . model import * from . model import *
@ -15,10 +35,119 @@ VALID_TEXT_TYPES = {'esej-spis', 'prakticno', 'solski-test', 'delo-v-razredu'}
VALID_GRAMMAR_CORRECTIONS = {'popr-ne', 'brez-popr', 'popr-da'} VALID_GRAMMAR_CORRECTIONS = {'popr-ne', 'brez-popr', 'popr-da'}
VALID_REGIONS = {'CE', 'GO', 'KK', 'KP', 'KR', 'LJ', 'MB', 'MS', 'NM', 'PO', 'SG'} VALID_REGIONS = {'CE', 'GO', 'KK', 'KP', 'KR', 'LJ', 'MB', 'MS', 'NM', 'PO', 'SG'}
#REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$')
REGEX_EMAIL = re.compile('^(?:[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$')
MAX_FNAME_LEN = 100
MAXLEN_FORM = 150 MAXLEN_FORM = 150
class ContractCreator:
def __init__(self, base_path, template_path):
self.base = base_path
template_loader = FileSystemLoader(searchpath="./")
template_env = Environment(loader=template_loader)
self.template = template_env.get_template(template_path)
self.pdfkit_options = {
'page-size': 'A4',
'margin-top': '0.75in',
'margin-right': '0.75in',
'margin-bottom': '0.75in',
'margin-left': '0.75in',
'encoding': "UTF-8",
'custom-header' : [
('Accept-Encoding', 'gzip')
]
}
def fill_template(self, **kwargs):
return self.template.render(**kwargs)
def create_pdf(self, f_name, fields_dict):
sub_dir = self.base / Path(f_name[:2])
if not sub_dir.exists():
sub_dir.mkdir()
out_f = sub_dir / Path(f_name[2:])
html_str = self.fill_template(**fields_dict)
pdfkit.from_string(html_str, out_f, options=self.pdfkit_options)
class UploadHandlerSolar():
def __init__(self, **kwargs):
self.config = kwargs
def set_contract_creator(self, contract_creator):
assert isinstance(contract_creator, ContractCreator)
self._contract_creator = contract_creator
def get_uploads_subdir(self, dir_name):
subdir = Path(self.config['UPLOADS_DIR']) / dir_name
if not subdir.exists():
subdir.mkdir(parents=True)
return subdir
@staticmethod
def create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes):
# Order is important while hashing, hence the sorting.
val_buff = [str(upload_timestamp)]
for key in sorted(form_data):
val_buff.append(form_data[key])
# This hash serves as an unique identifier for the whole upload.
metahash = hashlib.md5((''.join(val_buff)).encode())
# Include file hashes to avoid metafile name collisions if they have the same form values,
# but different data files. Sort hashes first so upload order doesn't matter.
sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort()
metahash.update(''.join(sorted_f_hashes).encode())
metahash = metahash.hexdigest()
return metahash
@staticmethod
def extract_upload_metadata(corpus_name, request):
upload_metadata = dict()
file_hashes = UploadHandlerSolar.create_file_hashes(request.files)
file_names = file_hashes.keys()
form_data = request.form.copy()
upload_timestamp = int(time.time())
upload_id = UploadHandlerSolar.create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes)
# Strip form fieds.
for key, val in form_data.items():
form_data[key] = val.strip()
upload_metadata['corpus_name'] = corpus_name
upload_metadata['form_data'] = form_data
upload_metadata['upload_id'] = upload_id
upload_metadata['timestamp'] = upload_timestamp
upload_metadata['file_hashes_dict'] = file_hashes
upload_metadata['file_names'] = file_names
upload_metadata['contract_file'] = upload_id + '.pdf'
return upload_metadata
@staticmethod
def store_model(model_obj):
try:
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
class UploadHandlerSolar(UploadHandler): @staticmethod
def create_file_hashes(files):
res = dict()
for key, f in files.items():
if key.startswith('file'):
h = hashlib.md5(f.filename.encode())
h.update(f.stream.read())
res[f.filename] = h.hexdigest()
f.seek(0)
return res
@staticmethod @staticmethod
def store_metadata(upload_metadata, user_id): def store_metadata(upload_metadata, user_id):
@ -45,7 +174,25 @@ class UploadHandlerSolar(UploadHandler):
grammar_corrections=form_data['jezikovni-popravki'], grammar_corrections=form_data['jezikovni-popravki'],
upload_file_hashes=sorted_f_hashes upload_file_hashes=sorted_f_hashes
) )
UploadHandler.store_model(model_obj) UploadHandlerSolar.store_model(model_obj)
def store_datafiles(self, files, upload_metadata):
base = self.get_uploads_subdir('files')
file_hashes = upload_metadata['file_hashes_dict']
for key, f in files.items():
if key.startswith('file'):
f_hash = file_hashes[f.filename]
# First byte used for indexing, similarly like git does for example.
sub_dir = base / f_hash[:2]
if not sub_dir.exists():
sub_dir.mkdir()
path = sub_dir / f_hash[2:]
if not path.exists():
path.mkdir()
f.save(path / f.filename)
def handle_upload(self, request, user_id): def handle_upload(self, request, user_id):
err = self.check_upload_request(request) err = self.check_upload_request(request)
@ -159,6 +306,83 @@ class UploadHandlerSolar(UploadHandler):
if len(val) > MAXLEN_FORM: if len(val) > MAXLEN_FORM:
return 'Value in form field "{}" exceeds max len of {}'.format(key, MAXLEN_FORM) return 'Value in form field "{}" exceeds max len of {}'.format(key, MAXLEN_FORM)
def send_confirm_mail(self, upload_metadata, attach_contract_file=False):
upload_id = upload_metadata['upload_id']
message = MIMEMultipart()
message['From'] = self.config['MAIL_LOGIN']
message['To'] = upload_metadata['form_data']['email']
message['Subject'] = self.config['MAIL_SUBJECT'].format(upload_id=upload_id)
body = self.config['MAIL_BODY'].format(upload_id=upload_id)
message.attach(MIMEText(body, "plain"))
if attach_contract_file:
contracts_dir = self.contract_creator.base
f_name = upload_metadata['contract_file']
sub_dir = contracts_dir / Path(f_name[:2])
contract_file = sub_dir / Path(f_name[2:])
with open(contract_file, "rb") as f:
part = MIMEApplication(
f.read(),
Name = f_name
)
part['Content-Disposition'] = 'attachment; filename="%s"' % f_name
message.attach(part)
text = message.as_string()
# Create a secure SSL context
context = ssl.create_default_context()
# TODO: Implement timeout.
try:
with SMTP_SSL(self.config['MAIL_HOST'], self.config['SMTP_PORT'], context=context) as server:
server.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
server.sendmail(message['From'], message['To'], text)
# Save copy of sent mail in Sent mailbox
#imap = imaplib.IMAP4_SSL(self.config['MAIL_HOST'], self.config['IMAP_PORT'])
#imap.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
#imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
#imap.logout()
except Exception:
traceback.print_exc()
def check_suffixes(self, files):
for key, f in files.items():
if key.startswith('file'):
suffix = f.filename.split('.')[-1]
if self.ENABLED_FILETYPES and suffix.lower() not in self.ENABLED_FILETYPES:
return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename)
return None
@staticmethod
def check_fname_lengths(files):
for key, f in files.items():
if key.startswith('file'):
if len(f.filename) > MAX_FNAME_LEN:
return 'Ime datoteke presega dolžino {} znakov.'.format(MAX_FNAME_LEN)
return None
def check_upload_request(self, request):
files = request.files
max_files = self.config['MAX_FILES_PER_UPLOAD']
if len(files) > max_files:
return 'Naložite lahko do {} datotek hkrati.'.format(max_files), 400
elif len(files) < 1:
return 'Priložena ni bila nobena datoteka.', 400
err = self.check_suffixes(files)
if err:
return err, 400
err = UploadHandlerSolar.check_fname_lengths(files)
if err:
return err, 400
return None
def get_upload_history(user_id, n=20): def get_upload_history(user_id, n=20):
if n == -1: if n == -1:
@ -231,3 +455,365 @@ def update_upload_item(item_id, program, subject, subject_custom, grade, text_ty
}) })
db.session.commit() db.session.commit()
return rowcount return rowcount
def get_user_institution(user_id):
mapping = UserInstitutionMapping.query.filter_by(user=user_id).first()
if mapping:
return Institution.query.filter_by(id=mapping.institution).first()
return None
def get_institution_contract(institution_id):
return InstitutionContract.query.filter_by(institution=institution_id).first()
def get_institution_cooperation_history(institution_id):
#return CooperationToken.query.join(UserCooperationTokenMapping,
# UserCooperationTokenMapping.cooperation_token == CooperationToken.id).filter(UserCooperationTokenMapping.user == user_id).all()
#
res = dict()
uch_rows = UserCooperationHistory.query.filter_by(institution=institution_id).order_by(UserCooperationHistory.school_year.desc()).all()
for row in uch_rows:
if row.user not in res:
res[row.user] = {
'coordinator': [],
'mentor': [],
'other': []
}
res[row.user][row.role].append((row.school_year, row.badge_text))
return res
def get_cooperation_history():
return UserCooperationHistory.query.all()
def add_cooperation_history_item(user_id, institution_id, role, school_year, badge_text):
model_obj = UserCooperationHistory(
user=user_id,
institution=institution_id,
role=role,
school_year=school_year,
badge_text=badge_text
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def del_cooperation_history_item(entry_id):
db.session.query(UserCooperationHistory).filter_by(id=entry_id).delete()
db.session.commit()
def has_user_corpus_access(user_id, corpus_name):
user = RegisteredUser.query.filter_by(id=user_id).first()
# TODO: check if user even is active?
# Admins always have access to everything.
if user.role == 'admin':
return True
# Check if user belongs to an institution, that has access to this corpus.
institution = get_user_institution(user_id)
has_access = False
row = CorpusAccess.query.filter_by(institution=institution.id, corpus=corpus_name).first()
if row:
has_access = True
return has_access
def is_admin(user_id):
user = RegisteredUser.query.filter_by(id=user_id).first()
if user.role == 'admin':
return True
return False
def get_user_obj(user_id):
return RegisteredUser.query.filter_by(id=user_id).first()
def get_user_obj_by_email(email):
return RegisteredUser.query.filter_by(email=email).first()
def get_institution_obj(institution_id):
return Institution.query.filter_by(id=institution_id).first()
def get_institution_obj_by_name(institution_name):
return Institution.query.filter_by(name=institution_name).first()
def register_new_user(name, email, password, active=True, admin=False):
model_obj = RegisteredUser(
name=name,
email=email,
role='admin' if admin else 'user',
pass_hash=generate_password_hash(password),
active=active,
registered=datetime.now()
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def add_institution(name, region):
model_obj = Institution(
name=name,
region=region
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def grant_institution_corpus_access(institution_id, corpus_name):
model_obj = CorpusAccess(
institution=institution_id,
corpus=corpus_name
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def add_user_to_institution(user_id, institution_id, role):
model_obj = UserInstitutionMapping(
user=user_id,
institution=institution_id,
role=role
)
db.session.add(model_obj)
db.session.commit()
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': phash})
db.session.commit()
return rowcount
def update_user_role(user_id, role):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'role': role})
db.session.commit()
return rowcount
def update_user_email(user_id, new_email):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'email': new_email})
db.session.commit()
return rowcount
def update_user_name(user_id, new_name):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'name': new_name})
db.session.commit()
return rowcount
def update_institution_data(institution_id, new_name, new_region):
rowcount = db.session.query(Institution).filter_by(id=institution_id).update({'name': new_name, 'region': new_region})
db.session.commit()
return rowcount
def remove_user(user_id):
db.session.query(UserCooperationHistory).filter(UserCooperationHistory.user == user_id).delete()
db.session.query(UserInstitutionMapping).filter(UserInstitutionMapping.user == user_id).delete()
db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).delete()
db.session.commit()
#db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).update({'is_removed': True})
#db.session.commit()
#def undo_remove_user(user_id):
# db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).update({'is_removed': False})
# db.session.commit()
def remove_institution(institution_id):
db.session.query(CorpusAccess).filter(CorpusAccess.institution == institution_id).delete()
db.session.query(Institution).filter(Institution.id == institution_id).delete()
db.session.commit()
#db.session.query(Institution).filter(Institution.id == institution_id).update({'is_removed': True})
#db.session.commit()
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():
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_users_join_institutions(active=True):
#return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all()
return db.session.query(RegisteredUser, UserInstitutionMapping).outerjoin(UserInstitutionMapping,
RegisteredUser.id == UserInstitutionMapping.user).filter(RegisteredUser.active == active).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_coordinator(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
if user_inst_mapping.role != 'coordinator':
return False
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_actual_institution_contract_filename(f_hash):
contract = InstitutionContract.query.filter_by(file_contract=f_hash).first()
if contract:
return contract.original_filename
return None
def get_actual_studentparent_contract_filename(f_hash):
contract = ContractsSolar.query.filter_by(file_contract=f_hash).first()
if contract:
return contract.original_filename
return None
def get_password_reset_token(email, key, expires=600):
return jwt.encode({'reset_password': email,
'exp': int(time.time()) + expires},
key=key, algorithm='HS256')
def transfer_users_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(UserInstitutionMapping).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def transfer_uploads_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(UploadSolar).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def transfer_contracts_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(ContractsSolar).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def verify_reset_token(token, key):
try:
message = jwt.decode(token,
key=key, algorithms=["HS256"])
email = message['reset_password']
exp = message['exp']
if int(time.time()) >= exp:
# Token timed out
return None
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, config['APP_SECRET_KEY'])
body = '''
Zahtevali ste ponastavitev gesla vašega uporabniškega računa.
Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format(jwt_token)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
# 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()
def send_admins_new_user_notification_mail(user_id, config):
user = RegisteredUser.query.filter_by(id=user_id).first()
body = '''
Nov uporabnik "{}" je ustvaril uporabniški račun na portalu za oddajanje besedil Šolar in čaka na odobritev.
'''.format(user.name)
admins = RegisteredUser.query.filter_by(role="admin").all()
# Create a secure SSL context
context = ssl.create_default_context()
for admin in admins:
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = admin.email
message['Subject'] = 'Nova registracija'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
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'], admin.email, text)
except Exception:
traceback.print_exc()
def send_user_activation_mail(user_id, config):
user = RegisteredUser.query.filter_by(id=user_id).first()
body = '''Vaš uporabniški račun "{}" na portalu Šolar je bil odobren.'''.format(user.name)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = user.email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
# 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'], user.email, text)
except Exception:
traceback.print_exc()

@ -1,298 +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: 732px;
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">
<div id="rect1">
<div id="logo-container">
<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:</label>
<input type="text" id="ime" name="ime" required="required"/>
<label for="podjetje">Podjetje / institucija:</label>
<input type="text" id="podjetje" name="podjetje"/>
<label for="naslov">Naslov:</label>
<input type="text" id="naslov" name="naslov"/>
<label for="posta">Pošta:</label>
<input type="text" id="posta" name="posta"/>
<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"/>
<div class="form-text">*Po kliku na gumb “Oddaj” se bo prikazala vsebina pogodbe o odstopu avtorskih pravic. Če se z vsebino strinjate, kliknite gumb “Pošlji”, da podatke posredujete v korpus, po e-pošti pa boste prejeli svoj izvod pogodbe.</div>
<button id="button-submit" type="submit">Oddaj</button>
</div>
<div class="dropzone-previews"></div>
</form>
</div>
</div>
<div id="popup-terms" style="display: none">
<div id="popup-terms-text">
<h2>POGODBA O PRENOSU AVTORSKIH PRAVIC</h2>
<h3>UVODNE DOLOČBE</h3>
<h4><b>1. člen</b></h4>
<p>1.1. Stranki uvodoma ugotavljata, da naročnik izvaja projekt Razvoj slovenščine v digitalnem
okolju RSDO (v nadaljevanju projekt RSDO), ki je bil na javnem razpisu Razvoj slovenščine v
digitalnem okolju jezikovni viri in tehnologije (JR-ESRR-Razvoj slovenščine v digitalnem
okolju), objavljenem v Uradnem listu RS št. 70/19 dne 29. 11. 2019, sprejet v sofinanciranje
in katerega vsebina je razvidna s spletnih strani https://slovenscina.eu.</p>
<p>1.2. Stranki uvodoma ugotavljata, da bo naročnik v okviru projekta RSDO:
- izdelal osrednjo digitalno slovarsko bazo, ki združuje različne tipe jezikovnih podatkov o
slovenščini v odprtem dostopu,
- izdelal terminološki portal z integriranim iskalnikom po slovenskih terminoloških virih, zlasti
terminoloških slovarjih,
- izdelal korpus prevodov po različnih domenah za učenje strojnega prevajalnika za jezikovni
par angleščina-slovenščina in slovenščina-angleščina.</p>
<p>1.3. Stranki uvodoma ugotavljata, da bo naročnik pri projektu RSDO za vse zgoraj opisane
namene zbiral in uporabil besedilne vire, ki so navedeni v prilogi k tej pogodbi in ki so lahko
avtorska dela ali drugi predmeti varstva v skladu z Zakonom o avtorski in sorodnih pravicah
(Uradni list RS, št. 16/07 uradno prečiščeno besedilo, 68/08, 110/13, 56/15, 63/16 ZKUASP
in 59/19; ZASP) in na katerih ima imetnik pravic avtorske, avtorski sorodne ali druge pravice v
skladu z ZASP (v nadaljevanju avtorska dela).</p>
<p>1.4. Stranki ugotavljata, da bodo avtorska dela in vse njihove morebitne spremembe in
predelave, ter zbirke podatkov, ki bodo med izvajanjem projekta RSDO nastale, javno
dostopni pod pogoji prostih licenc (npr. CC BY-SA) in bodo na voljo za nekomercialen in
komercialen razvoj tehnologij, za raziskave in za druge raziskovalne namene
posameznikom, raziskovalnim in izobraževalnim institucijam, neprofitnim organizacijam,
državnim organom, organizacijam z javnimi pooblastili in gospodarskim družbam v Sloveniji
in tujini.</p>
<h3>PREDMET POGODBE</h3>
<h4><b>2. člen</b></h4>
<p>2.1. Predmet pogodbe so vsa avtorska dela imetnika pravic, ki so navedena v prilogi k tej
pogodbi.</p>
<p>2.2. S podpisom te pogodbe imetnik avtorskih pravic na naročnika prenaša avtorske pravice
na avtorskih delih na način in v obsegu, kakor je navedeno v 3. členu te pogodbe.</p>
<h3>PRENOS AVTORSKIH PRAVIC</h3>
<h4><b>3. člen</b></h4>
<p>3.1. S podpisom te pogodbe imetnik pravic na avtorskih delih, ki so predmet te pogodbe, na
naročnika neizključno, brez časovnih in teritorialnih omejitev prenaša vse materialne avtorske
pravice, avtorski sorodne pravice in druge pravice avtorja v skladu z ZASP, zlasti pravico
reproduciranja (23. člen ZASP), distribuiranja (24. člena ZASP), dajanja v najem (25. člen ZASP),
priobčitve javnosti (26. do 32.a člen ZASP), vključno s pravico dajanja na voljo javnosti (32.a
člen ZASP) in pravico predelave (33. člen ZASP).</p>
<p>3.2. S podpisom te pogodbe imetnik pravic izrecno soglaša, da naročnik pravice iz točke 3.1.
prenaša naprej na tretje osebe brez omejitev.</p>
<h3>JAMČEVANJE IMETNIKA PRAVIC</h3>
<h4><b>4. člen</b></h4>
<p>4.1. S podpisom te pogodbe imetnik pravic jamči, da je na avtorskih delih, ki so predmet te
pogodbe, imetnik vseh avtorskih pravic, avtorski sorodnih pravic in drugih pravic avtorja v
skladu z ZASP, ki so potrebne za prenos pravic po tej pogodbi, in da na avtorskih delih ne
obstajajo pravice tretjih oseb, ki bi naročniku preprečevale njihovo uporabo.</p>
<p>4.2. Določbe te pogodbe ne vplivajo na prenos moralnih avtorskih pravic, ki so v skladu z
določbami ZASP neprenosljive.</p>
<h3>OSEBNI PODATKI</h3>
<h4><b>5. člen</b></h4>
<p>6.1. Stranki se zavezujeta, da bosta vse morebitne osebne podatke, ki jih bosta obdelovali za
namene izvajanja te pogodbe, obdelovali na način, da bosta upoštevali vse veljavne predpise
o varstvu osebnih podatkov in da bosta posameznikom, na katere se osebni podatki nanašajo,
zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatkov.<p>
<h3>KONTAKTNE OSEBE</h3>
<h4><b>6. člen</b></h4>
<p>7.1 Kontaktna oseba za izvedbo te pogodbe na strani naročnika je [xxx].</p>
<p>7.2. Kontaktna oseba za izvedbo te pogodbe na strani imetnika pravic je [xxx].</p>
<h3>KONČNE DOLOČBE</h3>
<h4><b>7. člen</b></h4>
<p>8.1. Če je katerakoli določba te pogodbe nična, ostanejo druga določila te pogodbe v veljavi.</p>
<h4><b>8. člen</b></h4>
<p>9.1. Za razmerja v zvezi s to pogodbo se uporabljajo pravni predpisi Republike Slovenije.</p>
<p>9.2. Spore iz te pogodbe bosta stranki reševali po mirni poti. V primeru, da mirna rešitev ne
bo mogoča, je za vse spore v zvezi s to pogodbo pristojno sodišče v Ljubljani.</p>
<h4><b>9. člen</b></h4>
<p>10.1. Ta pogodba nadomešča vsa predhodna pogajanja, ponudbe in druge dogovore med
strankama.</p>
<p>10.2. Ta pogodba je sestavljena v [dveh] istovetnih izvodih, od katerih prejme vsaka stranka
po enega.</p>
<p>10.3. Pogodbeni stranki s podpisom potrjujeta veljavnost te pogodbe.</p>
</div>
<button id="button-submit-cancel" class="button-terms" style="background: #ff2d2d;">Prekliči</button>
<button id="button-submit-final" class="button-terms">Pošlji</button>
</div>
<!--{{ dropzone.load_js() }}-->
<script src="/static/dropzone.js"></script>
<script>
/////////////////////////
// Dropzone //
/////////////////////////
var btnSubmit = document.getElementById("button-submit");
var btnSubmitFinal = document.getElementById("button-submit-final");
var btnSubmitCancel = document.getElementById("button-submit-cancel");
var elemTermsPopup = document.getElementById("popup-terms");
var termsScrollbox = document.getElementById("popup-terms-text");
var scrollboxTriggered = false;
var form = document.forms["my-dropzone"];
function isEmptyOrSpaces(str){
return str == null || str.match(/^ *$/) !== null;
}
const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
Dropzone.options.myDropzone = { // The camelized version of the ID of the form element
url: "/{{corpus_name}}/upload",
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: {{max_files}},
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 1000, // MB
timeout: 5000000, // 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 email = form["email"].value;
var podjetje = form["podjetje"].value;
var telefon = form["telefon"].value;
if (isEmptyOrSpaces(ime) || isEmptyOrSpaces(email)) {
alert("Izpolnite vsa obvezna polja!");
} else if (!reEmail.test(email.toLowerCase())) {
alert("Email napačnega formata!");
} else if (ime.length > 100 || email.length > 100 || podjetje.length > 100 || telefon.length > 100) {
alert("Velikost polj je omejena na 100 znakov.");
} else {
// Then make terms popup visible
btnSubmit.disabled = true;
btnSubmitFinal.disabled = true;
elemTermsPopup.style.display = "inline";
scrollboxTriggered = false;
}
});
// First change the button to actually tell Dropzone to process the queue.
btnSubmitFinal.addEventListener("click", function(e) {
// Hand off data to dropzone
dz.processQueue();
// Clear fields and hide popup agian
btnSubmit.disabled = false;
elemTermsPopup.style.display = "none";
form.reset();
scrollboxTriggered = false;
});
btnSubmitCancel.addEventListener("click", function(e) {
btnSubmit.disabled = false;
scrollboxTriggered = false;
elemTermsPopup.style.display = "none";
});
// Enable final submit button only if user scrolls to the end of the terms.
function checkScrollboxTrigger(event) {
var element = event.target;
if (!scrollboxTriggered
&& element.scrollHeight - element.scrollTop <= element.clientHeight + 50
) {
scrollboxTriggered = true;
btnSubmitFinal.disabled = false;
}
}
termsScrollbox.addEventListener('scroll', function(event) {
checkScrollboxTrigger(event);
});
termsScrollbox.addEventListener("mouseenter", function(event) {
checkScrollboxTrigger(event);
});
// 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>

@ -1,11 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<a href="/prevodi">Korpus paralelnih besdil ANG-SLO</a><br>
<a href="/gigafida">Korpus Gigafida</a><br>
<a href="/solar">Korpus Šolar</a><br>
</body>
</html>

@ -30,7 +30,7 @@
</style> </style>
</head> </head>
<body> <body>
<a href="/solar/oddaja">Nazaj na oddajo</a> <a href="../oddaja">Nazaj na oddajo</a>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
<div style="background: blue;"> <div style="background: blue;">
@ -65,7 +65,7 @@
</table> </table>
</div> </div>
<h3>Dodaj uporabnika</h3> <h3>Dodaj uporabnika</h3>
<form action="/solar/adduser" method="post"> <form action="../adduser" method="post">
<label for="name">Ime in priimek:</label><br> <label for="name">Ime in priimek:</label><br>
<input type="text" id="name" name="name"><br> <input type="text" id="name" name="name"><br>
<label for="email">Email:</label><br> <label for="email">Email:</label><br>
@ -75,7 +75,7 @@
<input type="submit" value="Dodaj"> <input type="submit" value="Dodaj">
</form> </form>
<h3>Spremeni email uporabnika</h3> <h3>Spremeni email uporabnika</h3>
<form action="/solar/changeuseremail" method="post"> <form action="../changeuseremail" method="post">
<label for="user-id">ID uporabnika:</label><br> <label for="user-id">ID uporabnika:</label><br>
<input type="text" id="user-id" name="user-id"><br> <input type="text" id="user-id" name="user-id"><br>
<label for="email">Nov email:</label><br> <label for="email">Nov email:</label><br>
@ -83,7 +83,7 @@
<input type="submit" value="Spremeni"> <input type="submit" value="Spremeni">
</form> </form>
<h3>Spremeni ime in priimek uporabnika</h3> <h3>Spremeni ime in priimek uporabnika</h3>
<form action="/solar/changeusername" method="post"> <form action="../changeusername" method="post">
<label for="user-id">ID uporabnika:</label><br> <label for="user-id">ID uporabnika:</label><br>
<input type="text" id="user-id" name="user-id"><br> <input type="text" id="user-id" name="user-id"><br>
<label for="name">Ime in priimek:</label><br> <label for="name">Ime in priimek:</label><br>
@ -91,13 +91,13 @@
<input type="submit" value="Spremeni"> <input type="submit" value="Spremeni">
</form> </form>
<h3>Odstrani uporabnika</h3> <h3>Odstrani uporabnika</h3>
<form action="/solar/deluser" method="post"> <form action="../deluser" method="post">
<label for="user_id">ID uporabnika:</label><br> <label for="user_id">ID uporabnika:</label><br>
<input type="text" id="user_id" name="user_id"><br> <input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani"> <input type="submit" value="Odstrani">
</form> </form>
<h3>Dodeli uporabnika instituciji</h3> <h3>Dodeli uporabnika instituciji</h3>
<form action="/solar/addusertoinstitution" method="post"> <form action="../addusertoinstitution" method="post">
<label for="user_id">ID uporabnika:</label> <label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br> <input type="text" id="user_id" name="user_id"><br>
<label for="institution_id">ID institucije:</label> <label for="institution_id">ID institucije:</label>
@ -111,13 +111,13 @@
<input type="submit" value="Dodeli"> <input type="submit" value="Dodeli">
</form> </form>
<h3>Odstrani uporabnika iz institucije</h3> <h3>Odstrani uporabnika iz institucije</h3>
<form action="/solar/deluserfrominstitution" method="post"> <form action="../deluserfrominstitution" method="post">
<label for="user_id">ID uporabnika:</label> <label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br> <input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani"> <input type="submit" value="Odstrani">
</form> </form>
<h3>Spremeni vlogo uporabniškega računa</h3> <h3>Spremeni vlogo uporabniškega računa</h3>
<form action="/solar/changeuserrole" method="post"> <form action="../changeuserrole" method="post">
<label for="user-id">ID uporabnika:</label> <label for="user-id">ID uporabnika:</label>
<input type="text" id="user-id" name="user-id"><br> <input type="text" id="user-id" name="user-id"><br>
<label for="role">Vloga:</label> <label for="role">Vloga:</label>
@ -152,7 +152,7 @@
</table> </table>
</div> </div>
<h3>Aktiviraj uporabnika</h3> <h3>Aktiviraj uporabnika</h3>
<form action="/solar/activateuser" method="post"> <form action="../activateuser" method="post">
<label for="id">ID uporabnika:</label> <label for="id">ID uporabnika:</label>
<input type="text" id="id" name="id"><br> <input type="text" id="id" name="id"><br>
<input type="submit" value="Aktiviraj"> <input type="submit" value="Aktiviraj">
@ -160,7 +160,7 @@
<div> </div> <div> </div>
<h2>Institucije</h2> <h2>Institucije</h2>
<h3>Dodaj institucijo</h3> <h3>Dodaj institucijo</h3>
<form action="/solar/addinstitution" method="post"> <form action="../addinstitution" method="post">
<label for="name">Naziv:</label> <label for="name">Naziv:</label>
<input type="text" id="name" name="name"><br> <input type="text" id="name" name="name"><br>
<label for="region">Regija:</label> <label for="region">Regija:</label>
@ -200,7 +200,7 @@
</table> </table>
</div> </div>
<h3>Združi instituciji</h3> <h3>Združi instituciji</h3>
<form action="/solar/mergeinstitutions" method="post"> <form action="../mergeinstitutions" method="post">
<label for="id-from">Institucijo z ID</label> <label for="id-from">Institucijo z ID</label>
<input type="text" id="id-from" name="id-from"> <input type="text" id="id-from" name="id-from">
<label for="id-to">združi v institucijo z ID</label> <label for="id-to">združi v institucijo z ID</label>
@ -208,7 +208,7 @@
<input type="submit" value="Združi"> <input type="submit" value="Združi">
</form> </form>
<h3>Spremeni podatke institucije</h3> <h3>Spremeni podatke institucije</h3>
<form action="/solar/changeinstitutiondata" method="post"> <form action="../changeinstitutiondata" method="post">
<label for="id">ID institucije</label> <label for="id">ID institucije</label>
<input type="text" id="id" name="id"><br> <input type="text" id="id" name="id"><br>
<label for="name">Nov naziv:</label> <label for="name">Nov naziv:</label>
@ -256,7 +256,7 @@
</table> </table>
</div> </div>
<h3>Dodaj vnos</h3> <h3>Dodaj vnos</h3>
<form action="/solar/addcooperationhistoryitem" method="post"> <form action="../addcooperationhistoryitem" method="post">
<label for="user">ID uporabnika</label> <label for="user">ID uporabnika</label>
<input type="text" id="user" name="user"><br> <input type="text" id="user" name="user"><br>
<label for="institution">ID institucije</label> <label for="institution">ID institucije</label>
@ -274,7 +274,7 @@
<input type="submit" value="Dodaj"> <input type="submit" value="Dodaj">
</form> </form>
<h3>Odstrani vnos</h3> <h3>Odstrani vnos</h3>
<form action="/solar/delcooperationhistoryitem" method="post"> <form action="../delcooperationhistoryitem" method="post">
<label for="entry-id">ID vnosa</label> <label for="entry-id">ID vnosa</label>
<input type="text" id="entry-id" name="entry-id"><br> <input type="text" id="entry-id" name="entry-id"><br>
<input type="submit" value="Odstrani"> <input type="submit" value="Odstrani">
@ -320,7 +320,7 @@
</table> </table>
</div> </div>
<h3>Posodobi podatke nalaganja</h3> <h3>Posodobi podatke nalaganja</h3>
<form action="/solar/updateuploaditem" method="post"> <form action="../updateuploaditem" method="post">
<label for="item-id">ID nalaganja</label> <label for="item-id">ID nalaganja</label>
<input type="text" id="item-id" name="item-id"/><br> <input type="text" id="item-id" name="item-id"/><br>
<label for="program">Program</label> <label for="program">Program</label>

@ -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>
<a href="/solar/login" class="contract-item-button" style="float: none;">Nazaj na prijavo</a> <a href="../login" class="contract-item-button" style="float: none;">Nazaj na prijavo</a>
<h3 id="title" style="font-size: 27px; text-align: left;">Pozabljeno geslo - ŠOLAR</h3> <h3 id="title" style="font-size: 27px; text-align: left;">Pozabljeno geslo - ŠOLAR</h3>
<div> <div>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
@ -22,7 +22,7 @@
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<form method="POST" action="/solar/sendresetpass"> <form method="POST" action="../sendresetpass">
<div> <div>
<div> <div>
<input type="email" name="email" placeholder="Email" autofocus=""> <input type="email" name="email" placeholder="Email" autofocus="">

@ -27,10 +27,10 @@
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<form method="POST" action="/solar/login"> <form method="POST" action="../login">
<div> <div>
<div> <div>
<img src="/static/image/user.png" alt="user" style="float: left; width: 10%;"/> <img src="../static/image/user.png" alt="user" style="float: left; width: 10%;"/>
<input type="email" name="email" placeholder="Email" autofocus="" <input type="email" name="email" placeholder="Email" autofocus=""
style="float: right; width: 85%; margin-bottom: 20px; margin-top: 10px;"> style="float: right; width: 85%; margin-bottom: 20px; margin-top: 10px;">
</div> </div>
@ -45,8 +45,8 @@
</div> </div>
<button class="button-general" style="margin-top: 30px; margin-bottom: 20px;">PRIJAVA</button> <button class="button-general" style="margin-top: 30px; margin-bottom: 20px;">PRIJAVA</button>
</form> </form>
<a href="/solar/forgotpass" class="contract-item-button">Pozabljeno geslo</a> <a href="../forgotpass" class="contract-item-button">Pozabljeno geslo</a>
<a href="/solar/register" class="contract-item-button" <a href="../register" class="contract-item-button"
style="-webkit-appearance: button; style="-webkit-appearance: button;
-moz-appearance: button; -moz-appearance: button;
appearance: button; appearance: button;
@ -54,7 +54,7 @@
text-decoration: none; text-decoration: none;
color: #46535b; color: #46535b;
width: 100%;" width: 100%;"
><img src="/static/image/register.png" alt="user" style="float: left; width: 15%; margin-right: 25px;"/> ><img src="../static/image/register.png" alt="user" style="float: left; width: 15%; margin-right: 25px;"/>
Registracija<br><div style="font-size: 11px; margin-top: 10px;">Še nimate uporabniškega računa? Registrirajte se!</div></a> Registracija<br><div style="font-size: 11px; margin-top: 10px;">Še nimate uporabniškega računa? Registrirajte se!</div></a>
</div> </div>
</div> </div>

@ -30,7 +30,7 @@
</style> </style>
</head> </head>
<body> <body>
<a href="/solar/oddaja">Nazaj na oddajo</a> <a href="../oddaja">Nazaj na oddajo</a>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
<div style="background: blue;"> <div style="background: blue;">
@ -81,7 +81,7 @@
</div> </div>
<br> <br>
<h3>Dodaj uporabnika instituciji</h3> <h3>Dodaj uporabnika instituciji</h3>
<form action="/solar/addusertoinstitution" method="post"> <form action="../addusertoinstitution" method="post">
<label for="user_id">ID uporabnika:</label> <label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br> <input type="text" id="user_id" name="user_id"><br>
<label for="role">Vloga v instituciji:</label> <label for="role">Vloga v instituciji:</label>
@ -93,7 +93,7 @@
<input type="submit" value="Dodeli"> <input type="submit" value="Dodeli">
</form> </form>
<h3>Odstrani uporabnika iz institucije</h3> <h3>Odstrani uporabnika iz institucije</h3>
<form action="/solar/deluserfrominstitution" method="post"> <form action="../deluserfrominstitution" method="post">
<label for="user_id">ID uporabnika:</label> <label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br> <input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani"> <input type="submit" value="Odstrani">

@ -18,12 +18,12 @@
<link rel="stylesheet" href="/static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
</head> </head>
<body> <body>
<a href="/solar/logout">Odjavi se</a> <a href="../logout">Odjavi se</a>
{% if is_institution_coordinator %} {% if is_institution_coordinator %}
<br><a href="/solar/manage-institution">Upravljaj z institucijo</a> <br><a href="../manage-institution">Upravljaj z institucijo</a>
{% endif %} {% endif %}
{% if is_admin %} {% if is_admin %}
<br><a href="/solar/admin">Administracijski meni</a> <br><a href="../admin">Administracijski meni</a>
{% endif %} {% endif %}
<br><a href="mailto:email@example.com">Pomoč</a> <br><a href="mailto:email@example.com">Pomoč</a>
<div class="bg"></div> <div class="bg"></div>
@ -161,7 +161,7 @@
const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
Dropzone.options.myDropzone = { // The camelized version of the ID of the form element Dropzone.options.myDropzone = { // The camelized version of the ID of the form element
url: "/solar/upload", url: "../upload",
autoProcessQueue: false, autoProcessQueue: false,
uploadMultiple: true, uploadMultiple: true,
parallelUploads: 20, parallelUploads: 20,
@ -266,13 +266,13 @@
btnZgodovina.addEventListener("click", function(e) { btnZgodovina.addEventListener("click", function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
window.location.replace("/solar/zgodovina"); window.location.replace("../zgodovina");
}); });
btnPogodbe.addEventListener("click", function(e) { btnPogodbe.addEventListener("click", function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
window.location.replace("/solar/pogodbe"); window.location.replace("../pogodbe");
}); });
// Enable final submit button only if user scrolls to the end of the terms. // Enable final submit button only if user scrolls to the end of the terms.

@ -6,12 +6,12 @@
<link rel="stylesheet" href="/static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
</head> </head>
<body> <body>
<a href="/solar/logout">Odjavi se</a> <a href="../logout">Odjavi se</a>
{% if is_institution_coordinator %} {% if is_institution_coordinator %}
<br><a href="/solar/manage-institution">Upravljaj z institucijo</a> <br><a href="../manage-institution">Upravljaj z institucijo</a>
{% endif %} {% endif %}
{% if is_admin %} {% if is_admin %}
<br><a href="/solar/admin">Administracijski meni</a> <br><a href="../admin">Administracijski meni</a>
{% endif %} {% endif %}
<br><a href="mailto:email@example.com">Pomoč</a> <br><a href="mailto:email@example.com">Pomoč</a>
<div class="bg"></div> <div class="bg"></div>
@ -23,9 +23,9 @@
</div> </div>
<h1 id="title" style="font-size: 25px; position: relative;">Korpus ŠOLAR</h1> <h1 id="title" style="font-size: 25px; position: relative;">Korpus ŠOLAR</h1>
<div class="selection-tabs"> <div class="selection-tabs">
<button onclick="window.location.replace('/solar/oddaja');" class="selection-tab-button">ODDAJA</button> <button onclick="window.location.replace('../oddaja');" class="selection-tab-button">ODDAJA</button>
<button onclick="window.location.replace('/solar/zgodovina');" class="selection-tab-button">ZGODOVINA</button> <button onclick="window.location.replace('../zgodovina');" class="selection-tab-button">ZGODOVINA</button>
<button onclick="window.location.replace('/solar/pogodbe');" class="selection-tab-button selected">POGODBE</button> <button onclick="window.location.replace('../pogodbe');" class="selection-tab-button selected">POGODBE</button>
</div> </div>
</div> </div>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
@ -45,7 +45,7 @@
<div class="contract-item-title">Pogodba s šolo</div> <div class="contract-item-title">Pogodba s šolo</div>
<div class="contract-item-data">{{contract_school.original_filename}}</div> <div class="contract-item-data">{{contract_school.original_filename}}</div>
<div class="contract-item-data">DODANO: {{contract_school.timestamp}}</div> <div class="contract-item-data">DODANO: {{contract_school.timestamp}}</div>
<a href="/solar/pogodbe-institucije/{{ contract_school.file_contract }}.pdf" class="contract-item-button">Prenesi</a> <a href="../pogodbe-institucije/{{ contract_school.file_contract }}.pdf" class="contract-item-button">Prenesi</a>
</div> </div>
</br> </br>
{% endif %} {% endif %}
@ -55,7 +55,7 @@
<div class="contract-item-title" style="font-size: 12px;">{{item.original_filename}}</div> <div class="contract-item-title" style="font-size: 12px;">{{item.original_filename}}</div>
<div class="contract-item-data">Pogodba o prenosu lastništva</div> <div class="contract-item-data">Pogodba o prenosu lastništva</div>
<div class="contract-item-data">DODANO: {{item.timestamp}}</div> <div class="contract-item-data">DODANO: {{item.timestamp}}</div>
<a href="/solar/pogodbe-ucencistarsi/{{ item.file_contract }}.pdf" class="contract-item-button">Prenesi</a> <a href="../pogodbe-ucencistarsi/{{ item.file_contract }}.pdf" class="contract-item-button">Prenesi</a>
</div> </div>
</br> </br>
{% endfor %} {% endfor %}
@ -65,7 +65,7 @@
{% if show_upload_form %} {% if show_upload_form %}
<div style="padding: 20px; position: absolute; top: 500px; width: 89%;"> <div style="padding: 20px; position: absolute; top: 500px; width: 89%;">
<div class="section-desc">Oddaj pogodbo</div> <div class="section-desc">Oddaj pogodbo</div>
<form action="/solar/pogodbe" method="post" enctype="multipart/form-data"> <form action="../pogodbe" method="post" enctype="multipart/form-data">
{% if enable_upload_school_contract %} {% if enable_upload_school_contract %}
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center"> <div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label style="width: 80%; text-align: right;" for="sola">Pogodba s šolo</label> <label style="width: 80%; text-align: right;" for="sola">Pogodba s šolo</label>

@ -17,7 +17,7 @@
<div class="form-text"><em>Zbiranje besedil za korpus Šolar poteka po naslednjem postopku, ki prinaša tudi točke za napredovanje.</em></div> <div class="form-text"><em>Zbiranje besedil za korpus Šolar poteka po naslednjem postopku, ki prinaša tudi točke za napredovanje.</em></div>
<a href="/solar/login" class="contract-item-button" style="float: none;">Nazaj na prijavo</a> <a href="../login" class="contract-item-button" style="float: none;">Nazaj na prijavo</a>
<h3 id="title" style="font-size: 27px; text-align: left;">Registracija</h3> <h3 id="title" style="font-size: 27px; text-align: left;">Registracija</h3>
<div> <div>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
@ -27,7 +27,7 @@
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<form method="POST" action="/solar/register"> <form method="POST" action="../register">
<div> <div>
<div> <div>
<input type="name" name="name" placeholder="Ime in priimek" autofocus=""> <input type="name" name="name" placeholder="Ime in priimek" autofocus="">

@ -7,12 +7,12 @@
<script src="/static/chart.js"></script> <script src="/static/chart.js"></script>
</head> </head>
<body> <body>
<a href="/solar/logout">Odjavi se</a> <a href="../logout">Odjavi se</a>
{% if is_institution_coordinator %} {% if is_institution_coordinator %}
<br><a href="/solar/manage-institution">Upravljaj z institucijo</a> <br><a href="../manage-institution">Upravljaj z institucijo</a>
{% endif %} {% endif %}
{% if is_admin %} {% if is_admin %}
<br><a href="/solar/admin">Administracijski meni</a> <br><a href="../admin">Administracijski meni</a>
{% endif %} {% endif %}
<br><a href="mailto:email@example.com">Pomoč</a> <br><a href="mailto:email@example.com">Pomoč</a>
<div class="bg"></div> <div class="bg"></div>
@ -24,9 +24,9 @@
</div> </div>
<h1 id="title" style="font-size: 25px; position: relative;">Korpus ŠOLAR</h1> <h1 id="title" style="font-size: 25px; position: relative;">Korpus ŠOLAR</h1>
<div class="selection-tabs"> <div class="selection-tabs">
<button onclick="window.location.replace('/solar/oddaja');" class="selection-tab-button">ODDAJA</button> <button onclick="window.location.replace('../oddaja');" class="selection-tab-button">ODDAJA</button>
<button onclick="window.location.replace('/solar/zgodovina');" class="selection-tab-button selected">ZGODOVINA</button> <button onclick="window.location.replace('../zgodovina');" class="selection-tab-button selected">ZGODOVINA</button>
<button onclick="window.location.replace('/solar/pogodbe');" class="selection-tab-button">POGODBE</button> <button onclick="window.location.replace('../pogodbe');" class="selection-tab-button">POGODBE</button>
</div> </div>
</div> </div>
<div id="history-container" style="padding: 20px;"> <div id="history-container" style="padding: 20px;">
@ -113,7 +113,7 @@
} }
}); });
} }
fetch('/solar/topuploads').then(r => r.json()).then(j => drawChart(j)); fetch('../topuploads').then(r => r.json()).then(j => drawChart(j));
</script> </script>
</div> </div>

Loading…
Cancel
Save