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)
@ -56,17 +64,26 @@ class UploadHandler:
def __init__(self, **kwargs):
self.config = kwargs
def set_contract_creator(self, contract_creator):
assert isinstance(contract_creator, ContractCreator)
self._contract_creator = contract_creator
self.contract_creator = ContractCreator()
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 extract_upload_metadata(self, corpus_name, request):
@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.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.add(model_obj)
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,89 +179,57 @@ class UploadHandler:
# Create a secure SSL context
context = ssl.create_default_context()
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)
try:
with SMTP_SSL(self.config['MAIL_HOST'], self.config['SMTP_PORT'], context=context) as server:
server.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
server.sendmail(message['From'], message['To'], text)
# Save copy of sent mail in Sent mailbox
imap = imaplib.IMAP4_SSL(self.config['MAIL_HOST'], self.config['IMAP_PORT'])
imap.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
imap.logout()
# Save copy of sent mail in Sent mailbox
imap = imaplib.IMAP4_SSL(self.config['MAIL_HOST'], self.config['IMAP_PORT'])
imap.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
imap.logout()
except Exception:
traceback.print_exc()
def 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
@staticmethod
def check_suffixes(files):
for key, f in files.items():
if key.startswith('file'):
suffix = f.filename.split('.')[-1]
if suffix not in ENABLED_FILETYPES:
return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename)
return None
err = check_suffixes(files)
if err:
return err, 400
@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
err = check_form(request.form)
if err:
return err, 400
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
# Parse request.
upload_metadata = upload_handler.extract_upload_metadata(corpus_name, request)
err = UploadHandler.check_suffixes(files)
if err:
return err, 400
logging.info('Upload with id "{}" supplied form data: {}'.format(upload_metadata['upload_id'],
str(upload_metadata['form_data'])))
err = UploadHandler.check_fname_lengths(files)
if err:
return err, 400
# Generate contract PDF file based on the uploads metadata.
upload_handler.generate_upload_contract_pdf(upload_metadata)
return None
# 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))
def check_suffixes(files):
for key, f in files.items():
if key.startswith('file'):
suffix = f.filename.split('.')[-1]
if suffix not in ENABLED_FILETYPES:
return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename)
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')
if len(ime) > 100:
return 'Predolgo ime.'
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'
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)
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 from user "{}" with upload id "{}" supplied form data: {}'.format(
request.user,
upload_metadata['upload_id'],
str(upload_metadata['form_data']
)))
# Store uploaded files to disk.
upload_handler.store_datafiles(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))
MAXLEN_FORM = 150
def check_form(form):
pass
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 = self.check_form(request.form)
if err:
return err, 400
# Parse request.
upload_metadata = self.extract_upload_metadata('solar', request)
logging.info('Upload from user "{}" with upload id "{}" supplied form data: {}'.format(
user_id,
upload_metadata['upload_id'],
str(upload_metadata['form_data']
)))
# Store uploaded files to disk.
self.store_datafiles(request.files, upload_metadata)
# 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):
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

@ -22,7 +22,7 @@ html {
}
html {
background: url(image/bg.jpeg) no-repeat center center fixed;
background: url(image/bg.jpeg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
@ -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 {