Progress on solar.
This commit is contained in:
parent
e9e8e94075
commit
51b1237b5f
|
@ -13,6 +13,6 @@ WORKDIR /usr/src/portal-webapp
|
||||||
RUN apt-get update && apt-get -y install wkhtmltopdf && \
|
RUN apt-get update && apt-get -y install wkhtmltopdf && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN pip3 install --no-cache-dir pdfkit flask flask-dropzone flask-log-request-id Flask-SQLAlchemy alembic flask-migrate Flask-script psycopg2 gunicorn pdfkit
|
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
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
166
app.py
166
app.py
|
@ -3,19 +3,23 @@ import os
|
||||||
import configparser
|
import configparser
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from werkzeug.security import check_password_hash
|
||||||
|
|
||||||
from flask import Flask, render_template, request
|
from flask import Flask, render_template, request, redirect, flash
|
||||||
from flask_dropzone import Dropzone
|
from flask_dropzone import Dropzone
|
||||||
from flask_migrate import Migrate, MigrateCommand
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
from flask_script import Manager
|
from flask_script import Manager
|
||||||
from portal.model import db
|
from flask_login import LoginManager, login_required, login_user, current_user
|
||||||
|
from portal.model import db, RegisteredUser
|
||||||
|
|
||||||
import portal.base
|
import portal.base
|
||||||
|
import portal.solar
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Implement user registration.
|
||||||
|
# TODO: Integrate Shibboleth login.
|
||||||
|
|
||||||
|
|
||||||
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}$')
|
|
||||||
|
|
||||||
# TODO: make logging level configurable
|
# TODO: make logging level configurable
|
||||||
logging.basicConfig(level=logging.DEBUG, format='[APP LOGGER] %(asctime)s %(levelname)s: %(message)s')
|
logging.basicConfig(level=logging.DEBUG, format='[APP LOGGER] %(asctime)s %(levelname)s: %(message)s')
|
||||||
|
@ -30,6 +34,7 @@ config = config['DEFAULT']
|
||||||
MAIL_HOST = config['MAIL_HOST']
|
MAIL_HOST = config['MAIL_HOST']
|
||||||
MAIL_LOGIN = config['MAIL_LOGIN']
|
MAIL_LOGIN = config['MAIL_LOGIN']
|
||||||
MAIL_PASS = config['MAIL_PASS']
|
MAIL_PASS = config['MAIL_PASS']
|
||||||
|
APP_SECRET_KEY = bytes.fromhex(config['APP_SECRET_KEY'])
|
||||||
SMTP_PORT = int(config['SMTP_PORT'])
|
SMTP_PORT = int(config['SMTP_PORT'])
|
||||||
IMAP_PORT = int(config['IMAP_PORT'])
|
IMAP_PORT = int(config['IMAP_PORT'])
|
||||||
MAX_UPLOAD_SIZE = int(config['MAX_UPLOAD_SIZE']) # Bytes
|
MAX_UPLOAD_SIZE = int(config['MAX_UPLOAD_SIZE']) # Bytes
|
||||||
|
@ -55,6 +60,8 @@ if 'PORTALDS4DS1_MAIL_LOGIN' in os.environ:
|
||||||
MAIL_LOGIN = os.environ['PORTALDS4DS1_MAIL_LOGIN']
|
MAIL_LOGIN = os.environ['PORTALDS4DS1_MAIL_LOGIN']
|
||||||
if 'PORTALDS4DS1_MAIL_PASS' in os.environ:
|
if 'PORTALDS4DS1_MAIL_PASS' in os.environ:
|
||||||
MAIL_PASS = os.environ['PORTALDS4DS1_MAIL_PASS']
|
MAIL_PASS = os.environ['PORTALDS4DS1_MAIL_PASS']
|
||||||
|
if 'PORTALDS4DS1_APP_SECRET_KEY' in os.environ:
|
||||||
|
APP_SECRET_KEY = bytes.fromhex(os.environ['PORTALDS4DS1_APP_SECRET_KEY'])
|
||||||
if 'PORTALDS4DS1_SMTP_PORT' in os.environ:
|
if 'PORTALDS4DS1_SMTP_PORT' in os.environ:
|
||||||
SMTP_PORT = int(os.environ['PORTALDS4DS1_SMTP_PORT'])
|
SMTP_PORT = int(os.environ['PORTALDS4DS1_SMTP_PORT'])
|
||||||
if 'PORTALDS4DS1_IMAP_PORT' in os.environ:
|
if 'PORTALDS4DS1_IMAP_PORT' in os.environ:
|
||||||
|
@ -86,6 +93,7 @@ VALID_CORPUS_NAMES = ['prevodi', 'gigafida', 'solar']
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
app.config.update(
|
app.config.update(
|
||||||
|
SECRET_KEY = APP_SECRET_KEY,
|
||||||
UPLOADED_PATH = UPLOADS_DIR,
|
UPLOADED_PATH = UPLOADS_DIR,
|
||||||
MAX_CONTENT_LENGTH = MAX_UPLOAD_SIZE,
|
MAX_CONTENT_LENGTH = MAX_UPLOAD_SIZE,
|
||||||
TEMPLATES_AUTO_RELOAD = True,
|
TEMPLATES_AUTO_RELOAD = True,
|
||||||
|
@ -111,10 +119,21 @@ upload_handler = portal.base.UploadHandler(
|
||||||
IMAP_PORT=IMAP_PORT,
|
IMAP_PORT=IMAP_PORT,
|
||||||
MAIL_SUBJECT=MAIL_SUBJECT,
|
MAIL_SUBJECT=MAIL_SUBJECT,
|
||||||
MAIL_BODY=MAIL_BODY,
|
MAIL_BODY=MAIL_BODY,
|
||||||
CONTRACT_CLIENT_CONTACT=CONTRACT_CLIENT_CONTACT
|
CONTRACT_CLIENT_CONTACT=CONTRACT_CLIENT_CONTACT,
|
||||||
|
MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Use flask-login to manage user sessions where they are required.
|
||||||
|
login_manager = LoginManager(app)
|
||||||
|
login_manager.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.get(user_id)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
@ -130,14 +149,61 @@ def index_corpus(corpus_name):
|
||||||
elif corpus_name == 'gigafida':
|
elif corpus_name == 'gigafida':
|
||||||
description = DESC_GIGAFIDA
|
description = DESC_GIGAFIDA
|
||||||
elif corpus_name == 'solar':
|
elif corpus_name == 'solar':
|
||||||
return handle_solar(request)
|
if current_user.is_authenticated:
|
||||||
|
return redirect('/solar/oddaja')
|
||||||
|
return redirect('/solar/login')
|
||||||
|
|
||||||
return render_template('basic.html',
|
return render_template('basic.html',
|
||||||
corpus_name=corpus_name, description=description, max_files=MAX_FILES_PER_UPLOAD)
|
corpus_name=corpus_name, description=description, max_files=MAX_FILES_PER_UPLOAD)
|
||||||
|
|
||||||
|
|
||||||
def handle_solar(request):
|
@login_manager.user_loader
|
||||||
return 404
|
def load_user(user_id):
|
||||||
|
return RegisteredUser.query.get(int(user_id))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/<corpus_name>/login')
|
||||||
|
def login_get(corpus_name):
|
||||||
|
return render_template('login.html', corpus_name=corpus_name)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/<corpus_name>/login', methods=['POST'])
|
||||||
|
def login_post(corpus_name):
|
||||||
|
email = request.form.get('email')
|
||||||
|
password = request.form.get('password')
|
||||||
|
remember = True if request.form.get('remember') else False
|
||||||
|
|
||||||
|
user = RegisteredUser.query.filter_by(email=email).first()
|
||||||
|
|
||||||
|
# TODO: Check if user is authorized to login to this corpus.
|
||||||
|
|
||||||
|
if not user or not check_password_hash(user.pass_hash, password):
|
||||||
|
flash('Napačni podatki za prijavo. Poskusite ponovno.')
|
||||||
|
return redirect('/{}/login'.format(corpus_name))
|
||||||
|
|
||||||
|
if not user.active:
|
||||||
|
flash('Vaš uporabniški račun še ni bil aktiviran.')
|
||||||
|
return redirect('/{}/login'.format(corpus_name))
|
||||||
|
|
||||||
|
login_user(user, remember=remember)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# 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('/<corpus_name>/upload', methods=['POST'])
|
@app.route('/<corpus_name>/upload', methods=['POST'])
|
||||||
|
@ -146,89 +212,13 @@ def handle_upload(corpus_name):
|
||||||
return 404
|
return 404
|
||||||
|
|
||||||
if corpus_name == 'solar':
|
if corpus_name == 'solar':
|
||||||
return handle_upload_solar(request)
|
if current_user.is_authenticated:
|
||||||
else:
|
return portal.solar.handle_upload(request, upload_handler)
|
||||||
return handle_upload_unauthenticated(request, corpus_name)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_upload_solar(request):
|
|
||||||
return 404
|
return 404
|
||||||
|
else:
|
||||||
|
return portal.base.handle_upload_unauthenticated(request, corpus_name)
|
||||||
|
|
||||||
|
|
||||||
def handle_upload_unauthenticated(request, corpus_name):
|
|
||||||
files = request.files
|
|
||||||
if len(files) > MAX_FILES_PER_UPLOAD:
|
|
||||||
return 'Naložite lahko do {} datotek hkrati.'.format(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))
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|
|
@ -3,6 +3,7 @@ SQL_CONN_STR=postgresql://portal:randompass123@localhost/portal
|
||||||
MAIL_HOST=posta.cjvt.si
|
MAIL_HOST=posta.cjvt.si
|
||||||
MAIL_LOGIN=oddaja-besedil@cjvt.si
|
MAIL_LOGIN=oddaja-besedil@cjvt.si
|
||||||
MAIL_PASS=secretmailpass123
|
MAIL_PASS=secretmailpass123
|
||||||
|
APP_SECRET_KEY=05ec303ec4a2c316426860523f95b349
|
||||||
SMTP_PORT=465
|
SMTP_PORT=465
|
||||||
IMAP_PORT=993
|
IMAP_PORT=993
|
||||||
MAX_UPLOAD_SIZE=1000000000
|
MAX_UPLOAD_SIZE=1000000000
|
||||||
|
|
|
@ -8,6 +8,7 @@ services:
|
||||||
- PORTALDS4DS1_MAIL_HOST=posta.cjvt.si
|
- PORTALDS4DS1_MAIL_HOST=posta.cjvt.si
|
||||||
- PORTALDS4DS1_MAIL_LOGIN=oddaja-besedil@cjvt.si
|
- PORTALDS4DS1_MAIL_LOGIN=oddaja-besedil@cjvt.si
|
||||||
- PORTALDS4DS1_MAIL_PASS=randompass123
|
- PORTALDS4DS1_MAIL_PASS=randompass123
|
||||||
|
- PORTALDS4DS1_APP_SECRET_KEY=05ec303ec4a2c316426860523f95b349
|
||||||
- PORTALDS4DS1_SMTP_PORT=465
|
- PORTALDS4DS1_SMTP_PORT=465
|
||||||
- PORTALDS4DS1_IMAP_PORT=993
|
- PORTALDS4DS1_IMAP_PORT=993
|
||||||
- PORTALDS4DS1_MAX_UPLOAD_SIZE=1000000000
|
- PORTALDS4DS1_MAX_UPLOAD_SIZE=1000000000
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Wait for PostgreSQL container to spin up.
|
||||||
|
while ! curl http://db:5432/ 2>&1 | grep '52'
|
||||||
|
do
|
||||||
|
echo "Waiting for PostgreSQL to start."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
# Upgrade DB schema to version used by application. This also initializes table, if they aren't already created.
|
# Upgrade DB schema to version used by application. This also initializes table, if they aren't already created.
|
||||||
flask db upgrade
|
flask db upgrade
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
"""Added tables for Solar data and authentication.
|
||||||
|
|
||||||
|
Revision ID: c6edf87b8bff
|
||||||
|
Revises: a846faa2b908
|
||||||
|
Create Date: 2021-05-03 10:12:50.632988
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'c6edf87b8bff'
|
||||||
|
down_revision = 'a846faa2b908'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('upload_solar')
|
||||||
|
op.drop_table('institution')
|
||||||
|
op.drop_table('stamps')
|
||||||
|
op.drop_table('registered_user')
|
||||||
|
op.drop_column('upload_unauthenticated', '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.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),
|
||||||
|
sa.Column('region', sa.TEXT(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('file_contract', sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id', name='institution_pkey'),
|
||||||
|
postgresql_ignore_search_path=False
|
||||||
|
)
|
||||||
|
op.create_table('registered_user',
|
||||||
|
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('registered_user_id_seq'::regclass)"), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('name', sa.TEXT(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('email', sa.TEXT(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('role', sa.TEXT(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('pass_hash', sa.TEXT(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('active', sa.BOOLEAN(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('last_login', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('registered', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['institution'], ['institution.id'], name='registered_user_institution_fkey'),
|
||||||
|
sa.PrimaryKeyConstraint('id', name='registered_user_pkey'),
|
||||||
|
postgresql_ignore_search_path=False
|
||||||
|
)
|
||||||
|
op.create_table('stamps',
|
||||||
|
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('name', sa.TEXT(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('file_logo', sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['institution'], ['institution.id'], name='stamps_institution_fkey'),
|
||||||
|
sa.PrimaryKeyConstraint('id', name='stamps_pkey')
|
||||||
|
)
|
||||||
|
op.create_table('upload_solar',
|
||||||
|
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('upload_user', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
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('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'),
|
||||||
|
sa.PrimaryKeyConstraint('id', name='upload_pkey')
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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);')
|
||||||
|
# ### end Alembic commands ###
|
113
portal/base.py
113
portal/base.py
|
@ -18,7 +18,11 @@ from email.mime.application import MIMEApplication
|
||||||
import pdfkit
|
import pdfkit
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
from . model import db, UploadUnauthenticated
|
from . model import db, UploadUnauthenticated, UploadSolar
|
||||||
|
|
||||||
|
|
||||||
|
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}$')
|
||||||
|
|
||||||
|
|
||||||
class ContractCreator:
|
class ContractCreator:
|
||||||
|
@ -74,7 +78,6 @@ class UploadHandler:
|
||||||
|
|
||||||
return upload_metadata
|
return upload_metadata
|
||||||
|
|
||||||
|
|
||||||
def get_uploads_subdir(self, dir_name):
|
def get_uploads_subdir(self, dir_name):
|
||||||
subdir = Path(self.config['UPLOADS_DIR']) / dir_name
|
subdir = Path(self.config['UPLOADS_DIR']) / dir_name
|
||||||
if not subdir.exists():
|
if not subdir.exists():
|
||||||
|
@ -126,7 +129,8 @@ class UploadHandler:
|
||||||
form_zipcode=form_data['posta'],
|
form_zipcode=form_data['posta'],
|
||||||
form_email=form_data['email'],
|
form_email=form_data['email'],
|
||||||
file_contract=upload_metadata['contract_file'],
|
file_contract=upload_metadata['contract_file'],
|
||||||
upload_file_hashes=sorted_f_hashes
|
upload_file_hashes=sorted_f_hashes,
|
||||||
|
corpus_name=todo
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(upload_unauthenticated)
|
db.session.add(upload_unauthenticated)
|
||||||
|
@ -134,8 +138,31 @@ class UploadHandler:
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
def store_metadata_authenticated(self, upload_metadata):
|
def store_metadata_solar(self, upload_metadata):
|
||||||
pass
|
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):
|
def store_datafiles(self, files, upload_metadata):
|
||||||
base = self.get_uploads_subdir('files')
|
base = self.get_uploads_subdir('files')
|
||||||
|
@ -206,3 +233,79 @@ class UploadHandler:
|
||||||
imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
|
imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
|
||||||
imap.logout()
|
imap.logout()
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import UserMixin
|
||||||
|
|
||||||
|
|
||||||
###########################################
|
###########################################
|
||||||
# Model classes for describing SQL tables #
|
# Model classes for describing SQL tables #
|
||||||
|
@ -23,4 +25,43 @@ class UploadUnauthenticated(db.Model):
|
||||||
form_email = db.Column(db.String)
|
form_email = db.Column(db.String)
|
||||||
file_contract = db.Column(db.String)
|
file_contract = db.Column(db.String)
|
||||||
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))
|
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))
|
||||||
|
corpus_name = db.Column(db.String)
|
||||||
|
|
||||||
|
|
||||||
|
class UploadSolar(db.Model):
|
||||||
|
__tablename__ = 'upload_solar'
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
upload_user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'))
|
||||||
|
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)
|
||||||
|
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))
|
||||||
|
|
||||||
|
|
||||||
|
class RegisteredUser(UserMixin, db.Model):
|
||||||
|
__tablename__ = 'registered_user'
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String)
|
||||||
|
email = db.Column(db.String)
|
||||||
|
role = db.Column(db.String)
|
||||||
|
pass_hash = db.Column(db.String)
|
||||||
|
active = db.Column(db.Boolean)
|
||||||
|
last_login = db.Column(db.DateTime)
|
||||||
|
registered = db.Column(db.DateTime)
|
||||||
|
institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'))
|
||||||
|
|
||||||
|
|
||||||
|
class Institution(db.Model):
|
||||||
|
__tablename__ = 'institution'
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String)
|
||||||
|
region = db.Column(db.String)
|
||||||
|
file_contract = db.Column(db.String)
|
||||||
|
|
||||||
|
|
41
portal/solar.py
Normal file
41
portal/solar.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import portal.base
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
def check_form(form):
|
||||||
|
pass
|
39
templates/login.html
Normal file
39
templates/login.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h3>Login</h3>
|
||||||
|
<div>
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div>
|
||||||
|
{{ messages[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<form method="POST" action="/{{corpus_name}}/login">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<input type="email" name="email" placeholder="Your Email" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<input type="password" name="password" placeholder="Your Password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button>Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
|
@ -27,29 +27,84 @@
|
||||||
|
|
||||||
<form id="my-dropzone" class="dropzone">
|
<form id="my-dropzone" class="dropzone">
|
||||||
<div style="position: relative; right: 390px;">
|
<div style="position: relative; right: 390px;">
|
||||||
<h1 id="title">Portal za oddajanje besedil {{corpus_name}}</h1>
|
<h1 id="title">Korpus ŠOLAR</h1>
|
||||||
<h2 id="subtitle">{{subtitle}}</h2>
|
|
||||||
|
|
||||||
<label for="ime">* Ime:</label>
|
<!--<label for="ime">IME</label>
|
||||||
<input type="text" id="ime" name="ime" required="required"/>
|
<input type="text" id="ime" name="ime" required="required"/>
|
||||||
|
|
||||||
<label for="podjetje">Podjetje / institucija:</label>
|
<label for="institucija">INSTITUCIJA</label>
|
||||||
<input type="text" id="podjetje" name="podjetje"/>
|
<input type="text" id="institucija" name="institucija"/>
|
||||||
|
|
||||||
<label for="naslov">Naslov:</label>
|
<label for="regija">REGIJA</label>
|
||||||
<input type="text" id="naslov" name="naslov"/>
|
<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>-->
|
||||||
|
|
||||||
<label for="posta">Pošta:</label>
|
<label for="program">PROGRAM</label>
|
||||||
<input type="text" id="posta" name="posta"/>
|
<select id="program" name="program">
|
||||||
|
<option value="OS" selected="selected">Osnovnošolski (OŠ)</option>
|
||||||
|
<option value="SSG">Splošna in strokovna gimnazija (SGG)</option>
|
||||||
|
<option value="MGP">Mednarodni gimnazijski programi (MGP)</option>
|
||||||
|
<option value="ZG">Zasebne gimnazije (ZG)</option>
|
||||||
|
<option value="NPI">Nižje poklicno izobraževanje (NPI)</option>
|
||||||
|
<option value="SPI">Srednje poklicno izobraževanje (SPI)</option>
|
||||||
|
<option value="SSI">Srednje strokovno izobraževanje (SSI)</option>
|
||||||
|
<option value="PTI">Poklicno-tehnično izobraževanje (PTI)</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<label for="email">* E-Pošta:</label>
|
<label for="predmet">PREDMET</label>
|
||||||
<input type="text" id="email" name="email" required="required"/>
|
<select id="predmet" name="predmet">
|
||||||
|
<option value="slo" selected="selected">Slovenščina</option>
|
||||||
|
<option value="drug-jez">Drugi jezikoslovni predmeti</option>
|
||||||
|
<option value="drug-druz">Drugi družboslovni predmeti</option>
|
||||||
|
<option value="drug-narav">Drugi naravoslovni predmeti</option>
|
||||||
|
<option value="drug-strok">Drugi strokovni predmeti</option>
|
||||||
|
<option value="drug-izb">Drugi izbirni ali dodatni predmeti</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<label for="telefon">Telefon:</label>
|
<label for="letnik">LETNIK</label>
|
||||||
<input type="text" id="telefon" name="telefon"/>
|
<select id="letnik" name="letnik">
|
||||||
|
<option value="1" selected="selected">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
<option value="5">5</option>
|
||||||
|
<option value="6">6</option>
|
||||||
|
<option value="7">7</option>
|
||||||
|
<option value="8">8</option>
|
||||||
|
<option value="9">9</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<input type="checkbox" id="izjava" name="izjava" value="izjava" required="required">
|
<label for="vrsta">VRSTA BESEDILA</label>
|
||||||
<label for="izjava">* Izjavljam, da sem lastnik avtorskih pravic in dovoljujem, da se besedila vključijo v korpuse v skladu z ustrezno licenco korpusa.</label>
|
<select id="vrsta" name="vrsta">
|
||||||
|
<option value="esej-spis" selected="selected">Esej ali spis</option>
|
||||||
|
<option value="prakticno">Praktično besedilo (npr. vabila, prošnje ipd. pri pouku slovenščine), napisano za oceno</option>
|
||||||
|
<option value="solski-test">Šolski test</option>
|
||||||
|
<option value="delo-v-razredu">Delo v razredu, ne za oceno</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="solsko-leto">ŠOLSKO LETO</label>
|
||||||
|
<select id="solsko-leto" name="solsko-leto">
|
||||||
|
<option value="20-21" selected="selected">2020/21</option>
|
||||||
|
<option value="21-22">2021/22</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="jezikovni-popravki">JEZIKOVNI POPRAVKI</label>
|
||||||
|
<select id="jezikovni-popravki" name="jezikovni-popravki">
|
||||||
|
<option value="popr-ne" selected="selected">Besedilo vsebuje učiteljske popravke in strinjam se z njihovo vključitvijo v korpus</option>
|
||||||
|
<option value="brez-popr">Besedilo ne vsebuje učiteljskih popravkov</option>
|
||||||
|
<option value="popr-da">Besedilo vsebuje učiteljske popravke in ne strinjam se z njihovo vključitvijo v korpus</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<button id="button-submit" type="submit">Oddaj</button>
|
<button id="button-submit" type="submit">Oddaj</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -187,7 +242,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
|
||||||
const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
Dropzone.options.myDropzone = { // The camelized version of the ID of the form element
|
Dropzone.options.myDropzone = { // The camelized version of the ID of the form element
|
||||||
url: "/{{corpus_name}}/upload",
|
url: "/solar/upload",
|
||||||
autoProcessQueue: false,
|
autoProcessQueue: false,
|
||||||
uploadMultiple: true,
|
uploadMultiple: true,
|
||||||
parallelUploads: 20,
|
parallelUploads: 20,
|
||||||
|
@ -296,5 +351,8 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue
Block a user