Added predavanja, restructure, progress on solar.

This commit is contained in:
msinkec 2021-05-17 14:33:53 +02:00
parent 51b1237b5f
commit 9acce4a8e9
17 changed files with 1058 additions and 329 deletions

View File

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

83
app.py
View File

@ -1,7 +1,6 @@
import logging
import os
import configparser
import re
from pathlib import Path
from werkzeug.security import check_password_hash
@ -14,6 +13,8 @@ from portal.model import db, RegisteredUser
import portal.base
import portal.solar
import portal.regular
import portal.predavanja
# TODO: Implement user registration.
@ -45,6 +46,7 @@ MAIL_BODY = config['MAIL_BODY']
SQL_CONN_STR = config['SQL_CONN_STR']
DESC_PREVODI = config['DESC_PREVODI']
DESC_GIGAFIDA = config['DESC_GIGAFIDA']
DESC_PREDAVANJA = config['DESC_PREDAVANJA']
if 'UPLOADS_DIR' in config:
UPLOADS_DIR = Path(config['UPLOADS_DIR'])
@ -85,7 +87,8 @@ if 'PORTALDS4DS1_DESC_PREVODI' in os.environ:
if 'PORTALDS4DS1_DESC_GIGAFIDA' in os.environ:
DESC_GIGAFIDA = os.environ['PORTALDS4DS1_DESC_GIGAFIDA']
VALID_CORPUS_NAMES = ['prevodi', 'gigafida', 'solar']
ENABLED_CORPUSES = ['prevodi', 'gigafida', 'solar', 'predavanja']
CORPUSES_LOGIN_REQUIRED = ['solar']
######################
@ -110,7 +113,33 @@ manager.add_command('db', MigrateCommand)
# Set up dropzone.js to serve all the stuff for "file dropping" on the web interface.
dropzone = Dropzone(app)
upload_handler = portal.base.UploadHandler(
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
)
upload_handler_solar = portal.solar.UploadHandlerSolar(
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
)
upload_handler_predavanja = portal.predavanja.UploadHandlerPredavanja(
UPLOADS_DIR=UPLOADS_DIR,
MAIL_HOST=MAIL_HOST,
MAIL_LOGIN=MAIL_LOGIN,
@ -141,13 +170,15 @@ def index():
@app.route('/<corpus_name>')
def index_corpus(corpus_name):
if corpus_name not in VALID_CORPUS_NAMES:
if corpus_name not in ENABLED_CORPUSES:
return 'Korpus "{}" ne obstaja.'.format(corpus_name), 404
if corpus_name == 'prevodi':
description = DESC_PREVODI
elif corpus_name == 'gigafida':
description = DESC_GIGAFIDA
elif corpus_name == 'predavanja':
return render_template('basic-predavanja.html', description=DESC_PREDAVANJA, max_files=MAX_FILES_PER_UPLOAD)
elif corpus_name == 'solar':
if current_user.is_authenticated:
return redirect('/solar/oddaja')
@ -164,11 +195,16 @@ def load_user(user_id):
@app.route('/<corpus_name>/login')
def login_get(corpus_name):
return render_template('login.html', corpus_name=corpus_name)
if corpus_name == 'solar':
return render_template('login.html', corpus_name=corpus_name, title='ŠOLAR')
return 404
@app.route('/<corpus_name>/login', methods=['POST'])
def login_post(corpus_name):
if corpus_name not in ENABLED_CORPUSES or corpus_name not in CORPUSES_LOGIN_REQUIRED:
return 404
email = request.form.get('email')
password = request.form.get('password')
remember = True if request.form.get('remember') else False
@ -189,35 +225,48 @@ def login_post(corpus_name):
if corpus_name == 'solar':
return redirect('/solar/oddaja')
return redirect('/{}/home'.format(corpus_name))
@app.route('/<corpus_name>/home')
@login_required
def profile(corpus_name):
return render_template('login.html', corpus_name=corpus_name)
return 404
# TODO: Move solar stuff to seperate file using Flask blueprints.
@app.route('/solar/oddaja')
@login_required
def solar_oddaja():
return render_template('solar-oddaja.html')
@app.route('/solar/zgodvina')
@login_required
def solar_zgodovina():
return render_template('solar-zgodovina.html')
@app.route('/solar/pogodbe')
@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'])
def handle_upload(corpus_name):
if corpus_name not in VALID_CORPUS_NAMES:
if corpus_name not in ENABLED_CORPUSES:
return 404
if corpus_name == 'solar':
if current_user.is_authenticated:
return portal.solar.handle_upload(request, upload_handler)
return upload_handler_solar.handle_upload(request, current_user.get_id())
return 404
elif corpus_name == 'predavanja':
return upload_handler_predavanja.handle_upload(request)
else:
return portal.base.handle_upload_unauthenticated(request, corpus_name)
return upload_handler_regular.handle_upload(request, corpus_name)
if __name__ == '__main__':

View File

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

30
contract/predavanja.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
table {
width: 80%;
margin: 0 auto;
}
</style>
</head>
<body>
{{ime_priimek}}
</body>
<p style="text-align: center;"><b>Priloga k pogodbi o prenosu avtorskih pravic: seznam avtorskih del, ki so predmet pogodbe</b></p>
<div style="width: 100%;">
<table>
<tr>
<td style="text-align: center;"><b>Ime, naslov ali oznaka dela</b></td>
</tr>
{{files_table_str}}
</table>
</div>
</html>

View File

@ -18,15 +18,15 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('upload_unauthenticated',
op.create_table('upload_regular',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('upload_hash', sa.String(), nullable=True),
sa.Column('timestamp', sa.DateTime(), nullable=True),
sa.Column('form_name', sa.String(), nullable=True),
sa.Column('form_org', sa.String(), nullable=True),
sa.Column('form_address', sa.String(), nullable=True),
sa.Column('form_zipcode', sa.String(), nullable=True),
sa.Column('form_email', sa.String(), nullable=True),
sa.Column('upload_hash', sa.String(), nullable=False),
sa.Column('timestamp', sa.DateTime(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('org', sa.String(), nullable=True),
sa.Column('address', sa.String(), nullable=True),
sa.Column('zipcode', sa.String(), nullable=True),
sa.Column('email', sa.String(), nullable=False),
sa.Column('file_contract', sa.String(), nullable=True),
sa.Column('upload_file_hashes', sa.ARRAY(sa.String()), nullable=True),
sa.PrimaryKeyConstraint('id')
@ -36,5 +36,5 @@ def upgrade():
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('upload_unauthenticated')
op.drop_table('upload_regular')
# ### end Alembic commands ###

View File

@ -22,13 +22,13 @@ def downgrade():
op.drop_table('institution')
op.drop_table('stamps')
op.drop_table('registered_user')
op.drop_column('upload_unauthenticated', 'corpus_name')
op.drop_column('upload_regular', 'corpus_name')
# ### end Alembic commands ###
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('upload_unauthenticated', sa.Column('corpus_name', sa.TEXT(), autoincrement=False, nullable=False))
op.add_column('upload_regular', sa.Column('corpus_name', sa.TEXT(), autoincrement=False, nullable=False))
op.create_table('institution',
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('institution_id_seq'::regclass)"), autoincrement=True, nullable=False),
sa.Column('name', sa.TEXT(), autoincrement=False, nullable=False),
@ -66,12 +66,14 @@ def upgrade():
sa.Column('upload_hash', sa.TEXT(), autoincrement=False, nullable=False),
sa.Column('timestamp', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
sa.Column('corpus_name', sa.TEXT(), autoincrement=False, nullable=False),
sa.Column('form_program', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('form_subject', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('form_grade', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('form_text_type', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('form_school_year', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('form_grammar_corrections', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('program', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('subject', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('subject_custom', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('grade', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('text_type', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('text_type_custom', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('school_year', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('grammar_corrections', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('upload_file_hashes', sa.ARRAY(sa.String()), nullable=True),
sa.ForeignKeyConstraint(['upload_user'], ['registered_user.id'], name='upload_upload_user_fkey'),
sa.ForeignKeyConstraint(['institution'], ['institution.id'], name='upload_institution_fkey'),
@ -80,4 +82,22 @@ def upgrade():
# Insert default admin user with username "admin" and pass "portal-admin".
op.execute('INSERT INTO registered_user(name, email, role, pass_hash, active) VALUES (\'admin\', \'admin@cjvt.si\', \'admin\', \'pbkdf2:sha256:150000$aPRDrEqF$f27256d6d57001770feb9e7012ea27252f4a3e5ea9989931368e466d798679ff\', TRUE);')
op.create_table('upload_predavanja',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('upload_hash', sa.String(), nullable=False),
sa.Column('timestamp', sa.DateTime(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('address', sa.String(), nullable=False),
sa.Column('subject', sa.String(), nullable=False),
sa.Column('faculty', sa.String(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('phone', sa.String(), nullable=True),
sa.Column('keywords', sa.String(), nullable=False),
sa.Column('agree_publish', sa.Boolean(), nullable=False),
sa.Column('file_contract', sa.String(), nullable=True),
sa.Column('upload_file_hashes', sa.ARRAY(sa.String()), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###

View File

@ -2,6 +2,7 @@ import hashlib
import time
import ssl
import traceback
import re
from pathlib import Path
from datetime import datetime
@ -18,19 +19,22 @@ from email.mime.application import MIMEApplication
import pdfkit
from jinja2 import Environment, FileSystemLoader
from . model import db, UploadUnauthenticated, UploadSolar
from . model import db, UploadRegular, UploadSolar, RegisteredUser
ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'xml', 'mxliff', 'tmx']
REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$')
MAX_FNAME_LEN = 100
class ContractCreator:
def __init__(self):
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('contract/template.html')
self.template = template_env.get_template(template_path)
self.pdfkit_options = {
'page-size': 'A4',
@ -47,7 +51,11 @@ class ContractCreator:
def fill_template(self, **kwargs):
return self.template.render(**kwargs)
def create_pdf(self, out_f, fields_dict):
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)
@ -57,16 +65,25 @@ class UploadHandler:
def __init__(self, **kwargs):
self.config = kwargs
self.contract_creator = ContractCreator()
def set_contract_creator(self, contract_creator):
assert isinstance(contract_creator, ContractCreator)
self._contract_creator = contract_creator
def extract_upload_metadata(self, corpus_name, request):
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 = self.create_file_hashes(request.files)
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 = self.create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes)
upload_id = UploadHandler.create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes)
upload_metadata['corpus_name'] = corpus_name
upload_metadata['form_data'] = form_data
@ -78,14 +95,8 @@ class UploadHandler:
return upload_metadata
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
def create_upload_id(self, corpus_name, form_data, upload_timestamp, file_hashes):
@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):
@ -102,7 +113,8 @@ class UploadHandler:
return metahash
def create_file_hashes(self, files):
@staticmethod
def create_file_hashes(files):
res = dict()
for key, f in files.items():
if key.startswith('file'):
@ -112,57 +124,14 @@ class UploadHandler:
f.seek(0)
return res
def store_metadata_unauthenticated(self, upload_metadata):
timestamp = datetime.fromtimestamp(upload_metadata['timestamp'])
form_data = upload_metadata['form_data']
file_hashes = upload_metadata['file_hashes_dict']
sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort()
@staticmethod
def store_model(self, model_obj):
try:
upload_unauthenticated = UploadUnauthenticated(
upload_hash=upload_metadata['upload_id'],
timestamp=timestamp,
form_name=form_data['ime'],
form_org=form_data['podjetje'],
form_address=form_data['naslov'],
form_zipcode=form_data['posta'],
form_email=form_data['email'],
file_contract=upload_metadata['contract_file'],
upload_file_hashes=sorted_f_hashes,
corpus_name=todo
)
db.session.add(upload_unauthenticated)
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def store_metadata_solar(self, upload_metadata):
timestamp = datetime.fromtimestamp(upload_metadata['timestamp'])
form_data = upload_metadata['form_data']
file_hashes = upload_metadata['file_hashes_dict']
sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort()
try:
upload_solar = UploadSolar(
upload_user = todo,
institution = todo,
upload_hash=upload_metadata['upload_id'],
timestamp=timestamp,
form_program=form_data['program'],
form_subject=form_data['subject'],
form_grade=form_data['grade'],
form_text_type=form_data['text_type'],
form_school_year=form_data['school_year'],
form_grammar_corrections=form_data['grammar_corrections'],
upload_file_hashes=sorted_f_hashes
)
db.session.add(upload_unauthenticated)
db.session.commit()
except Exception:
traceback.print_exc()
def store_datafiles(self, files, upload_metadata):
base = self.get_uploads_subdir('files')
@ -170,32 +139,18 @@ class UploadHandler:
for key, f in files.items():
if key.startswith('file'):
path = base / file_hashes[f.filename]
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 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['naslov'],
'posta': form_data['posta'],
'kontakt_narocnik': self.config['CONTRACT_CLIENT_CONTACT'],
'kontakt_imetnikpravic': form_data['ime'],
'files_table_str': files_table_str
}
self.contract_creator.create_pdf(base / upload_metadata['contract_file'], data)
def send_confirm_mail(self, upload_metadata):
upload_id = upload_metadata['upload_id']
@ -207,15 +162,16 @@ class UploadHandler:
body = self.config['MAIL_BODY'].format(upload_id=upload_id)
message.attach(MIMEText(body, "plain"))
contracts_dir = self.get_uploads_subdir('contracts')
base_name = upload_metadata['contract_file']
contract_file = contracts_dir / base_name
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 = base_name
Name = f_name
)
part['Content-Disposition'] = 'attachment; filename="%s"' % base_name
part['Content-Disposition'] = 'attachment; filename="%s"' % f_name
message.attach(part)
text = message.as_string()
@ -223,6 +179,7 @@ class UploadHandler:
# Create a secure SSL context
context = ssl.create_default_context()
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)
@ -232,44 +189,11 @@ class UploadHandler:
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 handle_upload_unauthenticated(request, corpus_name, upload_handler):
files = request.files
if len(files) > upload_handler.MAX_FILES_PER_UPLOAD:
return 'Naložite lahko do {} datotek hkrati.'.format(upload_handler.MAX_FILES_PER_UPLOAD), 400
elif len(files) < 1:
return 'Priložena ni bila nobena datoteka.', 400
err = check_suffixes(files)
if err:
return err, 400
err = check_form(request.form)
if err:
return err, 400
# Parse request.
upload_metadata = upload_handler.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.
upload_handler.generate_upload_contract_pdf(upload_metadata)
# Store uploaded files to disk.
upload_handler.store_datafiles(files, upload_metadata)
# Store metadata to database.
upload_handler.store_metadata_unauthenticated(upload_metadata)
# Send confirmation mail along with the contract to the submitted email address.
upload_handler.send_confirm_mail(upload_metadata)
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(files))
@staticmethod
def check_suffixes(files):
for key, f in files.items():
if key.startswith('file'):
@ -278,34 +202,34 @@ def check_suffixes(files):
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_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')
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
if len(ime) > 100:
return 'Predolgo ime.'
err = UploadHandler.check_suffixes(files)
if err:
return err, 400
if len(podjetje) > 100:
return 'Predolgo ime institucije.'
if len(email) > 100:
return 'Predolgi email naslov'
elif not re.search(REGEX_EMAIL, email):
return 'Email napačnega formata.'
if len(telefon) > 100:
return 'Predolga telefonska št.'
if len(naslov) > 100:
return 'Predolg naslov.'
if len(posta) > 100:
return 'Predolga pošta'
err = UploadHandler.check_fname_lengths(files)
if err:
return err, 400
return None
@staticmethod
def get_user_institution(user_id):
match = db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).one()
return match.institution

View File

@ -12,22 +12,39 @@ from flask_login import UserMixin
db = SQLAlchemy()
# Entries for uploads to corpora, that have no authentication. E.g. "prevodi" or "gigafida".
class UploadUnauthenticated(db.Model):
__tablename__ = 'upload_unauthenticated'
# "prevodi" or "gigafida".
class UploadRegular(db.Model):
__tablename__ = 'upload_regular'
id = db.Column(db.Integer, primary_key=True)
upload_hash = db.Column(db.String)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
form_name = db.Column(db.String)
form_org = db.Column(db.String)
form_address = db.Column(db.String)
form_zipcode = db.Column(db.String)
form_email = db.Column(db.String)
name = db.Column(db.String)
org = db.Column(db.String)
address = db.Column(db.String)
zipcode = db.Column(db.String)
email = db.Column(db.String)
file_contract = db.Column(db.String)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))
corpus_name = db.Column(db.String)
class UploadPredavanja(db.Model):
__tablename__ = 'upload_predavanja'
id = db.Column(db.Integer, primary_key=True)
upload_hash = db.Column(db.String)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
name = db.Column(db.String)
address = db.Column(db.String)
subject = db.Column(db.String)
faculty = db.Column(db.String)
email = db.Column(db.String)
phone = db.Column(db.String)
keywords = db.Column(db.String)
agree_publish = db.Column(db.Boolean)
file_contract = db.Column(db.String)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))
class UploadSolar(db.Model):
__tablename__ = 'upload_solar'
id = db.Column(db.Integer, primary_key=True)
@ -35,13 +52,14 @@ class UploadSolar(db.Model):
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'))
upload_hash = db.Column(db.String)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
corpus_name = db.Column(db.String)
form_program = db.Column(db.String)
form_subject = db.Column(db.String)
form_grade = db.Column(db.Integer)
form_text_type = db.Column(db.String)
form_school_year = db.Column(db.String)
form_grammar_corrections = db.Column(db.String)
program = db.Column(db.String)
subject = db.Column(db.String)
subject_custom = db.Column(db.String)
grade = db.Column(db.Integer)
text_type = db.Column(db.String)
text_type_custom = db.Column(db.String)
school_year = db.Column(db.String)
grammar_corrections = db.Column(db.String)
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))

123
portal/predavanja.py Normal file
View File

@ -0,0 +1,123 @@
import logging
import traceback
import re
from datetime import datetime
import portal.base
from portal.base import UploadHandler, ContractCreator, REGEX_EMAIL
from portal.model import db, UploadPredavanja
MAXLEN_FORM = 150
class UploadHandlerPredavanja(UploadHandler):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.contract_creator = ContractCreator(base_path=self.get_uploads_subdir('contracts'),
template_path='contract/predavanja.html')
def generate_upload_contract_pdf(self, upload_metadata):
form_data = upload_metadata['form_data']
files_table_str = []
for file_name in upload_metadata['file_names']:
files_table_str.append('<tr><td style="text-align: center;">')
files_table_str.append(file_name)
files_table_str.append('</td></tr>')
files_table_str = ''.join(files_table_str)
data = {
'ime_priimek': form_data['ime'],
'files_table_str': files_table_str
}
self.contract_creator.create_pdf(upload_metadata['contract_file'], data)
@staticmethod
def store_metadata(upload_metadata):
timestamp = datetime.fromtimestamp(upload_metadata['timestamp'])
form_data = upload_metadata['form_data']
file_hashes = upload_metadata['file_hashes_dict']
sorted_f_hashes = list(file_hashes.values())
sorted_f_hashes.sort()
try:
model_obj = UploadPredavanja(
upload_hash=upload_metadata['upload_id'],
timestamp=timestamp,
name=form_data['ime'],
address=form_data['naslov-predavanja'],
subject=form_data['predmet'],
faculty=form_data['fakulteta'],
email=form_data['email'],
phone=form_data.get('phone'),
keywords=form_data['kljucne-besede'],
agree_publish=True if 'kljucne-besde' in form_data else False,
file_contract=upload_metadata['contract_file'],
upload_file_hashes=sorted_f_hashes,
)
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def handle_upload(self, request):
err = self.check_upload_request(request)
if err:
return err, 400
err = self.check_form(request.form)
if err:
return err, 400
# Parse request.
upload_metadata = self.extract_upload_metadata('predavanja', request)
logging.info('Upload for "predavanja" with id "{}" supplied form data: {}'.format(
upload_metadata['upload_id'], str(upload_metadata['form_data'])))
# 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)
# Send confirmation mail along with the contract to the submitted email address.
self.send_confirm_mail(upload_metadata)
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files))
@staticmethod
def check_form(form):
name = form.get('ime')
address = form.get('naslov-predavanja')
subject = form.get('predmet')
faculty = form.get('fakulteta')
email = form.get('email')
phone = form.get('telefon')
keywords = form.get('kljucne-besede')
if not name \
or not address \
or not subject \
or not faculty \
or not email \
or not keywords:
return 'Izpolnite vsa obvezna polja.'
for keyword in keywords.split():
if not keyword.isalpha():
return 'Ključna beseda "{}" ni pravilnega formata.'.format(keyword)
if not re.search(REGEX_EMAIL, email):
return 'Email napačnega formata.'
for key, val in form.items():
if len(val) > MAXLEN_FORM:
return 'Polje "{}" presega dolžino {} znakov.'.format(key, MAXLEN_FORM)

130
portal/regular.py Normal file
View File

@ -0,0 +1,130 @@
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)
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

View File

@ -1,41 +1,101 @@
import logging
import re
from datetime import datetime
import portal.base
from portal.base import UploadHandler
from portal.model import UploadSolar
def handle_upload(request, upload_handler):
files = request.files
if len(files) > upload_handler.MAX_FILES_PER_UPLOAD:
return 'Naložite lahko do {} datotek hkrati.'.format(upload_handler.MAX_FILES_PER_UPLOAD), 400
elif len(files) < 1:
return 'Priložena ni bila nobena datoteka.', 400
VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'}
VALID_SUBJECTS = {'slo', 'drug-jez', 'drug-druz', 'drug-narav', 'drug-strok', 'drug-izb'}
VALID_TEXT_TYPES = {'esej-spis', 'prakticno', 'solski-test', 'delo-v-razredu'}
VALID_GRAMMAR_CORRECTIONS = {'popr-ne', 'brez-popr', 'popr-da'}
err = portal.base.check_suffixes(files)
MAXLEN_FORM = 150
class UploadHandlerSolar(UploadHandler):
@staticmethod
def store_metadata(upload_metadata, user_id):
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()
institution_id = UploadHandler.get_user_institution(user_id)
try:
model_obj = UploadSolar(
upload_user = user_id,
institution = institution_id,
upload_hash=upload_metadata['upload_id'],
timestamp=timestamp,
program=form_data['program'],
subject=form_data['predmet'],
subject_custom=form_data['predmet-custom'],
grade=form_data['letnik'],
text_type=form_data['vrsta'],
text_type_custom=form_data['vrsta-custom'],
school_year=form_data['solsko-leto'],
grammar_corrections=form_data['jezikovni-popravki'],
upload_file_hashes=sorted_f_hashes
)
db.session.add(model_obj)
db.session.commit()
except Exception:
traceback.print_exc()
def handle_upload(self, request, user_id):
err = portal.base.check_upload_request(request, self)
if err:
return err, 400
err = check_form(request.form)
err = self.check_form(request.form)
if err:
return err, 400
# Parse request.
upload_metadata = upload_handler.extract_upload_metadata(corpus_name, request)
upload_metadata = self.extract_upload_metadata('solar', request)
logging.info('Upload from user "{}" with upload id "{}" supplied form data: {}'.format(
request.user,
user_id,
upload_metadata['upload_id'],
str(upload_metadata['form_data']
)))
# Store uploaded files to disk.
upload_handler.store_datafiles(files, upload_metadata)
self.store_datafiles(request.files, upload_metadata)
# Store metadata to database.
upload_handler.store_metadata_solar(upload_metadata)
# Send confirmation mail along with the contract to the submitted email address.
upload_handler.send_confirm_mail(upload_metadata)
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(files))
# Store to database.
self.store_metadata(upload_metadata, user_id)
return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files))
@staticmethod
def check_form(form):
pass
program = form['program']
predmet = form['predmet']
letnik = int(form['letnik'])
vrsta = form['vrsta']
solsko_leto = form['solsko-leto']
jezikovni_popravki = form['jezikovni-popravki']
if program not in VALID_PROGRAMS:
return 'Invalid program "{}"'.format(program)
if predmet not in VALID_SUBJECTS:
return 'Invalid subject "{}"'.format(premdet)
if letnik < 1 or letnik > 9:
return 'Invalid grade: {}'.format(letnik)
if vrsta not in VALID_TEXT_TYPES:
return 'Invalid text type "{}"'.format(vrsta)
if not re.match('^\d{0,2}-\d{0,2}$', solsko_leto):
return 'Invalid school year "{}"'.format(solsko_leto)
if jezikovni_popravki not in VALID_GRAMMAR_CORRECTIONS:
return 'Invalid text type "{}"'.format(jezikovni_popravki)
for key, val in form.items():
if len(val) > MAXLEN_FORM:
return 'Value in form field "{}" exceeds max len of {}'.format(key, MAXLEN_FORM)

View File

@ -338,13 +338,12 @@ var _createClass = function() {
// TODO: find scrollbox eleemnt, if not exist, create one
var scrollbox = this.previewsContainer.querySelector(".scrollbox");
var scrollbox = this.previewsContainer.querySelector("#dropzone-scrollbox");
if (scrollbox == null) {
scrollbox = document.createElement("div");
scrollbox.setAttribute("class", "scrollbox");
scrollbox.setAttribute("id", "dropzone-scrollbox");
this.previewsContainer.appendChild(scrollbox);
}
console.log(scrollbox)
a.previewElement =
b.createElement(this.options.previewTemplate.trim()),

View File

@ -30,7 +30,6 @@ html {
overflow-y: scroll;
}
#main-window {
position: absolute;
top: 50%;
@ -43,7 +42,7 @@ html {
position: absolute;
top: -9%;
left: 35%;
background: #F5F5F5;
background: #f5f5f5;
border-radius: 50%;
padding-top: 5%;
padding-bottom: 5%;
@ -133,6 +132,30 @@ label {
margin: 0px 10px;
}
.button-general {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 10px 40px;
width: 138px;
height: 41px;
background: #006cb7;
border-radius: 29px;
border: 0px;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #ffffff;
flex: none;
order: 0;
flex-grow: 0;
}
.button-terms:enabled {
justify-content: center;
align-items: center;
@ -179,7 +202,6 @@ label {
margin-top: 15px;
}
input {
font-family: Roboto;
font-style: normal;
@ -193,19 +215,50 @@ input {
width: 100%;
}
.mock-side {
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;
}
select {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 19px;
color: #46535b;
background: #f5f5f5;
border: 0px;
border-bottom: 2px solid #c4c4c4;
width: 100%;
}
#izjava {
width: auto;
}
.scrollbox {
position: relative;
#dropzone-scrollbox {
position: absolute;
display: inline-block;
vertical-align: top;
margin-top: 26px;
margin-bottom: 26px;
min-height: 100px;
max-height: 670px;
top: -600px;
top: 0px;
overflow-x: hidden;
overflow-y: auto;
}
@ -336,7 +389,7 @@ input {
display: none;
}
.corpus-type-selector {
.selection-tabs {
width: 100%;
display: flex;
align-items: center;
@ -345,7 +398,7 @@ input {
margin-bottom: 20px;
}
.corpus-type-button {
.selection-tab-button {
width: 100%;
border: 0px;
outline: 0px;
@ -359,7 +412,7 @@ input {
color: #848c91;
}
.corpus-type-button.selected {
.selection-tab-button.selected {
width: 100%;
border: 0px;
outline: 0px;
@ -398,3 +451,8 @@ input {
line-height: 19px;
color: #46535b;
}
.dz-error-message {
top: 100% !important;
left: 25% !important;
}

View File

@ -0,0 +1,294 @@
<!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 in priimek:</label>
<input type="text" id="ime" name="ime" required="required"/>
<label for="naslov-predavanja">* Naslov predavanja:</label>
<input type="text" id="naslov-predavanja" name="naslov-predavanja" required="required"/>
<label for="predmet">* Predmet:</label>
<input type="text" id="predmet" name="predmet" required="required"/>
<label for="fakulteta">* Fakulteta:</label>
<input type="text" id="fakulteta" name="fakulteta" required="required"/>
<label for="email">* E-Pošta:</label>
<input type="text" id="email" name="email" required="required"/>
<label for="telefon">Telefon:</label>
<input type="text" id="telefon" name="telefon"/>
<label for="kljucne-besede">* Ključne besede:</label>
<input type="text" id="kljucne-besede" name="kljucne-besede" required="required"/>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label>Ali se strinjate z javno objavo predavanja?</label>
<input style="width: 10%;" type="checkbox" name="javna-objava" value="javna-objava" checked>
</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>
</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: "/predavanja/upload",
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: {{max_files}},
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 1000, // MB
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();
// TODO: Check form validity.
// 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>

View File

@ -2,10 +2,17 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portal {{title}}</title>
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div>
<h3>Login</h3>
<div id="main-window">
<div id="rect1">
<div style="padding: 50px;">
<div id="logo-container">
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<h3 id="title" style="font-size: 27px; text-align: left;">Prijava - {{title}}</h3>
<div>
{% with messages = get_flashed_messages() %}
{% if messages %}
@ -17,23 +24,25 @@
<form method="POST" action="/{{corpus_name}}/login">
<div>
<div>
<input type="email" name="email" placeholder="Your Email" autofocus="">
<input type="email" name="email" placeholder="Email" autofocus="">
</div>
</div>
<div>
<div>
<input type="password" name="password" placeholder="Your Password">
<input type="password" name="password" placeholder="Geslo">
</div>
</div>
<div>
<label>
<input type="checkbox">
Remember me
</label>
<div style="display:flex; flex-direction: row; justify-content: left; align-items: center">
<label>Zapomni prijavo</label>
<input style="width: 10%;" type="checkbox">
</div>
<button>Login</button>
<button class="button-general" style="margin-top: 20px;">PRIJAVA</button>
</form>
</div>
</div>
</div>
<div id="rect2" class="mock-side">
</div>
</div>
</body>

View File

@ -4,51 +4,35 @@
<meta charset="UTF-8">
<title>Portal za oddajanje besedil</title>
<!--{{ 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;
top: -0.5px;
width: 388px;
height: 632px;
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">
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<div class="bg"></div>
<div id="main-window">
<div id="rect1">
<div id="logo-container">
<img src="static/image/logo.svg" alt="logo"/>
<img src="/static/image/logo.svg" alt="logo"/>
</div>
<form id="my-dropzone" class="dropzone">
<div style="position: relative; right: 390px;">
<h1 id="title">Korpus ŠOLAR</h1>
<h1 id="title" style="font-size: 25px;">Korpus ŠOLAR</h1>
<!--<label for="ime">IME</label>
<input type="text" id="ime" name="ime" required="required"/>
<label for="institucija">INSTITUCIJA</label>
<input type="text" id="institucija" name="institucija"/>
<label for="regija">REGIJA</label>
<select id="regija" name="regija">
<option value="CE" selected="selected">Celje (CE)</option>
<option value="GO">Nova Gorica (GO)</option>
<option value="KK">Krško (KK)</option>
<option value="KP">Koper (KP)</option>
<option value="KR">Kranj (KR)</option>
<option value="LJ">Ljubljana (LJ)</option>
<option value="MB">Maribor (MB)</option>
<option value="MS">Murska Sobota (MS)</option>
<option value="NM">Novo Mesto (NM)</option>
<option value="PO">Postojna (PO)</option>
<option value="SG">Slovenj Gradec (SG)</option>
</select>-->
<div class="selection-tabs">
<button class="selection-tab-button selected">ODDAJA</button>
<button class="selection-tab-button">ZGODOVINA</button>
<button class="selection-tab-button">POGODBE</button>
</div>
<label for="program">PROGRAM</label>
<select id="program" name="program">
@ -71,6 +55,10 @@
<option value="drug-strok">Drugi strokovni predmeti</option>
<option value="drug-izb">Drugi izbirni ali dodatni predmeti</option>
</select>
<div id="predmet-custom-box" style="display: none;">
<label for="predmet-custom">Ime predmeta:</label>
<input type="text" id="predmet-custom" name="predmet-custom"/>
</div>
<label for="letnik">LETNIK</label>
<select id="letnik" name="letnik">
@ -92,6 +80,11 @@
<option value="solski-test">Šolski test</option>
<option value="delo-v-razredu">Delo v razredu, ne za oceno</option>
</select>
<div id="vrsta-custom-box" style="display: none;">
<label for="vrsta-custom">Vtipkajte besedilno vrsto:</label>
<input type="text" id="vrsta-custom" name="vrsta-custom"/>
</div>
<label for="solsko-leto">ŠOLSKO LETO</label>
<select id="solsko-leto" name="solsko-leto">
@ -222,11 +215,13 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
</div>
<!--{{ dropzone.load_js() }}-->
<script src="static/dropzone.js"></script>
<script src="/static/dropzone.js"></script>
<script>
/////////////////////////
// Dropzone //
/////////////////////////
var selectPredmet = document.getElementById("predmet");
var selectVrsta = document.getElementById("vrsta");
var btnSubmit = document.getElementById("button-submit");
var btnSubmitFinal = document.getElementById("button-submit-final");
var btnSubmitCancel = document.getElementById("button-submit-cancel");
@ -265,6 +260,23 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
init: function() {
var dz = this;
selectPredmet.addEventListener("change", function(e) {
var predmetCustomBox = document.getElementById("predmet-custom-box");
if (selectPredmet.value.startsWith("drug")) {
predmetCustomBox.style.display = "inherit";
} else {
predmetCustomBox.style.display = "none";
}
});
selectVrsta.addEventListener("change", function(e) {
var vrstaCustomBox = document.getElementById("vrsta-custom-box");
if (selectVrsta.value == "delo-v-razredu") {
vrstaCustomBox.style.display = "inherit";
} else {
vrstaCustomBox.style.display = "none";
}
});
btnSubmit.addEventListener("click", function(e) {
// Make sure that the form isn't actually being sent.
@ -272,17 +284,19 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
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;
var izjava = form["izjava"].checked;
if (isEmptyOrSpaces(ime) || isEmptyOrSpaces(email) || !izjava) {
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.");
var program = form["program"].value;
var predmet = form["predmet"].value;
var predmetCustom = form["predmet-custom"].value;
var letnik = form["letnik"].value;
var vrsta = form["vrsta"].value;
var vrstaCustom = form["vrsta-custom"].value;
var solskoLeto = form["solsko-leto"].value;
var jezikovniPopravki = form["jezikovni-popravki"].value;
if (predmet.startsWith("drug") && isEmptyOrSpaces(predmetCustom)) {
alert("Polje za predmet ne more biti prazno!");
} else if (vrsta === "delo-v-razredu" && isEmptyOrSpaces(vrstaCustom)) {
alert("Polje za vrsto besedila ne more biti prazno!");
} else {
// Then make terms popup visible
btnSubmit.disabled = true;