From 51b1237b5f227445bb3f81d6987504a272ddb2f1 Mon Sep 17 00:00:00 2001 From: msinkec Date: Wed, 5 May 2021 14:26:26 +0200 Subject: [PATCH] Progress on solar. --- Dockerfile | 2 +- app.py | 152 ++++++++---------- config.ini | 1 + docker-compose.yml | 1 + entrypoint.sh | 7 + ...7b8bff_added_tables_for_solar_data_and_.py | 83 ++++++++++ portal/base.py | 129 +++++++++++++-- portal/model.py | 41 +++++ portal/solar.py | 41 +++++ templates/login.html | 39 +++++ .../{index-solar.html => solar-oddaja.html} | 100 +++++++++--- 11 files changed, 480 insertions(+), 116 deletions(-) create mode 100644 migrations/versions/c6edf87b8bff_added_tables_for_solar_data_and_.py create mode 100644 portal/solar.py create mode 100644 templates/login.html rename templates/{index-solar.html => solar-oddaja.html} (73%) diff --git a/Dockerfile b/Dockerfile index e9d6ce4..46484a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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-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"] diff --git a/app.py b/app.py index efc2ac9..8ca882a 100644 --- a/app.py +++ b/app.py @@ -3,19 +3,23 @@ import os import configparser import re 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_migrate import Migrate, MigrateCommand 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.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 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_LOGIN = config['MAIL_LOGIN'] MAIL_PASS = config['MAIL_PASS'] +APP_SECRET_KEY = bytes.fromhex(config['APP_SECRET_KEY']) SMTP_PORT = int(config['SMTP_PORT']) IMAP_PORT = int(config['IMAP_PORT']) 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'] if 'PORTALDS4DS1_MAIL_PASS' in os.environ: 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: SMTP_PORT = int(os.environ['PORTALDS4DS1_SMTP_PORT']) if 'PORTALDS4DS1_IMAP_PORT' in os.environ: @@ -86,6 +93,7 @@ VALID_CORPUS_NAMES = ['prevodi', 'gigafida', 'solar'] app = Flask(__name__) app.config.update( + SECRET_KEY = APP_SECRET_KEY, UPLOADED_PATH = UPLOADS_DIR, MAX_CONTENT_LENGTH = MAX_UPLOAD_SIZE, TEMPLATES_AUTO_RELOAD = True, @@ -111,9 +119,20 @@ upload_handler = portal.base.UploadHandler( IMAP_PORT=IMAP_PORT, MAIL_SUBJECT=MAIL_SUBJECT, 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('/') def index(): @@ -130,105 +149,76 @@ def index_corpus(corpus_name): elif corpus_name == 'gigafida': description = DESC_GIGAFIDA 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', corpus_name=corpus_name, description=description, max_files=MAX_FILES_PER_UPLOAD) -def handle_solar(request): - return 404 - - -@app.route('//upload', methods=['POST']) -def handle_upload(corpus_name): - if corpus_name not in VALID_CORPUS_NAMES: - return 404 - - if corpus_name == 'solar': - return handle_upload_solar(request) - else: - return handle_upload_unauthenticated(request, corpus_name) +@login_manager.user_loader +def load_user(user_id): + return RegisteredUser.query.get(int(user_id)) -def handle_upload_solar(request): - return 404 +@app.route('//login') +def login_get(corpus_name): + return render_template('login.html', corpus_name=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 +@app.route('//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 - err = check_suffixes(files) - if err: - return err, 400 + user = RegisteredUser.query.filter_by(email=email).first() - err = check_form(request.form) - if err: - return err, 400 + # TODO: Check if user is authorized to login to this corpus. - # Parse request. - upload_metadata = upload_handler.extract_upload_metadata(corpus_name, request) + 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)) - logging.info('Upload with id "{}" supplied form data: {}'.format(upload_metadata['upload_id'], - str(upload_metadata['form_data']))) + if not user.active: + flash('Vaš uporabniški račun še ni bil aktiviran.') + return redirect('/{}/login'.format(corpus_name)) - # Generate contract PDF file based on the uploads metadata. - upload_handler.generate_upload_contract_pdf(upload_metadata) + login_user(user, remember=remember) - # 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 + if corpus_name == 'solar': + return redirect('/solar/oddaja') + return redirect('/{}/home'.format(corpus_name)) -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') +@app.route('//home') +@login_required +def profile(corpus_name): + return render_template('login.html', corpus_name=corpus_name) - if len(ime) > 100: - return 'Predolgo ime.' - if len(podjetje) > 100: - return 'Predolgo ime institucije.' +# TODO: Move solar stuff to seperate file using Flask blueprints. - if len(email) > 100: - return 'Predolgi email naslov' - elif not re.search(REGEX_EMAIL, email): - return 'Email napačnega formata.' +@app.route('/solar/oddaja') +@login_required +def solar_oddaja(): + return render_template('solar-oddaja.html') - if len(telefon) > 100: - return 'Predolga telefonska št.' - if len(naslov) > 100: - return 'Predolg naslov.' +@app.route('//upload', methods=['POST']) +def handle_upload(corpus_name): + if corpus_name not in VALID_CORPUS_NAMES: + return 404 + + if corpus_name == 'solar': + if current_user.is_authenticated: + return portal.solar.handle_upload(request, upload_handler) + return 404 + else: + return portal.base.handle_upload_unauthenticated(request, corpus_name) - if len(posta) > 100: - return 'Predolga pošta' - return None if __name__ == '__main__': app.run(debug=True) diff --git a/config.ini b/config.ini index 5c7ac3f..0328a3b 100644 --- a/config.ini +++ b/config.ini @@ -3,6 +3,7 @@ SQL_CONN_STR=postgresql://portal:randompass123@localhost/portal MAIL_HOST=posta.cjvt.si MAIL_LOGIN=oddaja-besedil@cjvt.si MAIL_PASS=secretmailpass123 +APP_SECRET_KEY=05ec303ec4a2c316426860523f95b349 SMTP_PORT=465 IMAP_PORT=993 MAX_UPLOAD_SIZE=1000000000 diff --git a/docker-compose.yml b/docker-compose.yml index 7061141..49685b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: - PORTALDS4DS1_MAIL_HOST=posta.cjvt.si - PORTALDS4DS1_MAIL_LOGIN=oddaja-besedil@cjvt.si - PORTALDS4DS1_MAIL_PASS=randompass123 + - PORTALDS4DS1_APP_SECRET_KEY=05ec303ec4a2c316426860523f95b349 - PORTALDS4DS1_SMTP_PORT=465 - PORTALDS4DS1_IMAP_PORT=993 - PORTALDS4DS1_MAX_UPLOAD_SIZE=1000000000 diff --git a/entrypoint.sh b/entrypoint.sh index e8952ea..1241f4c 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,12 @@ #!/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. flask db upgrade diff --git a/migrations/versions/c6edf87b8bff_added_tables_for_solar_data_and_.py b/migrations/versions/c6edf87b8bff_added_tables_for_solar_data_and_.py new file mode 100644 index 0000000..42d9f01 --- /dev/null +++ b/migrations/versions/c6edf87b8bff_added_tables_for_solar_data_and_.py @@ -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 ### diff --git a/portal/base.py b/portal/base.py index 39241ab..f9f3e56 100644 --- a/portal/base.py +++ b/portal/base.py @@ -18,7 +18,11 @@ from email.mime.application import MIMEApplication import pdfkit 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: @@ -74,7 +78,6 @@ class UploadHandler: return upload_metadata - def get_uploads_subdir(self, dir_name): subdir = Path(self.config['UPLOADS_DIR']) / dir_name if not subdir.exists(): @@ -118,15 +121,16 @@ class UploadHandler: 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 + 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) @@ -134,8 +138,31 @@ class UploadHandler: except Exception: traceback.print_exc() - def store_metadata_authenticated(self, upload_metadata): - pass + 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') @@ -206,3 +233,79 @@ class UploadHandler: imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8')) 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 + diff --git a/portal/model.py b/portal/model.py index 3e7f6d7..d740087 100644 --- a/portal/model.py +++ b/portal/model.py @@ -1,6 +1,8 @@ from datetime import datetime import sqlalchemy from flask_sqlalchemy import SQLAlchemy +from flask_login import UserMixin + ########################################### # Model classes for describing SQL tables # @@ -23,4 +25,43 @@ class UploadUnauthenticated(db.Model): form_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 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) diff --git a/portal/solar.py b/portal/solar.py new file mode 100644 index 0000000..66d9fcb --- /dev/null +++ b/portal/solar.py @@ -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 diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..c8ef3ac --- /dev/null +++ b/templates/login.html @@ -0,0 +1,39 @@ + + + + + + +
+

Login

+
+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+ {{ messages[0] }} +
+ {% endif %} + {% endwith %} +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+ diff --git a/templates/index-solar.html b/templates/solar-oddaja.html similarity index 73% rename from templates/index-solar.html rename to templates/solar-oddaja.html index ea60ca3..f9fb803 100644 --- a/templates/index-solar.html +++ b/templates/solar-oddaja.html @@ -27,29 +27,84 @@
-

Portal za oddajanje besedil {{corpus_name}}

-

{{subtitle}}

+

Korpus ŠOLAR

- + + + + + + + + + + + + + + + + + + +
@@ -187,7 +242,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; Dropzone.options.myDropzone = { // The camelized version of the ID of the form element - url: "/{{corpus_name}}/upload", + url: "/solar/upload", autoProcessQueue: false, uploadMultiple: true, parallelUploads: 20, @@ -296,5 +351,8 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk } } +