update predavanja, solar progress

This commit is contained in:
msinkec 2021-05-24 10:15:54 +02:00
parent e840e0b504
commit 3e59662396
14 changed files with 545 additions and 127 deletions

106
app.py
View File

@ -1,10 +1,11 @@
import logging import logging
import os import os
import re
import configparser import configparser
from pathlib import Path from pathlib import Path
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from flask import Flask, render_template, request, redirect, flash from flask import Flask, render_template, request, redirect, flash, safe_join, send_file
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
@ -103,6 +104,7 @@ app.config.update(
SQLALCHEMY_DATABASE_URI = SQL_CONN_STR, SQLALCHEMY_DATABASE_URI = SQL_CONN_STR,
SQLALCHEMY_ECHO = True SQLALCHEMY_ECHO = True
) )
app.url_map.strict_slashes = False
# Run "python app.py db -?" to see more info about DB migrations. # Run "python app.py db -?" to see more info about DB migrations.
manager = Manager(app) manager = Manager(app)
@ -158,11 +160,6 @@ login_manager = LoginManager(app)
login_manager.init_app(app) login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
@app.route('/') @app.route('/')
def index(): def index():
return render_template('index.html') return render_template('index.html')
@ -193,11 +190,9 @@ def load_user(user_id):
return RegisteredUser.query.get(int(user_id)) return RegisteredUser.query.get(int(user_id))
@app.route('/<corpus_name>/login') @app.route('/solar/login')
def login_get(corpus_name): def login_get():
if corpus_name == 'solar': return render_template('login.html', corpus_name='solar', title='ŠOLAR')
return render_template('login.html', corpus_name=corpus_name, title='ŠOLAR')
return 404
@app.route('/<corpus_name>/login', methods=['POST']) @app.route('/<corpus_name>/login', methods=['POST'])
@ -211,8 +206,6 @@ def login_post(corpus_name):
user = RegisteredUser.query.filter_by(email=email).first() user = RegisteredUser.query.filter_by(email=email).first()
# TODO: Check if user is authorized to login to this corpus.
if not user or not check_password_hash(user.pass_hash, password): if not user or not check_password_hash(user.pass_hash, password):
flash('Napačni podatki za prijavo. Poskusite ponovno.') flash('Napačni podatki za prijavo. Poskusite ponovno.')
return redirect('/{}/login'.format(corpus_name)) return redirect('/{}/login'.format(corpus_name))
@ -221,6 +214,11 @@ def login_post(corpus_name):
flash('Vaš uporabniški račun še ni bil aktiviran.') flash('Vaš uporabniški račun še ni bil aktiviran.')
return redirect('/{}/login'.format(corpus_name)) return redirect('/{}/login'.format(corpus_name))
# Check if user is authorized to log into this corpus. Admins are an exception.
if not portal.base.has_user_corpus_access(user.id, corpus_name):
flash('Nimate dostop do tega korpusa.')
return redirect('/{}/login'.format(corpus_name))
login_user(user, remember=remember) login_user(user, remember=remember)
if corpus_name == 'solar': if corpus_name == 'solar':
@ -229,30 +227,64 @@ def login_post(corpus_name):
# TODO: Move solar stuff to seperate file using Flask blueprints. # TODO: Move solar stuff to seperate file using Flask blueprints.
@app.route('/solar/oddaja') # TODO: Better routing logic.
@app.route('/solar/<path:text>')
@login_required @login_required
def solar_oddaja(): def solar(text):
return render_template('solar-oddaja.html') if not portal.base.has_user_corpus_access(current_user.id, 'solar'):
return 404
if text.startswith('oddaja/') or text == 'oddaja':
return render_template('solar-oddaja.html')
elif text.startswith('zgodovina/') or text == 'zgodovina':
upload_items = portal.solar.get_upload_history(current_user.id)
uploader_names = []
institution_names = []
for item in upload_items:
uploader_names.append(portal.base.get_user_obj(current_user.id).name)
institution = portal.base.get_institution_obj(item.institution)
if not institution:
institution_names.append(None)
else:
institution_names.append(institution.name)
return render_template('solar-zgodovina.html', upload_history=upload_items, uploader_names=uploader_names,
institution_names=institution_names)
elif text.startswith('pogodbe/') or text == 'pogodbe':
# Check for ownload contract request.
match = re.match('^pogodbe/[a-z0-9_]+\.pdf$', text)
if match:
filename = match.group(1)
safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), filename)
try:
return send_file(safe_path, as_attachment=True)
except FileNotFoundError:
return 404
user_obj = portal.base.get_user_obj(current_user.get_id())
institution_id = user_obj.institution
@app.route('/solar/zgodvina') contracts_students = []
contract_school = None
show_upload_form = True
if institution_id:
contracts_students = portal.solar.get_institution_student_contracts(institution_id)
contract_school = portal.solar.get_institution_contract(institution_id)
else:
show_upload_form = False
return render_template('solar-pogodbe.html', contracts_students=contracts_students,
contract_school=contract_school, show_upload_form=show_upload_form)
elif text.startswith('admin/') or text == 'admin':
if current_user.role == 'admin':
return render_template('solar-admin.html')
return 404
@app.route('/solar/pogodbe', methods=['POST'])
@login_required @login_required
def solar_zgodovina(): def solar_upload_contract():
return render_template('solar-zgodovina.html') logging.info('TESTTTTTT')
if not portal.base.has_user_corpus_access(current_user.id, 'solar'):
return 404
@app.route('/solar/pogodbe') return upload_handler_solar.handle_contract_upload(request, current_user.get_id())
@login_required
def solar_pogodbe():
return render_template('solar-pogodbe.html')
@app.route('/solar/admin')
@login_required
def solar_admin():
# TODO: check if user is admin
return render_template('solar-admin.html')
@app.route('/<corpus_name>/upload', methods=['POST']) @app.route('/<corpus_name>/upload', methods=['POST'])
def handle_upload(corpus_name): def handle_upload(corpus_name):
@ -260,9 +292,11 @@ def handle_upload(corpus_name):
return 404 return 404
if corpus_name == 'solar': if corpus_name == 'solar':
if current_user.is_authenticated: if not current_user.is_authenticated:
return upload_handler_solar.handle_upload(request, current_user.get_id()) return 404
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())
elif corpus_name == 'predavanja': elif corpus_name == 'predavanja':
return upload_handler_predavanja.handle_upload(request) return upload_handler_predavanja.handle_upload(request)
else: else:

