solar update

This commit is contained in:
msinkec 2021-06-08 08:00:18 +02:00
parent 31ce97cb44
commit 6697c60c7f
11 changed files with 554 additions and 84 deletions

171
app.py
View File

@ -9,7 +9,7 @@ from flask import Flask, render_template, request, redirect, flash, safe_join, s
from flask_dropzone import Dropzone from flask_dropzone import Dropzone
from flask_migrate import Migrate, MigrateCommand from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager from flask_script import Manager
from flask_login import LoginManager, login_required, login_user, current_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.base
@ -193,7 +193,8 @@ def index_corpus(corpus_name):
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):
return RegisteredUser.query.get(int(user_id)) user = RegisteredUser.query.get(int(user_id))
return user
@app.route('/solar/login') @app.route('/solar/login')
@ -204,7 +205,7 @@ def login_get():
@app.route('/<corpus_name>/login', methods=['POST']) @app.route('/<corpus_name>/login', methods=['POST'])
def login_post(corpus_name): def login_post(corpus_name):
if corpus_name not in ENABLED_CORPUSES or corpus_name not in CORPUSES_LOGIN_REQUIRED: if corpus_name not in ENABLED_CORPUSES or corpus_name not in CORPUSES_LOGIN_REQUIRED:
return 404 return '', 404
email = request.form.get('email') email = request.form.get('email')
password = request.form.get('password') password = request.form.get('password')
@ -222,23 +223,32 @@ def login_post(corpus_name):
# Check if user is authorized to log into this corpus. Admins are an exception. # Check if user is authorized to log into this corpus. Admins are an exception.
if not portal.base.has_user_corpus_access(user.id, corpus_name): if not portal.base.has_user_corpus_access(user.id, corpus_name):
flash('Nimate dostop do tega korpusa.') flash('Nimate dostopa do tega korpusa.')
return redirect('/{}/login'.format(corpus_name)) return redirect('/{}/login'.format(corpus_name))
#portal.base.add_user_session(user.id)
login_user(user, remember=remember) login_user(user, remember=remember)
if corpus_name == 'solar': if corpus_name == 'solar':
return redirect('/solar/oddaja') return redirect('/solar/oddaja')
return 404 return '', 404
# TODO: Move solar stuff to seperate file using Flask blueprints. # TODO: Move solar stuff to seperate file using Flask blueprints.
# TODO: Better routing logic. # TODO: Better routing logic.
@app.route('/solar/logout')
@login_required
def logout():
logout_user()
return redirect('/solar/login')
@app.route('/solar/<path:text>') @app.route('/solar/<path:text>')
@login_required @login_required
def solar(text): def solar(text):
if not portal.base.has_user_corpus_access(current_user.id, 'solar'): if not portal.base.has_user_corpus_access(current_user.id, 'solar'):
return 404 return '', 404
if text.startswith('oddaja/') or text == 'oddaja': if text.startswith('oddaja/') or text == 'oddaja':
return render_template('solar-oddaja.html') return render_template('solar-oddaja.html')
elif text.startswith('zgodovina/') or text == 'zgodovina': elif text.startswith('zgodovina/') or text == 'zgodovina':
@ -256,51 +266,162 @@ def solar(text):
institution_names=institution_names) institution_names=institution_names)
elif text.startswith('pogodbe/') or text == 'pogodbe': elif text.startswith('pogodbe/') or text == 'pogodbe':
# Check for ownload contract request. # Check for ownload contract request.
match = re.match('^pogodbe/[a-z0-9_]+\.pdf$', text) match = re.match('^pogodbe/([a-z0-9_]+\.pdf)$', text)
if match: if match:
filename = match.group(1) filename = match.group(1)
safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), filename) if len(filename) < 10:
return '', 404
prefix = filename[:2]
suffix = filename[2:]
safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), prefix, suffix)
try: try:
return send_file(safe_path, as_attachment=True) return send_file(safe_path, as_attachment=True)
except FileNotFoundError: except FileNotFoundError:
return 404 return '', 404
user_obj = portal.base.get_user_obj(current_user.get_id()) user_obj = portal.base.get_user_obj(current_user.get_id())
institution_id = user_obj.institution institutions = portal.base.get_user_institutions(user_obj.id)
contracts_students = [] contracts_students = []
contract_school = None contract_school = []
show_upload_form = True enable_upload_school_contract = False
if institution_id: show_upload_form = False
contracts_students = portal.solar.get_institution_student_contracts(institution_id) if len(institutions) > 0:
contract_school = portal.solar.get_institution_contract(institution_id) show_upload_form = True
else: institution = portal.base.get_user_institutions(user_obj.id)[0]
show_upload_form = False contracts_students = portal.solar.get_institution_student_contracts(institution.id)
contract_school = portal.solar.get_institution_contract(institution.id)
if portal.base.is_institution_moderator(user_obj.id, institution.id):
enable_upload_school_contract = True
return render_template('solar-pogodbe.html', contracts_students=contracts_students, return render_template('solar-pogodbe.html', contracts_students=contracts_students,
contract_school=contract_school, show_upload_form=show_upload_form) contract_school=contract_school,
enable_upload_school_contract=enable_upload_school_contract,
show_upload_form=show_upload_form)
elif text.startswith('admin/') or text == 'admin': elif text.startswith('admin/') or text == 'admin':
solar_users = portal.base.get_all_active_users()
solar_institutions = portal.solar.get_all_institutions()
if current_user.role == 'admin': if current_user.role == 'admin':
return render_template('solar-admin.html') return render_template('solar-admin.html', users=solar_users, institutions=solar_institutions)
return 404 return '', 404
@app.route('/solar/pogodbe', methods=['POST']) @app.route('/solar/pogodbe', methods=['POST'])
@login_required @login_required
def solar_upload_contract(): def solar_upload_contract():
if not portal.base.has_user_corpus_access(current_user.id, 'solar'): if not portal.base.has_user_corpus_access(current_user.id, 'solar'):
return 404 return '', 404
return upload_handler_solar.handle_contract_upload(request, current_user.get_id()) return upload_handler_solar.handle_contract_upload(request, current_user.get_id())
@app.route('/<corpus_name>/adduser', methods=['POST'])
@login_required
def solar_add_user(corpus_name):
if not portal.base.is_admin(current_user.id):
return '', 404
if not corpus_name in ENABLED_CORPUSES:
return '', 404
name = request.form['name']
email = request.form['email']
password = request.form['password']
if not name:
return 'Prazno polje za ime.'
if len(name) > 100:
return 'Predolgo ime.'
if not email:
return 'Prazno polje za elektronsko pošto.'
if len(email) > 100:
return 'Predolgi email naslov'
elif not re.search(portal.base.REGEX_EMAIL, email):
return 'Email napačnega formata.'
if not password:
return 'Prazno polje za geslo.'
if len(password) > 100:
return 'Predolgo geslo.'
portal.base.register_new_user(name, email, password)
return 'Uporabnik je bil dodan.'
@app.route('/solar/deluser', methods=['POST'])
@login_required
def solar_del_user():
# TODO: check if user is institution moderator for the added users institution or is an admin
# TODO: delete from "user", "user_institution_mapping", update "institution_contract" set user to NULL
return '', 404
@app.route('/<corpus_name>/addinstitution', methods=['POST'])
@login_required
def add_institution(corpus_name):
if not portal.base.is_admin(current_user.id):
return '', 404
if not corpus_name in ENABLED_CORPUSES:
return '', 404
name = request.form['name']
region = request.form['region']
if not name:
return 'Prazno polje za ime.'
if len(name) > 100:
return 'Predolgo ime.'
if not region:
return 'Prazno polje za regijo.'
if len(region) > 100:
return 'Predolgi niz za regijo.'
institution_id = portal.base.add_institution(name, region)
portal.base.grant_institution_corpus_access(institution_id, corpus_name)
return 'Institucija je bila dodana.'
@app.route('/<corpus_name>/addusertoinstitution', methods=['POST'])
@login_required
def add_user_institution_mapping(corpus_name):
if not portal.base.is_admin(current_user.id):
return '', 404
if not corpus_name in ENABLED_CORPUSES:
return '', 404
user_id = request.form['user_id']
institution_id = request.form['institution_id']
role = request.form['role']
if role not in ['moderator', 'user']:
return '', 404
# TODO: remove this restriction
if len(portal.base.get_user_institutions(user_id)) > 0:
return 'Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\
'zaenkrat ni implementirano.'
portal.base.add_user_to_institution(user_id, institution_id, role)
return 'Uporabnik je bil dodeljen instituciji.'
@app.route('/<corpus_name>/delinstitution', methods=['POST'])
@login_required
def del_institution(corpus_name):
# 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']) @app.route('/<corpus_name>/upload', methods=['POST'])
def handle_upload(corpus_name): def handle_upload(corpus_name):
if corpus_name not in ENABLED_CORPUSES: if corpus_name not in ENABLED_CORPUSES:
return 404 return '', 404
if corpus_name == 'solar': if corpus_name == 'solar':
if not current_user.is_authenticated: if not current_user.is_authenticated:
return 404 return '', 404
if not portal.base.has_user_corpus_access(current_user.id, corpus_name): if not portal.base.has_user_corpus_access(current_user.id, corpus_name):
return 404 return '', 404
return upload_handler_solar.handle_upload(request, current_user.get_id()) return upload_handler_solar.handle_upload(request, current_user.get_id())
elif corpus_name == 'predavanja': elif corpus_name == 'predavanja':
return upload_handler_predavanja.handle_upload(request) return upload_handler_predavanja.handle_upload(request)

View File

@ -0,0 +1,66 @@
"""Changed user<->institution<->corpus relational mapping.
Revision ID: 5ba116fc7f06
Revises: 7d6db184b8fc
Create Date: 2021-06-07 13:02:42.900168
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5ba116fc7f06'
down_revision = '7d6db184b8fc'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('institution_contract',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('institution', sa.Integer(), nullable=False),
sa.Column('corpus', sa.String(), nullable=False),
sa.Column('timestamp', sa.DateTime(), nullable=False),
sa.Column('file_contract', sa.String(), nullable=True),
sa.ForeignKeyConstraint(['institution'], ['institution.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user_institution_mapping',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user', sa.Integer(), nullable=False),
sa.Column('institution', sa.Integer(), nullable=False),
sa.Column('role', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['institution'], ['institution.id'], ),
sa.ForeignKeyConstraint(['user'], ['registered_user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.add_column('corpus_access', sa.Column('institution', sa.Integer(), nullable=False))
op.drop_constraint('user_id_fkey', 'corpus_access', type_='foreignkey')
op.create_foreign_key(None, 'corpus_access', 'institution', ['institution'], ['id'])
op.drop_column('corpus_access', 'user_id')
op.create_unique_constraint(None, 'institution', ['name'])
op.drop_column('institution', 'file_contract')
op.create_unique_constraint(None, 'registered_user', ['email'])
op.drop_constraint('registered_user_institution_fkey', 'registered_user', type_='foreignkey')
op.drop_column('registered_user', 'institution_moderator')
op.drop_column('registered_user', 'institution')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('registered_user', sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('registered_user', sa.Column('institution_moderator', sa.BOOLEAN(), autoincrement=False, nullable=True))
op.create_foreign_key('registered_user_institution_fkey', 'registered_user', 'institution', ['institution'], ['id'])
op.drop_constraint(None, 'registered_user', type_='unique')
op.add_column('institution', sa.Column('file_contract', sa.TEXT(), autoincrement=False, nullable=True))
op.drop_constraint(None, 'institution', type_='unique')
op.add_column('corpus_access', sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False))
op.drop_constraint(None, 'corpus_access', type_='foreignkey')
op.create_foreign_key('user_id_fkey', 'corpus_access', 'registered_user', ['user_id'], ['id'])
op.drop_column('corpus_access', 'institution')
op.drop_table('user_institution_mapping')
op.drop_table('institution_contract')
# ### end Alembic commands ###

View File

@ -3,6 +3,7 @@ import time
import ssl import ssl
import traceback import traceback
import re import re
import logging
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
@ -19,7 +20,9 @@ from email.mime.application import MIMEApplication
import pdfkit import pdfkit
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from . model import db, UploadRegular, UploadSolar, RegisteredUser, CorpusAccess, Institution from werkzeug.security import generate_password_hash
from . model import db, UploadRegular, UploadSolar, RegisteredUser, UserInstitutionMapping, Institution, InstitutionContract, CorpusAccess
#REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$') #REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$')
@ -131,7 +134,7 @@ class UploadHandler:
return res return res
@staticmethod @staticmethod
def store_model(self, model_obj): def store_model(model_obj):
try: try:
db.session.add(model_obj) db.session.add(model_obj)
db.session.commit() db.session.commit()
@ -234,20 +237,100 @@ class UploadHandler:
return None return None
@staticmethod
def get_user_institution(user_id): def get_user_institutions(user_id):
match = db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).one() return UserInstitutionMapping.query.filter_by(user=user_id).all()
return match.institution
def has_user_corpus_access(user_id, corpus_name): def has_user_corpus_access(user_id, corpus_name):
user = RegisteredUser.query.filter_by(id=user_id).first() 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': if user.role == 'admin':
return True return True
return CorpusAccess.query.filter_by(user_id=user.id, corpus=corpus_name).first() is not None
# Check if user belongs to an institution, that has access to this corpus.
institutions = get_user_institutions(user_id)
has_access = False
for institution in institutions:
row = CorpusAccess.query.filter_by(institution=institution.id, corpus=corpus_name).first()
if row:
has_access = True
break
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): def get_user_obj(user_id):
return RegisteredUser.query.filter_by(id=user_id).first() return RegisteredUser.query.filter_by(id=user_id).first()
def get_institution_obj(institution_id): def get_institution_obj(institution_id):
return Institution.query.filter_by(id=institution_id).first() return Institution.query.filter_by(id=institution_id).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 get_all_active_users():
return RegisteredUser.query.filter_by(active=True).all()
def is_institution_moderator(user_id, institution_id):
user_inst_mapping = UserInstitutionMapping.query.filter_by(user=user_id).first()
if not user_inst_mapping:
return False
if user_inst_mapping.role != 'moderator':
return False
return True

View File

@ -83,31 +83,45 @@ class ContractsSolar(db.Model):
contract_type = db.Column(db.String, nullable=False) contract_type = db.Column(db.String, nullable=False)
class CorpusAccess(db.Model):
__tablename__ = 'corpus_access'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=False)
corpus = db.Column(db.String, nullable=False)
class RegisteredUser(UserMixin, db.Model): class RegisteredUser(UserMixin, db.Model):
__tablename__ = 'registered_user' __tablename__ = 'registered_user'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False) name = db.Column(db.String, nullable=False)
email = db.Column(db.String, nullable=False) email = db.Column(db.String, nullable=False, unique=True)
role = db.Column(db.String, nullable=False) role = db.Column(db.String, nullable=False)
pass_hash = db.Column(db.String, nullable=False) pass_hash = db.Column(db.String, nullable=False)
active = db.Column(db.Boolean, nullable=True) active = db.Column(db.Boolean, nullable=True)
last_login = db.Column(db.DateTime, nullable=True) last_login = db.Column(db.DateTime, nullable=True)
registered = db.Column(db.DateTime, nullable=True) registered = db.Column(db.DateTime, nullable=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=True)
institution_moderator = db.Column(db.Boolean, default=False)
class UserInstitutionMapping(db.Model):
__tablename__ = 'user_institution_mapping'
id = db.Column(db.Integer, primary_key=True)
user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=False)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False)
role =db.Column(db.String, nullable=False)
class Institution(db.Model): class Institution(db.Model):
__tablename__ = 'institution' __tablename__ = 'institution'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False) name = db.Column(db.String, nullable=False, unique=True)
region = db.Column(db.String, nullable=False) region = db.Column(db.String, nullable=False)
class CorpusAccess(db.Model):
__tablename__ = 'corpus_access'
id = db.Column(db.Integer, primary_key=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False)
corpus = db.Column(db.String, nullable=False)
class InstitutionContract(db.Model):
__tablename__ = 'institution_contract'
id = db.Column(db.Integer, primary_key=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False)
corpus = db.Column(db.String, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
file_contract = db.Column(db.String, nullable=True) file_contract = db.Column(db.String, nullable=True)

View File

@ -3,10 +3,10 @@ import re
import traceback import traceback
import hashlib import hashlib
from datetime import datetime from datetime import datetime
from sqlalchemy import desc from sqlalchemy import desc, exists
from portal.base import UploadHandler from portal.base import UploadHandler, get_user_institutions, has_user_corpus_access
from portal.model import db, UploadSolar, ContractsSolar, RegisteredUser, Institution from portal.model import db, UploadSolar, ContractsSolar, RegisteredUser, Institution, InstitutionContract, UserInstitutionMapping, CorpusAccess
VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'} VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'}
@ -27,7 +27,8 @@ class UploadHandlerSolar(UploadHandler):
sorted_f_hashes = list(file_hashes.values()) sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort() sorted_f_hashes.sort()
institution_id = UploadHandler.get_user_institution(user_id) # If user is mapped to multiple institutions, let him chose in name of which one he makes the upload.
institution_id = get_user_institutions(user_id)[0].id
model_obj = UploadSolar( model_obj = UploadSolar(
upload_user = user_id, upload_user = user_id,
@ -44,7 +45,7 @@ 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
) )
self.store_model(model_obj) UploadHandler.store_model(model_obj)
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)
@ -73,9 +74,9 @@ class UploadHandlerSolar(UploadHandler):
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files)) return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files))
def handle_contract_upload(self, request, user_id): def handle_contract_upload(self, request, user_id):
contracts_type = request.form['tip-pogodbe'] contract_type = request.form['tip-pogodbe']
if contracts_type not in ['sola', 'ucenci-starsi']: if contract_type not in ['sola', 'ucenci-starsi']:
return 'Neveljaven tip pogodbe.' return 'Neveljaven tip pogodbe.'
f_obj = None f_obj = None
@ -92,35 +93,46 @@ class UploadHandlerSolar(UploadHandler):
return 'Niste naložili nobene datoteke.' return 'Niste naložili nobene datoteke.'
base = self.get_uploads_subdir('contracts') base = self.get_uploads_subdir('contracts')
f_hash = hashlib.md5(f_obj.read()) f_hash = hashlib.md5(f_obj.read()).hexdigest()
f_obj.seek(0, 0)
# First byte used for indexing, similarly like git does for example. # First byte used for indexing, similarly like git does for example.
sub_dir = base / f_hash[:2] sub_dir = base / f_hash[:2]
if not sub_dir.exists(): if not sub_dir.exists():
sub_dir.mkdir() sub_dir.mkdir()
path = sub_dir / f_hash[2:] + '.pdf' path = sub_dir / (f_hash[2:] + '.pdf')
f_obj.save(path) f_obj.save(path)
timestamp = datetime.now()
user_obj = RegisteredUser.query.filter_by(id=user_id).one() user_obj = RegisteredUser.query.filter_by(id=user_id).one()
institution_id = user_obj.institution user_institution_mapping = UserInstitutionMapping.query.filter_by(user=user_id).first()
is_institution_moderator = user_obj.institution_moderator if user_institution_mapping is None:
if institution_id is None:
return 'Vaš uporabnik ni dodeljen nobeni inštituciji.' return 'Vaš uporabnik ni dodeljen nobeni inštituciji.'
institution_id = user_institution_mapping.institution
is_institution_moderator = True if user_institution_mapping.role == 'moderator' else False
if contracts_type == 'sola': if contract_type == 'sola':
if not is_institution_moderator: if not is_institution_moderator:
return 'Vaš uporabnik nima pravic za nalaganje pogodbe s šolo.' return 'Vaš uporabnik nima pravic za nalaganje pogodbe s šolo.'
Institution.update().values(file_contract=f_hash).where(id=institution_id) # TODO: insert institution contract
model_obj = InstitutionContract(
institution=institution_id,
corpus='solar',
timestamp=timestamp,
file_contract=f_hash
)
self.store_model(model_obj)
else:
model_obj = ContractsSolar(
institution=institution_id,
upload_user=user_id,
timestamp=timestamp,
file_contract=f_hash,
contract_type=contract_type,
)
model_obj = ContractsSolar( self.store_model(model_obj)
institution=institution_id,
upload_user=user_id,
file_contract=f_hash,
contracts_type=contracts_type,
)
self.store_model(model_obj)
return 'Nalaganje pogodbe je bilo uspešno.' return 'Nalaganje pogodbe je bilo uspešno.'
@staticmethod @staticmethod
@ -154,12 +166,30 @@ def get_upload_history(user_id, n=20):
return UploadSolar.query.filter_by(upload_user=user_id).order_by(desc(UploadSolar.timestamp)).limit(n).all() return UploadSolar.query.filter_by(upload_user=user_id).order_by(desc(UploadSolar.timestamp)).limit(n).all()
def get_all_institutions():
# TODO: do filtering purely within an SQL query
res = []
for institution in Institution.query.all():
row = CorpusAccess.query.filter_by(institution=institution.id, corpus='solar').first()
if row:
res.append(institution)
return res
def get_institution_student_contracts(institution_id): def get_institution_student_contracts(institution_id):
return ContractsSolar.query.filter_by(id=institution_id, contract_type='ucenci-starsi').all() return ContractsSolar.query.filter_by(institution=institution_id, contract_type='ucenci-starsi').all()
def get_institution_contract(institution_id): def get_institution_contract(institution_id):
return ContractsSolar.query.filter_by(id=institution_id, contract_type='sola').first() return InstitutionContract.query.filter_by(institution=institution_id, corpus='solar').order_by(desc(InstitutionContract.timestamp)).first()
def get_all_active_users():
# TODO: do filtering purely within an SQL query
res = []
active_users = RegisteredUser.query.filter_by(active=True).all()
for user in active_users:
if has_user_corpus_access(user.id, 'solar'):
res.append(user)
return res

View File

@ -84,6 +84,20 @@ html {
color: #006cb7; color: #006cb7;
} }
.section-desc {
font-family: Roboto;
font-style: bold;
font-weight: 320;
font-size: 16px;
text-align: center;
line-height: 20px;
margin-block-start: 0.4em;
border-bottom: 3px solid #006cb7;
margin-bottom: 10px;
padding-bottom: 10px;
color: #006cb7;
}
label { label {
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;

110
templates/solar-admin.html Normal file
View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin panel - Šolar</title>
<style>
.tableFixHead {
overflow-y: auto;
height: 106px;
}
.tableFixHead thead th {
position: sticky;
top: 0;
}
table {
border-collapse: collapse;
width: 100%;
}
th,
td {
padding: 8px 16px;
border: 1px solid #ccc;
}
th {
background: #eee;
}
</style>
</head>
<body>
<h2>Uporabniki</h2>
<h3>Dodaj uporabnika</h3>
<form action="/solar/adduser" method="post">
<label for="name">Ime in priimek:</label><br>
<input type="text" id="name" name="name"><br>
<label for="email">Email:</label><br>
<input type="text" id="email" name="email"><br>
<label for="password">Geslo:</label><br>
<input type="text" id="password" name="password"><br>
<input type="submit" value="Dodaj">
</form>
<!--<h3>Odstrani uporabnika</h3>
<form action="/solar/deluser" method="post">
<label for="user_id">ID uporabnika:</label><br>
<input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani">
</form>-->
<h3>Seznam vseh aktivnih uporabnikov</h3>
<div class="tableFixHead">
<table>
<thead>
<tr>
<th>ID</th>
<th>Ime in priimek</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for item in users %}
<tr>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.email}}</td>
</tr>
{% endfor %}
</table>
</div>
<h3>Dodeli uporabnika instituciji</h3>
<form action="/solar/addusertoinstitution" method="post">
<label for="user_id">ID uporabnika:</label>
<input type="text" id="user_id" name="user_id"><br>
<label for="institution_id">ID institucije:</label>
<input type="text" id="institution_id" name="institution_id"><br>
<label for="role">Vloga v instituciji:</label>
<select name="role" id="role">
<option value="moderator">Moderator</option>
<option value="user">Uporabnik</option>
</select>
<input type="submit" value="Dodeli">
</form>
<div> </div>
<h2>Institucije</h2>
<h3>Dodaj institucijo</h3>
<form action="/solar/addinstitution" method="post">
<label for="name">Naziv:</label>
<input type="text" id="name" name="name"><br>
<label for="region">Regija:</label>
<input type="text" id="region" name="region"><br>
<input type="submit" value="Dodaj">
</form>
<h3>Seznam vseh instituticij</h3>
<div class="tableFixHead">
<table>
<thead>
<tr>
<th>ID</th>
<th>Naziv</th>
<th>Regija</th>
</tr>
</thead>
<tbody>
{% for item in institutions %}
<tr>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.region}}</td>
</tr>
{% endfor %}
</table>
</div>
</body>

View File

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

View File

@ -18,6 +18,7 @@
<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>
<div class="bg"></div> <div class="bg"></div>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">

View File

@ -6,6 +6,7 @@
<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>
<div class="bg"></div> <div class="bg"></div>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">
@ -22,33 +23,49 @@
</div> </div>
<div id="conract-container" style="padding: 20px;"> <div id="conract-container" style="padding: 20px;">
{% if contract_school %} {% if contract_school %}
<div class="contract-item" style="background-color: #ccffcc;"> <div class="contract-item" style="background-color: #ccffcc;">
<div class="contract-item-icon">...</div> <div class="contract-item-icon"></div>
<div class="contract-item-title">Pogodba o prenosu lastništva</div> <div class="contract-item-title">Pogodba s šolo</div>
<div class="contract-item-date">DODANO {{contract_school.date}}</div> <div class="contract-item-date">DODANO {{contract_school.date}}</div>
<a href="/solar/pogodbe/{{ contract_school.file_hash }}.pdf" class="contract-item-button">PRENESI</div> <a href="/solar/pogodbe/{{ contract_school.file_contract }}.pdf" class="contract-item-button">PRENESI</a>
</div> </div>
</br> </br>
{% endif %} {% endif %}
{% for item in contracts_students %} {% for item in contracts_students %}
<div class="contract-item"> <div class="contract-item">
<div class="contract-item-icon">...</div> <div class="contract-item-icon"></div>
<div class="contract-item-title">Pogodba o prenosu lastništva</div> <div class="contract-item-title">Pogodba o prenosu lastništva</div>
<div class="contract-item-date">DODANO {{item.date}}</div> <div class="contract-item-date">DODANO {{item.date}}</div>
<a href="/solar/pogodbe/{{ item.file_hash }}.pdf" class="contract-item-button">PRENESI</div> <a href="/solar/pogodbe/{{ item.file_contract }}.pdf" class="contract-item-button">PRENESI</a>
</div> </div>
</br> </br>
{% endfor %} {% endfor %}
</div> </div>
</br>
</br>
{% if show_upload_form %} {% if show_upload_form %}
<form action="/solar/pogodbe"> <div style="padding: 20px; position: absolute; top: 500px; width: 89%;">
<input type="radio" id="sola" name="tip-pogodbe" value="sola"> <div class="section-desc">Oddaj pogodbo</div>
<label for="sola">Pogodba s šolo</label><br> <form action="/solar/pogodbe" method="post" enctype="multipart/form-data">
<input type="radio" id="ucenci-starsi" name="tip-pogodbe" value="ucenci-starsi"> {% if enable_upload_school_contract %}
<label for="sola">Pogodba z učenci / starši</label><br> <div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<input type="file" id="file-contract" name="filename"> <label style="width: 80%; text-align: right;" for="sola">Pogodba s šolo</label>
<input type="submit" text="Naloži pogodbo"> <input style="width: 20%;" type="radio" id="sola" name="tip-pogodbe" value="sola">
</div>
{% else %}
<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>
<input style="width: 20%;"type="radio" id="sola" name="tip-pogodbe" value="sola" disabled>
</div>
{% endif %}
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label style="width: 80%; text-align: right;" for="ucenci-starsi">Pogodba z učenci / starši</label>
<input style="width: 20%;" type="radio" id="ucenci-starsi" name="tip-pogodbe" value="ucenci-starsi" checked>
</div>
<input style="font-size: 10px;" type="file" id="file-contract" name="filename">
<button style="float: right;" type="submit">Oddaj pogodbo</button>
</form> </form>
</div>
{% endif %} {% endif %}
</div> </div>
<div id="rect2" class="mock-side"> <div id="rect2" class="mock-side">

View File

@ -6,6 +6,7 @@
<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>
<div class="bg"></div> <div class="bg"></div>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">