View File

@ -0,0 +1,55 @@
"""permissions and solar stuff
Revision ID: 7d6db184b8fc
Revises: c6edf87b8bff
Create Date: 2021-05-18 13:49:37.642465
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7d6db184b8fc'
down_revision = 'c6edf87b8bff'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.rename_table('stamps', 'stamps_solar')
op.drop_column('upload_solar', 'corpus_name')
op.create_table('corpus_access',
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('corpus_access_id_seq'::regclass)"), autoincrement=True, nullable=False),
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('corpus', sa.TEXT(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['registered_user.id'], name='user_id_fkey'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('contracts_solar',
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('contracts_solar_id_seq'::regclass)"), autoincrement=True, nullable=False),
sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('upload_user', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('timestamp', sa.DateTime(), nullable=False),
sa.Column('file_contract', sa.TEXT(), autoincrement=False, nullable=False),
sa.Column('contract_type', sa.TEXT(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['institution'], ['institution.id'], name='institution_fkey'),
sa.ForeignKeyConstraint(['upload_user'], ['registered_user.id'], name='upload_user_fkey'),
sa.PrimaryKeyConstraint('id')
)
op.add_column('registered_user', sa.Column('institution_moderator', sa.BOOLEAN(), autoincrement=False, default=False))
op.add_column('upload_predavanja', sa.Column('agree_publish_future', sa.TEXT(), nullable=False))
op.add_column('upload_predavanja', sa.Column('agree_machine_translation', sa.BOOLEAN(), nullable=False))
op.add_column('upload_predavanja', sa.Column('agree_news_cjvt', sa.BOOLEAN(), nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('upload_solar', sa.Column('corpus_name', sa.TEXT(), autoincrement=False, nullable=False))
op.rename_table('stamps_solar', 'stamps')
op.drop_table('corpus_access')
op.drop_table('contracts_solar')
op.drop_column('registered_user', 'institution_moderator')
# ### end Alembic commands ###

View File

@ -18,11 +18,12 @@ depends_on = None
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_column('upload_regular', 'corpus_name')
op.drop_table('upload_solar') op.drop_table('upload_solar')
op.drop_table('upload_predavanja')
op.drop_table('institution') op.drop_table('institution')
op.drop_table('stamps') op.drop_table('stamps')
op.drop_table('registered_user') op.drop_table('registered_user')
op.drop_column('upload_regular', 'corpus_name')
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@ -19,7 +19,7 @@ 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 from . model import db, UploadRegular, UploadSolar, RegisteredUser, CorpusAccess, Institution
ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'xml', 'mxliff', 'tmx'] ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'xml', 'mxliff', 'tmx']
@ -233,3 +233,15 @@ class UploadHandler:
match = db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).one() match = db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).one()
return match.institution return match.institution
def has_user_corpus_access(user_id, corpus_name):
user = RegisteredUser.query.filter_by(id=user_id).first()
if user.role == 'admin':
return True
return CorpusAccess.query.filter_by(user_id=user.id, corpus=corpus_name).first() is not None
def get_user_obj(user_id):
return RegisteredUser.query.filter_by(id=user_id).first()
def get_institution_obj(institution_id):
return Institution.query.filter_by(id=institution_id).first()

View File

@ -16,70 +16,99 @@ db = SQLAlchemy()
class UploadRegular(db.Model): class UploadRegular(db.Model):
__tablename__ = 'upload_regular' __tablename__ = 'upload_regular'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
upload_hash = db.Column(db.String) upload_hash = db.Column(db.String, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
name = db.Column(db.String) name = db.Column(db.String, nullable=False)
org = db.Column(db.String) org = db.Column(db.String, nullable=True)
address = db.Column(db.String) address = db.Column(db.String, nullable=True)
zipcode = db.Column(db.String) zipcode = db.Column(db.String, nullable=True)
email = db.Column(db.String) email = db.Column(db.String, nullable=False)
file_contract = db.Column(db.String) file_contract = db.Column(db.String, nullable=True)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String)) upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String), nullable=True)
corpus_name = db.Column(db.String) corpus_name = db.Column(db.String, nullable=False)
class UploadPredavanja(db.Model): class UploadPredavanja(db.Model):
__tablename__ = 'upload_predavanja' __tablename__ = 'upload_predavanja'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
upload_hash = db.Column(db.String) upload_hash = db.Column(db.String, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
name = db.Column(db.String) name = db.Column(db.String, nullable=False)
address = db.Column(db.String) address = db.Column(db.String, nullable=False)
subject = db.Column(db.String) subject = db.Column(db.String, nullable=False)
faculty = db.Column(db.String) faculty = db.Column(db.String, nullable=False)
email = db.Column(db.String) email = db.Column(db.String, nullable=False)
phone = db.Column(db.String) phone = db.Column(db.String, nullable=True)
keywords = db.Column(db.String) keywords = db.Column(db.String, nullable=False)
agree_publish = db.Column(db.Boolean) agree_publish = db.Column(db.Boolean, nullable=False)
file_contract = db.Column(db.String) agree_publish_future = db.Column(db.String, nullable=False)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String)) agree_machine_translation = db.Column(db.Boolean, default=False, nullable=False)
agree_news_cjvt = db.Column(db.Boolean, default=False, nullable=False)
file_contract = db.Column(db.String, nullable=True)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String), nullable=True)
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)
upload_user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id')) upload_user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id')) institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=True)
upload_hash = db.Column(db.String) upload_hash = db.Column(db.String, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
program = db.Column(db.String) program = db.Column(db.String, nullable=True)
subject = db.Column(db.String) subject = db.Column(db.String, nullable=True)
subject_custom = db.Column(db.String) subject_custom = db.Column(db.String, nullable=True)
grade = db.Column(db.Integer) grade = db.Column(db.Integer, nullable=True)
text_type = db.Column(db.String) text_type = db.Column(db.String, nullable=True)
text_type_custom = db.Column(db.String) text_type_custom = db.Column(db.String, nullable=True)
school_year = db.Column(db.String) school_year = db.Column(db.String, nullable=True)
grammar_corrections = db.Column(db.String) grammar_corrections = db.Column(db.String, nullable=True)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String)) upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String), nullable=True)
class StampsSolar(db.Model):
__tablename__ = 'stamps_solar'
id = db.Column(db.Integer, primary_key=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=True)
name = db.Column(db.String, nullable=False)
file_logo = db.Column(db.String, nullable=True)
class ContractsSolar(db.Model):
__tablename__ = 'contracts_solar'
id = db.Column(db.Integer, primary_key=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False)
upload_user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=True)
timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
file_contract = 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) name = db.Column(db.String, nullable=False)
email = db.Column(db.String) email = db.Column(db.String, nullable=False)
role = db.Column(db.String) role = db.Column(db.String, nullable=False)
pass_hash = db.Column(db.String) pass_hash = db.Column(db.String, nullable=False)
active = db.Column(db.Boolean) active = db.Column(db.Boolean, nullable=True)
last_login = db.Column(db.DateTime) last_login = db.Column(db.DateTime, nullable=True)
registered = db.Column(db.DateTime) registered = db.Column(db.DateTime, nullable=True)
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id')) institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=True)
institution_moderator = db.Column(db.Boolean, default=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) name = db.Column(db.String, nullable=False)
region = db.Column(db.String) region = db.Column(db.String, nullable=False)
file_contract = db.Column(db.String) file_contract = db.Column(db.String, nullable=True)

View File

@ -54,6 +54,9 @@ class UploadHandlerPredavanja(UploadHandler):
phone=form_data.get('phone'), phone=form_data.get('phone'),
keywords=form_data['kljucne-besede'], keywords=form_data['kljucne-besede'],
agree_publish=True if 'kljucne-besde' in form_data else False, agree_publish=True if 'kljucne-besde' in form_data else False,
agree_publish_future=form_data['javna-objava-prihodnost'],
agree_machine_translation=True if 'strojno-prevajanje' in form_data else False,
agree_news_cjvt=True if 'obvestila' in form_data else False,
file_contract=upload_metadata['contract_file'], file_contract=upload_metadata['contract_file'],
upload_file_hashes=sorted_f_hashes, upload_file_hashes=sorted_f_hashes,
) )

View File

@ -1,10 +1,12 @@
import logging import logging
import re import re
import traceback
import hashlib
from datetime import datetime from datetime import datetime
from sqlalchemy import desc
import portal.base
from portal.base import UploadHandler from portal.base import UploadHandler
from portal.model import UploadSolar from portal.model import db, UploadSolar, ContractsSolar, RegisteredUser, Institution
VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'} VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'}
@ -27,29 +29,25 @@ class UploadHandlerSolar(UploadHandler):
institution_id = UploadHandler.get_user_institution(user_id) institution_id = UploadHandler.get_user_institution(user_id)
try: model_obj = UploadSolar(
model_obj = UploadSolar( upload_user = user_id,
upload_user = user_id, institution = institution_id,
institution = institution_id, upload_hash=upload_metadata['upload_id'],
upload_hash=upload_metadata['upload_id'], timestamp=timestamp,
timestamp=timestamp, program=form_data['program'],
program=form_data['program'], subject=form_data['predmet'],
subject=form_data['predmet'], subject_custom=form_data['predmet-custom'],
subject_custom=form_data['predmet-custom'], grade=form_data['letnik'],
grade=form_data['letnik'], text_type=form_data['vrsta'],
text_type=form_data['vrsta'], text_type_custom=form_data['vrsta-custom'],
text_type_custom=form_data['vrsta-custom'], school_year=form_data['solsko-leto'],
school_year=form_data['solsko-leto'], 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)
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def handle_upload(self, request, user_id): def handle_upload(self, request, user_id):
err = portal.base.check_upload_request(request, self) err = self.check_upload_request(request)
if err: if err:
return err, 400 return err, 400
@ -74,6 +72,57 @@ 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):
contracts_type = request.form['tip-pogodbe']
if contracts_type not in ['sola', 'ucenci-starsi']:
return 'Neveljaven tip pogodbe.'
f_obj = None
for key, f in request.files.items():
if key.startswith('file'):
mimetype = f.content_type
if mimetype != 'application/pdf':
return 'Datoteka "{}" ni formata PDF.'.format(f.filename)
else:
f_obj = f
break
if not f_obj:
return 'Niste naložili nobene datoteke.'
base = self.get_uploads_subdir('contracts')
f_hash = hashlib.md5(f_obj.read())
# 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:] + '.pdf'
f_obj.save(path)
user_obj = RegisteredUser.query.filter_by(id=user_id).one()
institution_id = user_obj.institution
is_institution_moderator = user_obj.institution_moderator
if institution_id is None:
return 'Vaš uporabnik ni dodeljen nobeni inštituciji.'
if contracts_type == 'sola':
if not is_institution_moderator:
return 'Vaš uporabnik nima pravic za nalaganje pogodbe s šolo.'
Institution.update().values(file_contract=f_hash).where(id=institution_id)
model_obj = ContractsSolar(
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.'
@staticmethod @staticmethod
def check_form(form): def check_form(form):
program = form['program'] program = form['program']
@ -99,3 +148,18 @@ class UploadHandlerSolar(UploadHandler):
for key, val in form.items(): for key, val in form.items():
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 get_upload_history(user_id, n=20):
return UploadSolar.query.filter_by(upload_user=user_id).order_by(desc(UploadSolar.timestamp)).limit(n).all()
def get_institution_student_contracts(institution_id):
return ContractsSolar.query.filter_by(id=institution_id, contract_type='ucenci-starsi').all()
def get_institution_contract(institution_id):
return ContractsSolar.query.filter_by(id=institution_id, contract_type='sola').first()

View File

@ -456,3 +456,51 @@ select {
top: 100% !important; top: 100% !important;
left: 25% !important; left: 25% !important;
} }
.history-item-date {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 12px;
/* identical to box height */
text-transform: uppercase;
color: #46535b;
}
.history-item-uploader {
height: 19px;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 19px;
/* identical to box height */
float: left;
color: #46535B;
}
.history-item-filecount {
height: 19px;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 19px;
/* identical to box height */
text-align: right;
color: #006CB7;
}
.history-item-desc {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 14px;
color: #848C91;
}

View File

@ -4,24 +4,24 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Portal za oddajanje besedil</title> <title>Portal za oddajanje besedil</title>
<!--{{ dropzone.load_css() }}--> <!--{{ dropzone.load_css() }}-->
<link rel="stylesheet" href="static/dropzone.css" type="text/css"> <link rel="stylesheet" href="/static/dropzone.css" type="text/css">
{{ dropzone.style('position: absolute; {{ dropzone.style('position: absolute;
top: -0.5px; top: -0.5px;
width: 388px; width: 388px;
height: 732px; height: 1232px;
left: 385px; 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%); 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); box-shadow: 20px 4px 40px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
border: 0px; border: 0px;
border-radius: 0px 20px 20px 0px;') }} border-radius: 0px 20px 20px 0px;') }}
<link rel="stylesheet" href="static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
</head> </head>
<body> <body>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1" style="height: 1231px;">
<div id="logo-container"> <div id="logo-container" style="top: -5.4%;">
<img src="static/image/logo.svg" alt="logo"/> <img src="/static/image/logo.svg" alt="logo"/>
</div> </div>
<form id="my-dropzone" class="dropzone"> <form id="my-dropzone" class="dropzone">
@ -55,9 +55,33 @@
<input style="width: 10%;" type="checkbox" name="javna-objava" value="javna-objava" checked> <input style="width: 10%;" type="checkbox" name="javna-objava" value="javna-objava" checked>
</div> </div>
<div style="display: flex; flex-direction: row; justify-content: left; align-items: center; width: 310px;">
<label>Ali bi se v prihodnosti strinjali z javno objavo posnetka? (V primeru strinjanja bi podpisali poseben dogovor o pogojih objave.)</label>
<div style="display: inline-block;">
<input type="radio" name="javna-objava-prihodnost" value="da" style="display: inline; float: left; width: 20px;" checked>
<label for="da" style="display: inline; float: right; position: absolute; margin-top: 5px;">Da</label><br>
<input type="radio" name="javna-objava-prihodnost" value="morda" style="display: inline; float: left; width: 20px;">
<label for="morda" style="display: inline; float: right; position: absolute; margin-top: 10px;">Morda</label><br>
<input type="radio" name="javna-objava-prihodnost" value="ne" style="display: inline; float: left; width: 20px;">
<label for="ne" style="display: inline; float: right; position: absolute; margin-top: 18px;">Ne</label><br>
</div>
</div>
<br>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label style="width: 95%">Strinjam se, da Univerza v Ljubljani uporabi posnetek naloženega predavanja v okviru projekta za strojno prevajanje predavanj ON. Dostop do posnetka bodo imeli izključno sodelavci projekta za namen transkripcije govora.</label>
<input style="width: 5%;" type="checkbox" name="strojno-prevajanje" value="strojno-prevajanje" checked>
</div>
<br>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label style="width: 95%">Želim, da me Center za jezikovne vire in tehnologije UL obvešča o novicah v zvezi s sistemom za strojno prevajanje predavanj ON.</label>
<input style="width: 5%;" type="checkbox" name="obvestila" value="obvestila" checked>
</div>
<br>
<div class="form-text">Univerza v Ljubljani, Fakulteta za računalništvo in informatiko, Večna pot 113, 1000 Ljubljana, osebne podatke potrebuje zaradi izvedbe zgoraj navedenega projekta in komuniciranja v zvezi z njim (pravna podlaga je 6/1(b) člen GDPR). Podatke bomo hranili, dokler bo podano vaše soglasje za obveščanje. Če ste se strinjali, da vam pošiljamo obvestila, ste nam dali svojo osebno privolitev (6/1(a) člen GDPR). V tem primeru bomo vaše osebne podatke hranili do preklica privolitve. Privolitev lahko kadar koli prekličete s sporočilom na elektronski naslov info@cjvt.si. Preklic privolitve ne vpliva na zakonitost obdelave podatkov, ki se je izvajala do preklica. Obveščamo vas, da lahko kadar koli uveljavljate pravico do dostopa do svojih osebnih podatkov, popravek, izbris (v primeru osebne privolitve), pravico do omejitve obdelave (v primerih, določenih z GDPR) in pravico do prenosljivosti podatkov. Za vprašanja v zvezi z varstvom osebnih podatkov se lahko obrnete na pooblaščeno osebo za varstvo podatkov Univerze v Ljubljani na elektronski naslov dpo@uni-lj.si. Če boste menili, da vaših pravic ne uresničujemo ustrezno, se lahko pritožite Informacijskemu pooblaščencu RS (ip-rs.si).</div>
<div class="form-text">*Po kliku na gumb “Oddaj” se bo prikazala vsebina pogodobe 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> <div class="form-text">*Po kliku na gumb “Oddaj” se bo prikazala vsebina pogodobe 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> <button id="button-submit" type="submit" style="top: 1150px;">Oddaj</button>
</div> </div>
<div class="dropzone-previews"></div> <div class="dropzone-previews"></div>
@ -173,7 +197,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
</div> </div>
<!--{{ dropzone.load_js() }}--> <!--{{ dropzone.load_js() }}-->
<script src="static/dropzone.js"></script> <script src="/static/dropzone.js"></script>
<script> <script>
///////////////////////// /////////////////////////
// Dropzone // // Dropzone //

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Portal za oddajanje besedil</title> <title>Portal za oddajanje besedil</title>
<!--{{ dropzone.load_css() }}--> <!--{{ dropzone.load_css() }}-->
<link rel="stylesheet" href="static/dropzone.css" type="text/css"> <link rel="stylesheet" href="/static/dropzone.css" type="text/css">
{{ dropzone.style('position: absolute; {{ dropzone.style('position: absolute;
top: -0.5px; top: -0.5px;
width: 388px; width: 388px;
@ -15,13 +15,13 @@
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
border: 0px; border: 0px;
border-radius: 0px 20px 20px 0px;') }} border-radius: 0px 20px 20px 0px;') }}
<link rel="stylesheet" href="static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
</head> </head>
<body> <body>
<div id="main-window"> <div id="main-window">
<div id="rect1"> <div id="rect1">
<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>
<form id="my-dropzone" class="dropzone"> <form id="my-dropzone" class="dropzone">
@ -165,7 +165,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
</div> </div>
<!--{{ dropzone.load_js() }}--> <!--{{ dropzone.load_js() }}-->
<script src="static/dropzone.js"></script> <script src="/static/dropzone.js"></script>
<script> <script>
///////////////////////// /////////////////////////
// Dropzone // // Dropzone //

View File

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

View File

@ -29,9 +29,9 @@
<h1 id="title" style="font-size: 25px;">Korpus ŠOLAR</h1> <h1 id="title" style="font-size: 25px;">Korpus ŠOLAR</h1>
<div class="selection-tabs"> <div class="selection-tabs">
<button class="selection-tab-button selected">ODDAJA</button> <button id="button-oddaja" class="selection-tab-button selected">ODDAJA</button>
<button class="selection-tab-button">ZGODOVINA</button> <button id="button-zgodovina" class="selection-tab-button">ZGODOVINA</button>
<button class="selection-tab-button">POGODBE</button> <button id="button-pogodbe" class="selection-tab-button">POGODBE</button>
</div> </div>
<label for="program">PROGRAM</label> <label for="program">PROGRAM</label>
@ -225,6 +225,8 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
var btnSubmit = document.getElementById("button-submit"); var btnSubmit = document.getElementById("button-submit");
var btnSubmitFinal = document.getElementById("button-submit-final"); var btnSubmitFinal = document.getElementById("button-submit-final");
var btnSubmitCancel = document.getElementById("button-submit-cancel"); var btnSubmitCancel = document.getElementById("button-submit-cancel");
var btnZgodovina = document.getElementById("button-zgodovina");
var btnPogodbe = document.getElementById("button-pogodbe");
var elemTermsPopup = document.getElementById("popup-terms"); var elemTermsPopup = document.getElementById("popup-terms");
var termsScrollbox = document.getElementById("popup-terms-text"); var termsScrollbox = document.getElementById("popup-terms-text");
var scrollboxTriggered = false; var scrollboxTriggered = false;
@ -302,12 +304,12 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
btnSubmit.disabled = true; btnSubmit.disabled = true;
btnSubmitFinal.disabled = true; btnSubmitFinal.disabled = true;
elemTermsPopup.style.display = "inline"; elemTermsPopup.style.display = "inline";
scrollboxTriggered = false; scrollboxtriggered = false;
} }
}); });
// First change the button to actually tell Dropzone to process the queue. // First change the button to actually tell dropzone to process the queue.
btnSubmitFinal.addEventListener("click", function(e) { btnSubmitFinal.addEventListener("click", function(e) {
// Hand off data to dropzone // Hand off data to dropzone
dz.processQueue(); dz.processQueue();
@ -325,6 +327,18 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
elemTermsPopup.style.display = "none"; elemTermsPopup.style.display = "none";
}); });
btnZgodovina.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
window.location.replace("/solar/zgodovina");
});
btnPogodbe.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
window.location.replace("/solar/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.
function checkScrollboxTrigger(event) { function checkScrollboxTrigger(event) {
var element = event.target; var element = event.target;
@ -362,11 +376,9 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
// Gets triggered when there was an error sending the files. // Gets triggered when there was an error sending the files.
// Maybe show form again, and notify user of error // Maybe show form again, and notify user of error
}); });
} }
} }
</script> </script>
<script>
</script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal za oddajanje besedil</title>
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div class="bg"></div>
<div id="main-window">
<div id="rect1">
<div style="padding: 20px;">
<div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<h1 id="title" style="font-size: 25px; position: relative;">Korpus ŠOLAR</h1>
<div class="selection-tabs">
<button onclick="window.location.replace('/solar/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('/solar/pogodbe');" class="selection-tab-button selected">POGODBE</button>
</div>
</div>
<div id="conract-container" style="padding: 20px;">
{% if contract_school %}
<div class="contract-item" style="background-color: #ccffcc;">
<div class="contract-item-icon">...</div>
<div class="contract-item-title">Pogodba o prenosu lastništva</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>
</div>
</br>
{% endif %}
{% for item in contracts_students %}
<div class="contract-item">
<div class="contract-item-icon">...</div>
<div class="contract-item-title">Pogodba o prenosu lastništva</div>
<div class="contract-item-date">DODANO {{item.date}}</div>
<a href="/solar/pogodbe/{{ item.file_hash }}.pdf" class="contract-item-button">PRENESI</div>
</div>
</br>
{% endfor %}
</div>
{% if show_upload_form %}
<form action="/solar/pogodbe">
<input type="radio" id="sola" name="tip-pogodbe" value="sola">
<label for="sola">Pogodba s šolo</label><br>
<input type="radio" id="ucenci-starsi" name="tip-pogodbe" value="ucenci-starsi">
<label for="sola">Pogodba z učenci / starši</label><br>
<input type="file" id="file-contract" name="filename">
<input type="submit" text="Naloži pogodbo">
</form>
{% endif %}
</div>
<div id="rect2" class="mock-side">
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal za oddajanje besedil</title>
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div class="bg"></div>
<div id="main-window">
<div id="rect1">
<div style="padding: 20px;">
<div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<h1 id="title" style="font-size: 25px; position: relative;">Korpus ŠOLAR</h1>
<div class="selection-tabs">
<button onclick="window.location.replace('/solar/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('/solar/pogodbe');" class="selection-tab-button">POGODBE</button>
</div>
</div>
<div id="history-container" style="padding: 20px;">
{% for item in upload_history %}
<div class="history-item">
<div class="history-item-date">{{ item.timestamp }}</div>
<div class="history-item-uploader">{{ uploader_names[loop.index - 1] }}</div>
<div class="history-item-filecount">Št. datotek: {{ item.upload_file_hashes|length }}</div>
<div class="history-item-desc">
{% set began = False %}
{% if institution_names[loop.index - 1] %}
{% if began %}|{% endif %} {{ institution_names[loop.index - 1] }}
{% set began = True %}
{% endif %}
{% if item.program %}
{% if began %}|{% endif %} {{ item.program }}
{% set began = True %}
{% endif %}
{% if item.subject %}
{% if began %}|{% endif %} {{ item.subject }}
{% set began = True %}
{% endif %}
{% if item.subject_custom %}
{% if began %}|{% endif %} {{ item.subject_custom }}
{% set began = True %}
{% endif %}
{% if item.grade %}
{% if began %}|{% endif %} {{ item.grade }}
{% set began = True %}
{% endif %}
{% if item.text_type %}
{% if began %}|{% endif %} {{ item.text_type }}
{% set began = True %}
{% endif %}
{% if item.text_type_custom %}
{% if began %}|{% endif %} {{ item.text_type_custom }}
{% set began = True %}
{% endif %}
{% if item.school_year %}
{% if began %}|{% endif %} {{ item.school_year }}
{% set began = True %}
{% endif %}
{% if item.grammar_corrections %}
{% if began %}|{% endif %} {{ item.grammar_corrections }}
{% set began = True %}
{% endif %}
</div>
</div>
</br>
{% endfor %}
</div>
</div>
<div id="rect2" class="mock-side">
</div>
</div>
</body>
</html>