Progress on solar implementation, switched to storing metadata in SQL database...
This commit is contained in:
parent
13aad3a61a
commit
17f727f620
|
@ -1,16 +1,18 @@
|
||||||
FROM python:3.9
|
FROM python:3.9
|
||||||
|
|
||||||
|
COPY entrypoint.sh /usr/src/portal-webapp/
|
||||||
COPY app.py /usr/src/portal-webapp/
|
COPY app.py /usr/src/portal-webapp/
|
||||||
COPY config.ini /usr/src/portal-webapp/
|
COPY config.ini /usr/src/portal-webapp/
|
||||||
COPY templates /usr/src/portal-webapp/templates
|
COPY templates /usr/src/portal-webapp/templates
|
||||||
COPY static /usr/src/portal-webapp/static
|
COPY static /usr/src/portal-webapp/static
|
||||||
COPY contract/ /usr/src/portal-webapp/contract
|
COPY contract/ /usr/src/portal-webapp/contract
|
||||||
COPY portal/ /usr/src/portal-webapp/portal
|
COPY portal/ /usr/src/portal-webapp/portal
|
||||||
|
COPY migrations// /usr/src/portal-webapp/migrations
|
||||||
WORKDIR /usr/src/portal-webapp
|
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 gunicorn pdfkit
|
RUN pip3 install --no-cache-dir pdfkit flask flask-dropzone flask-log-request-id Flask-SQLAlchemy alembic flask-migrate Flask-script psycopg2 gunicorn pdfkit
|
||||||
|
|
||||||
CMD ["gunicorn", "--bind", "0.0.0.0:80", "-w", "1", "--access-logfile", "-", "app:app"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
## A web application for contribution of textual data files
|
161
app.py
161
app.py
|
@ -1,15 +1,25 @@
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from flask import Flask, render_template, request
|
from flask import Flask, render_template, request
|
||||||
from flask_dropzone import Dropzone
|
from flask_dropzone import Dropzone
|
||||||
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
|
from flask_script import Manager
|
||||||
|
from portal.model import db
|
||||||
|
|
||||||
import portal.base
|
import portal.base
|
||||||
|
|
||||||
# TODO: Put all the stuff in base.py into a class, so it can have a state of it's own, to avoid passing a bunch of arguments at each function call.
|
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Load configuration #
|
# Load configuration #
|
||||||
######################
|
######################
|
||||||
|
@ -23,9 +33,13 @@ MAIL_PASS = config['MAIL_PASS']
|
||||||
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
|
||||||
|
MAX_FILES_PER_UPLOAD = int(config['MAX_FILES_PER_UPLOAD'])
|
||||||
CONTRACT_CLIENT_CONTACT = config['CONTRACT_CLIENT_CONTACT']
|
CONTRACT_CLIENT_CONTACT = config['CONTRACT_CLIENT_CONTACT']
|
||||||
MAIL_SUBJECT = config['MAIL_SUBJECT']
|
MAIL_SUBJECT = config['MAIL_SUBJECT']
|
||||||
MAIL_BODY = config['MAIL_BODY']
|
MAIL_BODY = config['MAIL_BODY']
|
||||||
|
SQL_CONN_STR = config['SQL_CONN_STR']
|
||||||
|
DESC_PREVODI = config['DESC_PREVODI']
|
||||||
|
DESC_GIGAFIDA = config['DESC_GIGAFIDA']
|
||||||
|
|
||||||
if 'UPLOADS_DIR' in config:
|
if 'UPLOADS_DIR' in config:
|
||||||
UPLOADS_DIR = Path(config['UPLOADS_DIR'])
|
UPLOADS_DIR = Path(config['UPLOADS_DIR'])
|
||||||
|
@ -34,13 +48,6 @@ else:
|
||||||
if not UPLOADS_DIR.exists:
|
if not UPLOADS_DIR.exists:
|
||||||
UPLOADS_DIR.mkdir(parents=True)
|
UPLOADS_DIR.mkdir(parents=True)
|
||||||
|
|
||||||
if 'DATA_DIR' in config:
|
|
||||||
DATA_DIR = Path(config['DATA_DIR'])
|
|
||||||
else:
|
|
||||||
DATA_DIR = Path(__file__).resolve().parent / 'data'
|
|
||||||
if not DATA_DIR.exists:
|
|
||||||
DATA_DIR.mkdir(parents=True)
|
|
||||||
|
|
||||||
# Override configs with environment variables, if set
|
# Override configs with environment variables, if set
|
||||||
if 'PORTALDS4DS1_MAIL_HOST' in os.environ:
|
if 'PORTALDS4DS1_MAIL_HOST' in os.environ:
|
||||||
MAIL_HOST = os.environ['PORTALDS4DS1_MAIL_HOST']
|
MAIL_HOST = os.environ['PORTALDS4DS1_MAIL_HOST']
|
||||||
|
@ -54,16 +61,22 @@ if 'PORTALDS4DS1_IMAP_PORT' in os.environ:
|
||||||
IMAP_PORT = int(os.environ['PORTALDS4DS1_IMAP_PORT'])
|
IMAP_PORT = int(os.environ['PORTALDS4DS1_IMAP_PORT'])
|
||||||
if 'PORTALDS4DS1_MAX_UPLOAD_SIZE' in os.environ:
|
if 'PORTALDS4DS1_MAX_UPLOAD_SIZE' in os.environ:
|
||||||
MAX_UPLOAD_SIZE = int(os.environ['PORTALDS4DS1_MAX_UPLOAD_SIZE'])
|
MAX_UPLOAD_SIZE = int(os.environ['PORTALDS4DS1_MAX_UPLOAD_SIZE'])
|
||||||
|
if 'PORTALDS4DS1_MAX_FILES_PER_UPLOAD' in os.environ:
|
||||||
|
MAX_FILES_PER_UPLOAD = int(os.environ['PORTALDS4DS1_MAX_FILES_PER_UPLOAD'])
|
||||||
if 'PORTALDS4DS1_CONTRACT_CLIENT_CONTACT' in os.environ:
|
if 'PORTALDS4DS1_CONTRACT_CLIENT_CONTACT' in os.environ:
|
||||||
CONTRACT_CLIENT_CONTACT = os.environ['PORTALDS4DS1_CONTRACT_CLIENT_CONTACT']
|
CONTRACT_CLIENT_CONTACT = os.environ['PORTALDS4DS1_CONTRACT_CLIENT_CONTACT']
|
||||||
if 'PORTALDS4DS1_UPLOADS_DIR' in os.environ:
|
if 'PORTALDS4DS1_UPLOADS_DIR' in os.environ:
|
||||||
UPLOADS_DIR = os.environ['PORTALDS4DS1_UPLOADS_DIR']
|
UPLOADS_DIR = os.environ['PORTALDS4DS1_UPLOADS_DIR']
|
||||||
if 'PORTALDS4DS1_DATA_DIR' in os.environ:
|
|
||||||
DATA_DIR = os.environ['PORTALDS4DS1_DATA_DIR']
|
|
||||||
if 'PORTALDS4DS1_MAIL_SUBJECT' in os.environ:
|
if 'PORTALDS4DS1_MAIL_SUBJECT' in os.environ:
|
||||||
MAIL_SUBJECT = os.environ['PORTALDS4DS1_MAIL_SUBJECT']
|
MAIL_SUBJECT = os.environ['PORTALDS4DS1_MAIL_SUBJECT']
|
||||||
if 'PORTALDS4DS1_MAIL_BODY' in os.environ:
|
if 'PORTALDS4DS1_MAIL_BODY' in os.environ:
|
||||||
MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY']
|
MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY']
|
||||||
|
if 'PORTALDS4DS1_SQL_CONN_STR' in os.environ:
|
||||||
|
SQL_CONN_STR = os.environ['PORTALDS4DS1_SQL_CONN_STR']
|
||||||
|
if 'PORTALDS4DS1_DESC_PREVODI' in os.environ:
|
||||||
|
DESC_PREVODI = os.environ['PORTALDS4DS1_DESC_PREVODI']
|
||||||
|
if 'PORTALDS4DS1_DESC_GIGAFIDA' in os.environ:
|
||||||
|
DESC_GIGAFIDA = os.environ['PORTALDS4DS1_DESC_GIGAFIDA']
|
||||||
|
|
||||||
VALID_CORPUS_NAMES = ['prevodi', 'gigafida', 'solar']
|
VALID_CORPUS_NAMES = ['prevodi', 'gigafida', 'solar']
|
||||||
|
|
||||||
|
@ -75,11 +88,32 @@ app = Flask(__name__)
|
||||||
app.config.update(
|
app.config.update(
|
||||||
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,
|
||||||
|
SQLALCHEMY_DATABASE_URI = SQL_CONN_STR,
|
||||||
|
SQLALCHEMY_ECHO = True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Run "python app.py db -?" to see more info about DB migrations.
|
||||||
|
manager = Manager(app)
|
||||||
|
db.init_app(app)
|
||||||
|
migrate = Migrate(app, db)
|
||||||
|
manager.add_command('db', MigrateCommand)
|
||||||
|
|
||||||
|
# Set up dropzone.js to serve all the stuff for "file dropping" on the web interface.
|
||||||
dropzone = Dropzone(app)
|
dropzone = Dropzone(app)
|
||||||
|
|
||||||
|
upload_handler = portal.base.UploadHandler(
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
@ -90,52 +124,111 @@ def index():
|
||||||
def index_corpus(corpus_name):
|
def index_corpus(corpus_name):
|
||||||
if corpus_name not in VALID_CORPUS_NAMES:
|
if corpus_name not in VALID_CORPUS_NAMES:
|
||||||
return 'Korpus "{}" ne obstaja.'.format(corpus_name), 404
|
return 'Korpus "{}" ne obstaja.'.format(corpus_name), 404
|
||||||
|
|
||||||
if corpus_name == 'prevodi':
|
if corpus_name == 'prevodi':
|
||||||
subtitle = 'KORPUS PARALELNIH BESEDIL ANG-SLO'
|
description = DESC_PREVODI
|
||||||
elif corpus_name == 'gigafida':
|
elif corpus_name == 'gigafida':
|
||||||
subtitle = 'KORPUS GIGAFIDA'
|
description = DESC_GIGAFIDA
|
||||||
return render_template('basic.html', subtitle=subtitle, corpus_name=corpus_name)
|
elif corpus_name == 'solar':
|
||||||
|
return handle_solar(request)
|
||||||
|
|
||||||
|
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('/<corpus_name>/upload', methods=['POST'])
|
@app.route('/<corpus_name>/upload', methods=['POST'])
|
||||||
def handle_upload(corpus_name):
|
def handle_upload(corpus_name):
|
||||||
if corpus_name not in VALID_CORPUS_NAMES:
|
if corpus_name not in VALID_CORPUS_NAMES:
|
||||||
return 404
|
return 404
|
||||||
|
|
||||||
|
if corpus_name == 'solar':
|
||||||
|
return handle_upload_solar(request)
|
||||||
|
else:
|
||||||
|
return handle_upload_unauthenticated(request, corpus_name)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_upload_solar(request):
|
||||||
|
return 404
|
||||||
|
|
||||||
|
|
||||||
|
def handle_upload_unauthenticated(request, corpus_name):
|
||||||
files = request.files
|
files = request.files
|
||||||
if len(files) > 20:
|
if len(files) > MAX_FILES_PER_UPLOAD:
|
||||||
return 'Naložite lahko do 20 datotek hkrati.', 400
|
return 'Naložite lahko do {} datotek hkrati.'.format(MAX_FILES_PER_UPLOAD), 400
|
||||||
elif len(files) < 1:
|
elif len(files) < 1:
|
||||||
return 'Priložena ni bila nobena datoteka.', 400
|
return 'Priložena ni bila nobena datoteka.', 400
|
||||||
|
|
||||||
print('one')
|
err = check_suffixes(files)
|
||||||
err = portal.base.check_suffixes(files)
|
|
||||||
if err:
|
if err:
|
||||||
return err, 400
|
return err, 400
|
||||||
|
|
||||||
print('two')
|
err = check_form(request.form)
|
||||||
err = portal.base.check_form(request.form)
|
|
||||||
if err:
|
if err:
|
||||||
return err, 400
|
return err, 400
|
||||||
|
|
||||||
print('three')
|
# Parse request.
|
||||||
upload_metadata = portal.base.get_upload_metadata(corpus_name, request)
|
upload_metadata = upload_handler.extract_upload_metadata(corpus_name, request)
|
||||||
contract_file_name = portal.base.generate_contract_pdf(UPLOADS_DIR, upload_metadata, CONTRACT_CLIENT_CONTACT)
|
|
||||||
|
|
||||||
# Add contract_file_name to metadata TODO: move somewhere else
|
logging.info('Upload with id "{}" supplied form data: {}'.format(upload_metadata['upload_id'],
|
||||||
upload_metadata['contract'] = contract_file_name
|
str(upload_metadata['form_data'])))
|
||||||
portal.base.store_datafiles(UPLOADS_DIR, files, upload_metadata)
|
|
||||||
portal.base.store_metadata(UPLOADS_DIR, upload_metadata)
|
# Generate contract PDF file based on the uploads metadata.
|
||||||
portal.base.send_confirm_mail(
|
upload_handler.generate_upload_contract_pdf(upload_metadata)
|
||||||
subject=MAIL_SUBJECT,
|
|
||||||
body=MAIL_BODY,
|
# Store uploaded files to disk.
|
||||||
uploads_path=UPLOADS_DIR,
|
upload_handler.store_datafiles(files, upload_metadata)
|
||||||
upload_metadata=upload_metadata,
|
|
||||||
mail_host=MAIL_HOST, mail_login=MAIL_LOGIN, mail_pass=MAIL_PASS,
|
# Store metadata to database.
|
||||||
imap_port=IMAP_PORT, smtp_port=SMTP_PORT)
|
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))
|
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)
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
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
|
||||||
SMTP_PORT=465
|
SMTP_PORT=465
|
||||||
IMAP_PORT=993
|
IMAP_PORT=993
|
||||||
MAX_UPLOAD_SIZE=1000000000
|
MAX_UPLOAD_SIZE=1000000000
|
||||||
|
MAX_FILES_PER_UPLOAD=30
|
||||||
UPLOADS_DIR=./uploads
|
UPLOADS_DIR=./uploads
|
||||||
DATA_DIR=./data
|
|
||||||
CONTRACT_CLIENT_CONTACT=Testko Tester
|
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/strojno-prevajanje/pogosta-vprasanja">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>
|
||||||
MAIL_SUBJECT=RSDO: pogodba za oddana besedila ({upload_id})
|
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,26 @@ services:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- PORTALDS4DS1_MAIL_HOST=posta.cjvt.si
|
- PORTALDS4DS1_SQL_CONN_STR=postgresql://portal:randompass123@db/portal
|
||||||
- PORTALDS4DS1_MAIL_LOGIN=oddaja-besedil@cjvt.si
|
- PORTALDS4DS1_MAIL_HOST=posta.cjvt.si
|
||||||
- PORTALDS4DS1_MAIL_PASS=randompass123
|
- PORTALDS4DS1_MAIL_LOGIN=oddaja-besedil@cjvt.si
|
||||||
- PORTALDS4DS1_SMTP_PORT=465
|
- PORTALDS4DS1_MAIL_PASS=randompass123
|
||||||
- PORTALDS4DS1_IMAP_PORT=993
|
- PORTALDS4DS1_SMTP_PORT=465
|
||||||
- PORTALDS4DS1_MAX_UPLOAD_SIZE=1000000000
|
- PORTALDS4DS1_IMAP_PORT=993
|
||||||
- PORTALDS4DS1_BASE_DIR=./
|
- PORTALDS4DS1_MAX_UPLOAD_SIZE=1000000000
|
||||||
- PORTALDS4DS1_CONTRACT_CLIENT_CONTACT=Testko Tester
|
- PORTALDS4DS1_BASE_DIR=./
|
||||||
|
- PORTALDS4DS1_CONTRACT_CLIENT_CONTACT=Testko Tester
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:5000:80
|
- 127.0.0.1:5000:80
|
||||||
volumes:
|
volumes:
|
||||||
- /tmp/uploads/:/usr/src/portal-webapp/uploads
|
- /tmp/portal-ds4-ds1/uploads:/usr/src/portal-webapp/uploads
|
||||||
|
command: gunicorn --bind 0.0.0.0:80 -w 1 --access-logfile - app:app
|
||||||
|
db:
|
||||||
|
image: postgres:9.6.21-alpine
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- POSTGRES_PASSWORD=randompass123
|
||||||
|
- POSTGRES_USER=portal
|
||||||
|
- POSTGRES_DB=portal
|
||||||
|
volumes:
|
||||||
|
- /tmp/portal-ds4-ds1/db:/var/lib/postgresql/data
|
||||||
|
|
7
entrypoint.sh
Executable file
7
entrypoint.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Upgrade DB schema to version used by application. This also initializes table, if they aren't already created.
|
||||||
|
flask db upgrade
|
||||||
|
|
||||||
|
|
||||||
|
exec "$@"
|
1
migrations/README
Normal file
1
migrations/README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Generic single-database configuration.
|
50
migrations/alembic.ini
Normal file
50
migrations/alembic.ini
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic,flask_migrate
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[logger_flask_migrate]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = flask_migrate
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
90
migrations/env.py
Normal file
90
migrations/env.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
config.set_main_option(
|
||||||
|
'sqlalchemy.url',
|
||||||
|
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
|
||||||
|
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url, target_metadata=target_metadata, literal_binds=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this callback is used to prevent an auto-migration from being generated
|
||||||
|
# when there are no changes to the schema
|
||||||
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
|
connectable = current_app.extensions['migrate'].db.engine
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
process_revision_directives=process_revision_directives,
|
||||||
|
**current_app.extensions['migrate'].configure_args
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
40
migrations/versions/a846faa2b908_initial_migration.py
Normal file
40
migrations/versions/a846faa2b908_initial_migration.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
"""Initial migration.
|
||||||
|
|
||||||
|
Revision ID: a846faa2b908
|
||||||
|
Revises:
|
||||||
|
Create Date: 2021-03-24 08:53:24.792682
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a846faa2b908'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('upload_unauthenticated',
|
||||||
|
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('file_contract', sa.String(), nullable=True),
|
||||||
|
sa.Column('upload_file_hashes', sa.ARRAY(sa.String()), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('upload_unauthenticated')
|
||||||
|
# ### end Alembic commands ###
|
316
portal/base.py
316
portal/base.py
|
@ -1,8 +1,9 @@
|
||||||
import re
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
import ssl
|
import ssl
|
||||||
|
import traceback
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import imaplib
|
import imaplib
|
||||||
from smtplib import SMTP_SSL
|
from smtplib import SMTP_SSL
|
||||||
|
@ -17,9 +18,7 @@ from email.mime.application import MIMEApplication
|
||||||
import pdfkit
|
import pdfkit
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
from . model import db, UploadUnauthenticated
|
||||||
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:
|
||||||
|
@ -49,202 +48,161 @@ class ContractCreator:
|
||||||
pdfkit.from_string(html_str, out_f, options=self.pdfkit_options)
|
pdfkit.from_string(html_str, out_f, options=self.pdfkit_options)
|
||||||
|
|
||||||
|
|
||||||
contract_creator = ContractCreator()
|
class UploadHandler:
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.config = kwargs
|
||||||
|
|
||||||
|
self.contract_creator = ContractCreator()
|
||||||
|
|
||||||
|
def extract_upload_metadata(self, corpus_name, request):
|
||||||
|
upload_metadata = dict()
|
||||||
|
|
||||||
|
file_hashes = self.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_metadata['corpus_name'] = corpus_name
|
||||||
|
upload_metadata['form_data'] = form_data
|
||||||
|
upload_metadata['upload_id'] = upload_id
|
||||||
|
upload_metadata['timestamp'] = upload_timestamp
|
||||||
|
upload_metadata['file_hashes_dict'] = file_hashes
|
||||||
|
upload_metadata['file_names'] = file_names
|
||||||
|
upload_metadata['contract_file'] = upload_id + '.pdf'
|
||||||
|
|
||||||
|
return upload_metadata
|
||||||
|
|
||||||
|
|
||||||
def get_upload_metadata(corpus_name, request):
|
def get_uploads_subdir(self, dir_name):
|
||||||
upload_metadata = dict()
|
subdir = self.config['UPLOADS_DIR'] / dir_name
|
||||||
|
if not subdir.exists():
|
||||||
file_hashes = create_file_hashes(request.files)
|
subdir.mkdir(parents=True)
|
||||||
file_names = file_hashes.keys()
|
return subdir
|
||||||
form_data = request.form.copy()
|
|
||||||
upload_timestamp = int(time.time())
|
|
||||||
upload_id = create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes)
|
|
||||||
|
|
||||||
upload_metadata['corpus_name'] = corpus_name
|
|
||||||
upload_metadata['form_data'] = form_data
|
|
||||||
upload_metadata['upload_id'] = upload_id
|
|
||||||
upload_metadata['timestamp'] = upload_timestamp
|
|
||||||
upload_metadata['file_hashes'] = file_hashes
|
|
||||||
upload_metadata['file_names'] = file_names
|
|
||||||
|
|
||||||
return upload_metadata
|
|
||||||
|
|
||||||
|
|
||||||
def check_suffixes(files):
|
def create_upload_id(self, corpus_name, form_data, upload_timestamp, file_hashes):
|
||||||
for key, f in files.items():
|
# Order is important while hashing, hence the sorting.
|
||||||
if key.startswith('file'):
|
val_buff = [str(upload_timestamp)]
|
||||||
suffix = f.filename.split('.')[-1]
|
for key in sorted(form_data):
|
||||||
if suffix not in ENABLED_FILETYPES:
|
val_buff.append(form_data[key])
|
||||||
return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
# This hash serves as an unique identifier for the whole upload.
|
||||||
|
metahash = hashlib.md5((''.join(val_buff)).encode())
|
||||||
|
# Include file hashes to avoid metafile name collisions if they have the same form values,
|
||||||
|
# but different data files. Sort hashes first so upload order doesn't matter.
|
||||||
|
sorted_f_hashes = list(file_hashes.values())
|
||||||
|
sorted_f_hashes.sort()
|
||||||
|
metahash.update(''.join(sorted_f_hashes).encode())
|
||||||
|
metahash = metahash.hexdigest()
|
||||||
|
|
||||||
def get_subdir(uploads_path, dir_name):
|
return metahash
|
||||||
subdir = uploads_path / dir_name
|
|
||||||
if not subdir.exists():
|
|
||||||
subdir.mkdir(parents=True)
|
|
||||||
return subdir
|
|
||||||
|
|
||||||
|
def create_file_hashes(self, files):
|
||||||
|
res = dict()
|
||||||
|
for key, f in files.items():
|
||||||
|
if key.startswith('file'):
|
||||||
|
h = hashlib.md5(f.filename.encode())
|
||||||
|
h.update(f.stream.read())
|
||||||
|
res[f.filename] = h.hexdigest()
|
||||||
|
f.seek(0)
|
||||||
|
return res
|
||||||
|
|
||||||
def create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes):
|
def store_metadata_unauthenticated(self, upload_metadata):
|
||||||
ime = form_data.get('ime')
|
timestamp = datetime.fromtimestamp(upload_metadata['timestamp'])
|
||||||
podjetje = form_data.get('podjetje')
|
form_data = upload_metadata['form_data']
|
||||||
naslov = form_data.get('naslov')
|
file_hashes = upload_metadata['file_hashes_dict']
|
||||||
posta = form_data.get('posta')
|
sorted_f_hashes = list(file_hashes.values())
|
||||||
email = form_data.get('email')
|
sorted_f_hashes.sort()
|
||||||
telefon = form_data.get('telefon')
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
# This hash serves as an unique identifier for the whole upload.
|
db.session.add(upload_unauthenticated)
|
||||||
metahash = hashlib.md5((corpus_name+ime+podjetje+naslov+posta+email+telefon).encode())
|
db.session.commit()
|
||||||
# Include file hashes to avoid metafile name collisions if they have the same form values,
|
except Exception:
|
||||||
# but different data files. Sort hashes first so upload order doesn't matter.
|
traceback.print_exc()
|
||||||
sorted_f_hashes = list(file_hashes.values())
|
|
||||||
sorted_f_hashes.sort()
|
|
||||||
metahash.update(''.join(sorted_f_hashes).encode())
|
|
||||||
metahash = metahash.hexdigest()
|
|
||||||
|
|
||||||
return metahash
|
def store_metadata_authenticated(self, upload_metadata):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def store_datafiles(self, files, upload_metadata):
|
||||||
|
base = self.get_uploads_subdir('files')
|
||||||
|
file_hashes = upload_metadata['file_hashes_dict']
|
||||||
|
|
||||||
def check_form(form):
|
for key, f in files.items():
|
||||||
ime = form.get('ime')
|
if key.startswith('file'):
|
||||||
podjetje = form.get('podjetje')
|
path = base / file_hashes[f.filename]
|
||||||
naslov = form.get('naslov')
|
if not path.exists():
|
||||||
posta = form.get('posta')
|
path.mkdir()
|
||||||
email = form.get('email')
|
f.save(path / f.filename)
|
||||||
telefon = form.get('telefon')
|
|
||||||
|
|
||||||
if len(ime) > 100:
|
def generate_upload_contract_pdf(self, upload_metadata):
|
||||||
return 'Predolgo ime.'
|
base = self.get_uploads_subdir('contracts')
|
||||||
|
form_data = upload_metadata['form_data']
|
||||||
|
|
||||||
if len(podjetje) > 100:
|
files_table_str = []
|
||||||
return 'Predolgo ime institucije.'
|
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)
|
||||||
|
|
||||||
if len(email) > 100:
|
data = {
|
||||||
return 'Predolgi email naslov'
|
'ime_priimek': form_data['ime'],
|
||||||
elif not re.search(REGEX_EMAIL, email):
|
'naslov': form_data['naslov'],
|
||||||
return 'Email napačnega formata.'
|
'posta': form_data['posta'],
|
||||||
|
'kontakt_narocnik': self.config['CONTRACT_CLIENT_CONTACT'],
|
||||||
|
'kontakt_imetnikpravic': form_data['ime'],
|
||||||
|
'files_table_str': files_table_str
|
||||||
|
}
|
||||||
|
|
||||||
if len(telefon) > 100:
|
self.contract_creator.create_pdf(base / upload_metadata['contract_file'], data)
|
||||||
return 'Predolga telefonska št.'
|
|
||||||
|
|
||||||
if len(naslov) > 100:
|
def send_confirm_mail(self, upload_metadata):
|
||||||
return 'Predolg naslov.'
|
upload_id = upload_metadata['upload_id']
|
||||||
|
|
||||||
if len(posta) > 100:
|
message = MIMEMultipart()
|
||||||
return 'Predolga pošta'
|
message['From'] = self.config['MAIL_LOGIN']
|
||||||
|
message['To'] = upload_metadata['form_data']['email']
|
||||||
|
message['Subject'] = self.config['MAIL_SUBJECT'].format(upload_id=upload_id)
|
||||||
|
body = self.config['MAIL_BODY'].format(upload_id=upload_id)
|
||||||
|
message.attach(MIMEText(body, "plain"))
|
||||||
|
|
||||||
return None
|
contracts_dir = self.get_uploads_subdir('contracts')
|
||||||
|
base_name = upload_metadata['contract_file']
|
||||||
|
contract_file = contracts_dir / base_name
|
||||||
|
with open(contract_file, "rb") as f:
|
||||||
|
part = MIMEApplication(
|
||||||
|
f.read(),
|
||||||
|
Name = base_name
|
||||||
|
)
|
||||||
|
part['Content-Disposition'] = 'attachment; filename="%s"' % base_name
|
||||||
|
message.attach(part)
|
||||||
|
|
||||||
|
text = message.as_string()
|
||||||
|
|
||||||
def create_file_hashes(files):
|
# Create a secure SSL context
|
||||||
res = dict()
|
context = ssl.create_default_context()
|
||||||
for key, f in files.items():
|
|
||||||
if key.startswith('file'):
|
|
||||||
h = hashlib.md5(f.filename.encode())
|
|
||||||
h.update(f.stream.read())
|
|
||||||
res[f.filename] = h.hexdigest()
|
|
||||||
f.seek(0)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
def store_metadata(uploads_path, upload_metadata):
|
# Save copy of sent mail in Sent mailbox
|
||||||
base = get_subdir(uploads_path, 'meta')
|
imap = imaplib.IMAP4_SSL(self.config['MAIL_HOST'], self.config['IMAP_PORT'])
|
||||||
|
imap.login(self.config['MAIL_LOGIN'], self.config['MAIL_PASS'])
|
||||||
timestamp = upload_metadata['timestamp']
|
imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
|
||||||
upload_id = upload_metadata['upload_id']
|
imap.logout()
|
||||||
form_data = upload_metadata['form_data']
|
|
||||||
email = form_data['email']
|
|
||||||
file_hashes = upload_metadata['file_hashes']
|
|
||||||
contract = upload_metadata['contract']
|
|
||||||
filename = str(timestamp) + '-' + email + '-' + upload_id + '.meta'
|
|
||||||
|
|
||||||
sorted_f_hashes = list(file_hashes.values())
|
|
||||||
sorted_f_hashes.sort()
|
|
||||||
|
|
||||||
path = base / filename
|
|
||||||
with path.open('w') as f:
|
|
||||||
f.write('korpus=' + upload_metadata['corpus_name'])
|
|
||||||
f.write('\nime=' + form_data['ime'])
|
|
||||||
f.write('\npodjetje=' + form_data['podjetje'])
|
|
||||||
f.write('\nnaslov=' + form_data['naslov'])
|
|
||||||
f.write('\nposta=' + form_data['posta'])
|
|
||||||
f.write('\nemail=' + form_data['email'])
|
|
||||||
f.write('\ndatoteke=' + str(sorted_f_hashes))
|
|
||||||
f.write('\npogodba=' + contract)
|
|
||||||
|
|
||||||
|
|
||||||
def store_datafiles(uploads_path, files, upload_metadata):
|
|
||||||
base = get_subdir(uploads_path, 'files')
|
|
||||||
file_hashes = upload_metadata['file_hashes']
|
|
||||||
|
|
||||||
for key, f in files.items():
|
|
||||||
if key.startswith('file'):
|
|
||||||
path = base / file_hashes[f.filename]
|
|
||||||
if not path.exists():
|
|
||||||
path.mkdir()
|
|
||||||
f.save(path / f.filename)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_contract_pdf(uploads_path, upload_metadata, contract_client_contact):
|
|
||||||
base = get_subdir(uploads_path, 'contracts')
|
|
||||||
contract_file_name = upload_metadata['upload_id'] + '.pdf'
|
|
||||||
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': contract_client_contact,
|
|
||||||
'kontakt_imetnikpravic': form_data['ime'],
|
|
||||||
'files_table_str': files_table_str
|
|
||||||
}
|
|
||||||
|
|
||||||
contract_creator.create_pdf(base / contract_file_name, data)
|
|
||||||
return contract_file_name
|
|
||||||
|
|
||||||
|
|
||||||
def send_confirm_mail(subject, body, uploads_path, upload_metadata, mail_host, mail_login, mail_pass, imap_port=993, smtp_port=465):
|
|
||||||
upload_id = upload_metadata['upload_id']
|
|
||||||
|
|
||||||
message = MIMEMultipart()
|
|
||||||
message['From'] = mail_login
|
|
||||||
message['To'] = upload_metadata['form_data']['email']
|
|
||||||
message['Subject'] = subject.format(upload_id=upload_id)
|
|
||||||
body = body.format(upload_id=upload_id)
|
|
||||||
message.attach(MIMEText(body, "plain"))
|
|
||||||
|
|
||||||
contracts_dir = get_subdir(uploads_path, 'contracts')
|
|
||||||
base_name = upload_metadata['contract']
|
|
||||||
contract_file = contracts_dir / base_name
|
|
||||||
with open(contract_file, "rb") as f:
|
|
||||||
part = MIMEApplication(
|
|
||||||
f.read(),
|
|
||||||
Name = base_name
|
|
||||||
)
|
|
||||||
part['Content-Disposition'] = 'attachment; filename="%s"' % base_name
|
|
||||||
message.attach(part)
|
|
||||||
|
|
||||||
text = message.as_string()
|
|
||||||
|
|
||||||
# Create a secure SSL context
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
|
|
||||||
with SMTP_SSL(mail_host, smtp_port, context=context) as server:
|
|
||||||
server.login(mail_login, mail_pass)
|
|
||||||
server.sendmail(message['From'], message['To'], text)
|
|
||||||
|
|
||||||
# Save copy of sent mail in Sent mailbox
|
|
||||||
imap = imaplib.IMAP4_SSL(mail_host, imap_port)
|
|
||||||
imap.login(mail_login, mail_pass)
|
|
||||||
imap.append('Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
|
|
||||||
imap.logout()
|
|
||||||
|
|
||||||
|
|
26
portal/model.py
Normal file
26
portal/model.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from datetime import datetime
|
||||||
|
import sqlalchemy
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
# Model classes for describing SQL tables #
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
# The "db" object gets bound to the Flask app in app.py.
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
|
||||||
|
# Entries for uploads to corpora, that have no authentication. E.g. "prevodi" or "gigafida".
|
||||||
|
class UploadUnauthenticated(db.Model):
|
||||||
|
__tablename__ = 'upload_unauthenticated'
|
||||||
|
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)
|
||||||
|
file_contract = db.Column(db.String)
|
||||||
|
upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String))
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 951 KiB After Width: | Height: | Size: 298 KiB |
|
@ -21,14 +21,16 @@ html {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg {
|
html {
|
||||||
background-image: url("image/bg.jpeg");
|
background: url(image/bg.jpeg) no-repeat center center fixed;
|
||||||
height: 100%;
|
-webkit-background-size: cover;
|
||||||
background-position: center;
|
-moz-background-size: cover;
|
||||||
background-repeat: no-repeat;
|
-o-background-size: cover;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#main-window {
|
#main-window {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
@ -60,7 +62,7 @@ html {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
font-size: 36px;
|
font-size: 32px;
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
margin-block-start: 0.4em;
|
margin-block-start: 0.4em;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
@ -89,6 +91,15 @@ label {
|
||||||
color: #46535b;
|
color: #46535b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-text {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 12px;
|
||||||
|
color: #46535b;
|
||||||
|
}
|
||||||
|
|
||||||
#button-submit {
|
#button-submit {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -102,7 +113,7 @@ label {
|
||||||
background: #006cb7;
|
background: #006cb7;
|
||||||
border-radius: 29px;
|
border-radius: 29px;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
top: 530px;
|
top: 630px;
|
||||||
|
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -187,8 +198,8 @@ input {
|
||||||
margin-top: 26px;
|
margin-top: 26px;
|
||||||
margin-bottom: 26px;
|
margin-bottom: 26px;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: 500px;
|
max-height: 670px;
|
||||||
top: -530px;
|
top: -600px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +207,7 @@ input {
|
||||||
#rect1 {
|
#rect1 {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 388px;
|
width: 388px;
|
||||||
height: 631px;
|
height: 731px;
|
||||||
|
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
box-shadow: 0px 4px 40px rgba(0, 0, 0, 0.25);
|
box-shadow: 0px 4px 40px rgba(0, 0, 0, 0.25);
|
||||||
|
@ -238,7 +249,7 @@ input {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 40%;
|
height: 40%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding-top: 90px;
|
padding-top: 120px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
right: 75px;
|
right: 75px;
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{{ dropzone.style('position: absolute;
|
{{ dropzone.style('position: absolute;
|
||||||
top: -0.5px;
|
top: -0.5px;
|
||||||
width: 388px;
|
width: 388px;
|
||||||
height: 632px;
|
height: 732px;
|
||||||
left: 385px;
|
left: 385px;
|
||||||
background: linear-gradient(198.62deg, rgba(255, 255, 255, 0.49) -1.62%, rgba(255, 255, 255, 0.73) -1.61%, rgba(255, 255, 255, 0.41) 79.34%);
|
background: linear-gradient(198.62deg, rgba(255, 255, 255, 0.49) -1.62%, rgba(255, 255, 255, 0.73) -1.61%, rgba(255, 255, 255, 0.41) 79.34%);
|
||||||
box-shadow: 20px 4px 40px rgba(0, 0, 0, 0.25);
|
box-shadow: 20px 4px 40px rgba(0, 0, 0, 0.25);
|
||||||
|
@ -18,7 +18,6 @@
|
||||||
<link rel="stylesheet" href="static/style.css" type="text/css">
|
<link rel="stylesheet" href="static/style.css" type="text/css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="bg"></div>
|
|
||||||
<div id="main-window">
|
<div id="main-window">
|
||||||
<div id="rect1">
|
<div id="rect1">
|
||||||
<div id="logo-container">
|
<div id="logo-container">
|
||||||
|
@ -27,8 +26,8 @@
|
||||||
|
|
||||||
<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">Portal za oddajanje besedil</h1>
|
||||||
<h2 id="subtitle">{{subtitle}}</h2>
|
<div class="form-text">{{description|safe}}</div>
|
||||||
|
|
||||||
<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"/>
|
||||||
|
@ -48,8 +47,7 @@
|
||||||
<label for="telefon">Telefon:</label>
|
<label for="telefon">Telefon:</label>
|
||||||
<input type="text" id="telefon" name="telefon"/>
|
<input type="text" id="telefon" name="telefon"/>
|
||||||
|
|
||||||
<input type="checkbox" id="izjava" name="izjava" value="izjava" required="required">
|
<div class="form-text">*Po kliku na gumb “Oddaj” se bo prikazala vsebina pogodobe o odstopu avtorskih pravic. Če se z vsebino strinjate, kliknite gumb “Pošlji”, da podatke posredujete v korpus, po e-pošti pa boste prejeli svoj izvod pogodbe.</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<button id="button-submit" type="submit">Oddaj</button>
|
<button id="button-submit" type="submit">Oddaj</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -163,7 +161,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
|
||||||
<p>10.3. Pogodbeni stranki s podpisom potrjujeta veljavnost te pogodbe.</p>
|
<p>10.3. Pogodbeni stranki s podpisom potrjujeta veljavnost te pogodbe.</p>
|
||||||
</div>
|
</div>
|
||||||
<button id="button-submit-cancel" class="button-terms" style="background: #ff2d2d;">Prekliči</button>
|
<button id="button-submit-cancel" class="button-terms" style="background: #ff2d2d;">Prekliči</button>
|
||||||
<button id="button-submit-final" class="button-terms">Oddaj</button>
|
<button id="button-submit-final" class="button-terms">Pošlji</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--{{ dropzone.load_js() }}-->
|
<!--{{ dropzone.load_js() }}-->
|
||||||
|
@ -190,17 +188,17 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
|
||||||
url: "/{{corpus_name}}/upload",
|
url: "/{{corpus_name}}/upload",
|
||||||
autoProcessQueue: false,
|
autoProcessQueue: false,
|
||||||
uploadMultiple: true,
|
uploadMultiple: true,
|
||||||
parallelUploads: 20,
|
parallelUploads: {{max_files}},
|
||||||
paramName: "file", // The name that will be used to transfer the file
|
paramName: "file", // The name that will be used to transfer the file
|
||||||
maxFilesize: 1000, // MB
|
maxFilesize: 1000, // MB
|
||||||
acceptedFiles: ".txt, .csv, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx",
|
acceptedFiles: ".txt, .csv, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx",
|
||||||
maxFiles: 20,
|
maxFiles: {{max_files}},
|
||||||
dictDefaultMessage: `Kliknite ali odložite datoteke sem.`,
|
dictDefaultMessage: `Kliknite ali odložite datoteke sem.`,
|
||||||
dictFallbackMessage: "Vaš brskalnik ne podpira izbiranje datotek z odlaganjem (\"drag & drop\").",
|
dictFallbackMessage: "Vaš brskalnik ne podpira izbiranje datotek z odlaganjem (\"drag & drop\").",
|
||||||
dictInvalidFileType: "Datoteka je napačnega formata.",
|
dictInvalidFileType: "Datoteka je napačnega formata.",
|
||||||
dictFileTooBig: "Datoteke je prevelika {{filesize}}. Največja dovoljena velikost: {{maxFilesize}}MiB.",
|
dictFileTooBig: "Datoteke je prevelika {{filesize}}. Največja dovoljena velikost: {{maxFilesize}}MiB.",
|
||||||
dictResponseError: "Napaka strežnika: {{statusCode}}",
|
dictResponseError: "Napaka strežnika: {{statusCode}}",
|
||||||
dictMaxFilesExceeded: "Ne morete naložiti več datotek.",
|
dictMaxFilesExceeded: "Največje število datotek že doseženo.",
|
||||||
dictCancelUpload: "Prekini prenos",
|
dictCancelUpload: "Prekini prenos",
|
||||||
dictRemoveFile: "Odstrani datoteko",
|
dictRemoveFile: "Odstrani datoteko",
|
||||||
dictCancelUploadConfirmation: "Ali res želite odstraniti to datoteko?",
|
dictCancelUploadConfirmation: "Ali res želite odstraniti to datoteko?",
|
||||||
|
@ -221,8 +219,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
|
||||||
var email = form["email"].value;
|
var email = form["email"].value;
|
||||||
var podjetje = form["podjetje"].value;
|
var podjetje = form["podjetje"].value;
|
||||||
var telefon = form["telefon"].value;
|
var telefon = form["telefon"].value;
|
||||||
var izjava = form["izjava"].checked;
|
if (isEmptyOrSpaces(ime) || isEmptyOrSpaces(email)) {
|
||||||
if (isEmptyOrSpaces(ime) || isEmptyOrSpaces(email) || !izjava) {
|
|
||||||
alert("Izpolnite vsa obvezna polja!");
|
alert("Izpolnite vsa obvezna polja!");
|
||||||
} else if (!reEmail.test(email.toLowerCase())) {
|
} else if (!reEmail.test(email.toLowerCase())) {
|
||||||
alert("Email napačnega formata!");
|
alert("Email napačnega formata!");
|
||||||
|
|
300
templates/index-solar.html
Normal file
300
templates/index-solar.html
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Portal za oddajanje besedil</title>
|
||||||
|
<!--{{ dropzone.load_css() }}-->
|
||||||
|
<link rel="stylesheet" href="static/dropzone.css" type="text/css">
|
||||||
|
{{ dropzone.style('position: absolute;
|
||||||
|
top: -0.5px;
|
||||||
|
width: 388px;
|
||||||
|
height: 632px;
|
||||||
|
left: 385px;
|
||||||
|
background: linear-gradient(198.62deg, rgba(255, 255, 255, 0.49) -1.62%, rgba(255, 255, 255, 0.73) -1.61%, rgba(255, 255, 255, 0.41) 79.34%);
|
||||||
|
box-shadow: 20px 4px 40px rgba(0, 0, 0, 0.25);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 0px 20px 20px 0px;') }}
|
||||||
|
<link rel="stylesheet" href="static/style.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg"></div>
|
||||||
|
<div id="main-window">
|
||||||
|
<div id="rect1">
|
||||||
|
<div id="logo-container">
|
||||||
|
<img src="static/image/logo.svg" alt="logo"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="my-dropzone" class="dropzone">
|
||||||
|
<div style="position: relative; right: 390px;">
|
||||||
|
<h1 id="title">Portal za oddajanje besedil {{corpus_name}}</h1>
|
||||||
|
<h2 id="subtitle">{{subtitle}}</h2>
|
||||||
|
|
||||||
|
<label for="ime">* Ime:</label>
|
||||||
|
<input type="text" id="ime" name="ime" required="required"/>
|
||||||
|
|
||||||
|
<label for="podjetje">Podjetje / institucija:</label>
|
||||||
|
<input type="text" id="podjetje" name="podjetje"/>
|
||||||
|
|
||||||
|
<label for="naslov">Naslov:</label>
|
||||||
|
<input type="text" id="naslov" name="naslov"/>
|
||||||
|
|
||||||
|
<label for="posta">Pošta:</label>
|
||||||
|
<input type="text" id="posta" name="posta"/>
|
||||||
|
|
||||||
|
<label for="email">* E-Pošta:</label>
|
||||||
|
<input type="text" id="email" name="email" required="required"/>
|
||||||
|
|
||||||
|
<label for="telefon">Telefon:</label>
|
||||||
|
<input type="text" id="telefon" name="telefon"/>
|
||||||
|
|
||||||
|
<input type="checkbox" id="izjava" name="izjava" value="izjava" required="required">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<button id="button-submit" type="submit">Oddaj</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dropzone-previews"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="popup-terms" style="display: none">
|
||||||
|
<div id="popup-terms-text">
|
||||||
|
<h2>POGODBA O PRENOSU AVTORSKIH PRAVIC</h2>
|
||||||
|
|
||||||
|
<h3>UVODNE DOLOČBE</h3>
|
||||||
|
<h4><b>1. člen</b></h4>
|
||||||
|
|
||||||
|
<p>1.1. Stranki uvodoma ugotavljata, da naročnik izvaja projekt Razvoj slovenščine v digitalnem
|
||||||
|
okolju – RSDO (v nadaljevanju projekt RSDO), ki je bil na javnem razpisu Razvoj slovenščine v
|
||||||
|
digitalnem okolju – jezikovni viri in tehnologije (JR-ESRR-Razvoj slovenščine v digitalnem
|
||||||
|
okolju), objavljenem v Uradnem listu RS št. 70/19 dne 29. 11. 2019, sprejet v sofinanciranje
|
||||||
|
in katerega vsebina je razvidna s spletnih strani https://slovenscina.eu.</p>
|
||||||
|
|
||||||
|
<p>1.2. Stranki uvodoma ugotavljata, da bo naročnik v okviru projekta RSDO:
|
||||||
|
- izdelal osrednjo digitalno slovarsko bazo, ki združuje različne tipe jezikovnih podatkov o
|
||||||
|
slovenščini v odprtem dostopu,
|
||||||
|
- izdelal terminološki portal z integriranim iskalnikom po slovenskih terminoloških virih, zlasti
|
||||||
|
terminoloških slovarjih,
|
||||||
|
- izdelal korpus prevodov po različnih domenah za učenje strojnega prevajalnika za jezikovni
|
||||||
|
par angleščina-slovenščina in slovenščina-angleščina.</p>
|
||||||
|
<p>1.3. Stranki uvodoma ugotavljata, da bo naročnik pri projektu RSDO za vse zgoraj opisane
|
||||||
|
namene zbiral in uporabil besedilne vire, ki so navedeni v prilogi k tej pogodbi in ki so lahko
|
||||||
|
avtorska dela ali drugi predmeti varstva v skladu z Zakonom o avtorski in sorodnih pravicah
|
||||||
|
(Uradni list RS, št. 16/07 – uradno prečiščeno besedilo, 68/08, 110/13, 56/15, 63/16 – ZKUASP
|
||||||
|
in 59/19; ZASP) in na katerih ima imetnik pravic avtorske, avtorski sorodne ali druge pravice v
|
||||||
|
skladu z ZASP (v nadaljevanju avtorska dela).</p>
|
||||||
|
|
||||||
|
<p>1.4. Stranki ugotavljata, da bodo avtorska dela in vse njihove morebitne spremembe in
|
||||||
|
predelave, ter zbirke podatkov, ki bodo med izvajanjem projekta RSDO nastale, javno
|
||||||
|
dostopni pod pogoji prostih licenc (npr. CC BY-SA) in bodo na voljo za nekomercialen in
|
||||||
|
komercialen razvoj tehnologij, za raziskave in za druge raziskovalne namene
|
||||||
|
posameznikom, raziskovalnim in izobraževalnim institucijam, neprofitnim organizacijam,
|
||||||
|
državnim organom, organizacijam z javnimi pooblastili in gospodarskim družbam v Sloveniji
|
||||||
|
in tujini.</p>
|
||||||
|
|
||||||
|
<h3>PREDMET POGODBE</h3>
|
||||||
|
<h4><b>2. člen</b></h4>
|
||||||
|
|
||||||
|
<p>2.1. Predmet pogodbe so vsa avtorska dela imetnika pravic, ki so navedena v prilogi k tej
|
||||||
|
pogodbi.</p>
|
||||||
|
|
||||||
|
<p>2.2. S podpisom te pogodbe imetnik avtorskih pravic na naročnika prenaša avtorske pravice
|
||||||
|
na avtorskih delih na način in v obsegu, kakor je navedeno v 3. členu te pogodbe.</p>
|
||||||
|
|
||||||
|
<h3>PRENOS AVTORSKIH PRAVIC</h3>
|
||||||
|
<h4><b>3. člen</b></h4>
|
||||||
|
|
||||||
|
<p>3.1. S podpisom te pogodbe imetnik pravic na avtorskih delih, ki so predmet te pogodbe, na
|
||||||
|
naročnika neizključno, brez časovnih in teritorialnih omejitev prenaša vse materialne avtorske
|
||||||
|
pravice, avtorski sorodne pravice in druge pravice avtorja v skladu z ZASP, zlasti pravico
|
||||||
|
reproduciranja (23. člen ZASP), distribuiranja (24. člena ZASP), dajanja v najem (25. člen ZASP),
|
||||||
|
priobčitve javnosti (26. do 32.a člen ZASP), vključno s pravico dajanja na voljo javnosti (32.a
|
||||||
|
člen ZASP) in pravico predelave (33. člen ZASP).</p>
|
||||||
|
|
||||||
|
<p>3.2. S podpisom te pogodbe imetnik pravic izrecno soglaša, da naročnik pravice iz točke 3.1.
|
||||||
|
prenaša naprej na tretje osebe brez omejitev.</p>
|
||||||
|
|
||||||
|
<h3>JAMČEVANJE IMETNIKA PRAVIC</h3>
|
||||||
|
|
||||||
|
<h4><b>4. člen</b></h4>
|
||||||
|
|
||||||
|
<p>4.1. S podpisom te pogodbe imetnik pravic jamči, da je na avtorskih delih, ki so predmet te
|
||||||
|
pogodbe, imetnik vseh avtorskih pravic, avtorski sorodnih pravic in drugih pravic avtorja v
|
||||||
|
skladu z ZASP, ki so potrebne za prenos pravic po tej pogodbi, in da na avtorskih delih ne
|
||||||
|
obstajajo pravice tretjih oseb, ki bi naročniku preprečevale njihovo uporabo.</p>
|
||||||
|
|
||||||
|
<p>4.2. Določbe te pogodbe ne vplivajo na prenos moralnih avtorskih pravic, ki so v skladu z
|
||||||
|
določbami ZASP neprenosljive.</p>
|
||||||
|
|
||||||
|
<h3>OSEBNI PODATKI</h3>
|
||||||
|
<h4><b>5. člen</b></h4>
|
||||||
|
|
||||||
|
<p>6.1. Stranki se zavezujeta, da bosta vse morebitne osebne podatke, ki jih bosta obdelovali za
|
||||||
|
namene izvajanja te pogodbe, obdelovali na način, da bosta upoštevali vse veljavne predpise
|
||||||
|
o varstvu osebnih podatkov in da bosta posameznikom, na katere se osebni podatki nanašajo,
|
||||||
|
zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatkov.<p>
|
||||||
|
|
||||||
|
<h3>KONTAKTNE OSEBE</h3>
|
||||||
|
<h4><b>6. člen</b></h4>
|
||||||
|
|
||||||
|
<p>7.1 Kontaktna oseba za izvedbo te pogodbe na strani naročnika je [xxx].</p>
|
||||||
|
<p>7.2. Kontaktna oseba za izvedbo te pogodbe na strani imetnika pravic je [xxx].</p>
|
||||||
|
|
||||||
|
<h3>KONČNE DOLOČBE</h3>
|
||||||
|
<h4><b>7. člen</b></h4>
|
||||||
|
|
||||||
|
<p>8.1. Če je katerakoli določba te pogodbe nična, ostanejo druga določila te pogodbe v veljavi.</p>
|
||||||
|
|
||||||
|
<h4><b>8. člen</b></h4>
|
||||||
|
|
||||||
|
<p>9.1. Za razmerja v zvezi s to pogodbo se uporabljajo pravni predpisi Republike Slovenije.</p>
|
||||||
|
<p>9.2. Spore iz te pogodbe bosta stranki reševali po mirni poti. V primeru, da mirna rešitev ne
|
||||||
|
bo mogoča, je za vse spore v zvezi s to pogodbo pristojno sodišče v Ljubljani.</p>
|
||||||
|
|
||||||
|
<h4><b>9. člen</b></h4>
|
||||||
|
|
||||||
|
<p>10.1. Ta pogodba nadomešča vsa predhodna pogajanja, ponudbe in druge dogovore med
|
||||||
|
strankama.</p>
|
||||||
|
<p>10.2. Ta pogodba je sestavljena v [dveh] istovetnih izvodih, od katerih prejme vsaka stranka
|
||||||
|
po enega.</p>
|
||||||
|
<p>10.3. Pogodbeni stranki s podpisom potrjujeta veljavnost te pogodbe.</p>
|
||||||
|
</div>
|
||||||
|
<button id="button-submit-cancel" class="button-terms" style="background: #ff2d2d;">Prekliči</button>
|
||||||
|
<button id="button-submit-final" class="button-terms">Oddaj</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--{{ dropzone.load_js() }}-->
|
||||||
|
<script src="static/dropzone.js"></script>
|
||||||
|
<script>
|
||||||
|
/////////////////////////
|
||||||
|
// Dropzone //
|
||||||
|
/////////////////////////
|
||||||
|
var btnSubmit = document.getElementById("button-submit");
|
||||||
|
var btnSubmitFinal = document.getElementById("button-submit-final");
|
||||||
|
var btnSubmitCancel = document.getElementById("button-submit-cancel");
|
||||||
|
var elemTermsPopup = document.getElementById("popup-terms");
|
||||||
|
var termsScrollbox = document.getElementById("popup-terms-text");
|
||||||
|
var scrollboxTriggered = false;
|
||||||
|
var form = document.forms["my-dropzone"];
|
||||||
|
|
||||||
|
function isEmptyOrSpaces(str){
|
||||||
|
return str == null || str.match(/^ *$/) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
|
Dropzone.options.myDropzone = { // The camelized version of the ID of the form element
|
||||||
|
url: "/{{corpus_name}}/upload",
|
||||||
|
autoProcessQueue: false,
|
||||||
|
uploadMultiple: true,
|
||||||
|
parallelUploads: 20,
|
||||||
|
paramName: "file", // The name that will be used to transfer the file
|
||||||
|
maxFilesize: 1000, // MB
|
||||||
|
acceptedFiles: ".txt, .csv, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx",
|
||||||
|
maxFiles: 20,
|
||||||
|
dictDefaultMessage: `Kliknite ali odložite datoteke sem.`,
|
||||||
|
dictFallbackMessage: "Vaš brskalnik ne podpira izbiranje datotek z odlaganjem (\"drag & drop\").",
|
||||||
|
dictInvalidFileType: "Datoteka je napačnega formata.",
|
||||||
|
dictFileTooBig: "Datoteke je prevelika {{filesize}}. Največja dovoljena velikost: {{maxFilesize}}MiB.",
|
||||||
|
dictResponseError: "Napaka strežnika: {{statusCode}}",
|
||||||
|
dictMaxFilesExceeded: "Ne morete naložiti več datotek.",
|
||||||
|
dictCancelUpload: "Prekini prenos",
|
||||||
|
dictRemoveFile: "Odstrani datoteko",
|
||||||
|
dictCancelUploadConfirmation: "Ali res želite odstraniti to datoteko?",
|
||||||
|
dictUploadCanceled: "Prenos prekinjen",
|
||||||
|
|
||||||
|
// The setting up of the dropzone
|
||||||
|
init: function() {
|
||||||
|
var dz = this;
|
||||||
|
|
||||||
|
|
||||||
|
btnSubmit.addEventListener("click", function(e) {
|
||||||
|
// Make sure that the form isn't actually being sent.
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Check form validity.
|
||||||
|
var ime = form["ime"].value;
|
||||||
|
var email = form["email"].value;
|
||||||
|
var podjetje = form["podjetje"].value;
|
||||||
|
var telefon = form["telefon"].value;
|
||||||
|
var izjava = form["izjava"].checked;
|
||||||
|
if (isEmptyOrSpaces(ime) || isEmptyOrSpaces(email) || !izjava) {
|
||||||
|
alert("Izpolnite vsa obvezna polja!");
|
||||||
|
} else if (!reEmail.test(email.toLowerCase())) {
|
||||||
|
alert("Email napačnega formata!");
|
||||||
|
} else if (ime.length > 100 || email.length > 100 || podjetje.length > 100 || telefon.length > 100) {
|
||||||
|
alert("Velikost polj je omejena na 100 znakov.");
|
||||||
|
} else {
|
||||||
|
// Then make terms popup visible
|
||||||
|
btnSubmit.disabled = true;
|
||||||
|
btnSubmitFinal.disabled = true;
|
||||||
|
elemTermsPopup.style.display = "inline";
|
||||||
|
scrollboxTriggered = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// First change the button to actually tell Dropzone to process the queue.
|
||||||
|
btnSubmitFinal.addEventListener("click", function(e) {
|
||||||
|
// Hand off data to dropzone
|
||||||
|
dz.processQueue();
|
||||||
|
|
||||||
|
// Clear fields and hide popup agian
|
||||||
|
btnSubmit.disabled = false;
|
||||||
|
elemTermsPopup.style.display = "none";
|
||||||
|
form.reset();
|
||||||
|
scrollboxTriggered = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
btnSubmitCancel.addEventListener("click", function(e) {
|
||||||
|
btnSubmit.disabled = false;
|
||||||
|
scrollboxTriggered = false;
|
||||||
|
elemTermsPopup.style.display = "none";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable final submit button only if user scrolls to the end of the terms.
|
||||||
|
function checkScrollboxTrigger(event) {
|
||||||
|
var element = event.target;
|
||||||
|
if (!scrollboxTriggered
|
||||||
|
&& element.scrollHeight - element.scrollTop <= element.clientHeight + 50
|
||||||
|
) {
|
||||||
|
scrollboxTriggered = true;
|
||||||
|
btnSubmitFinal.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
termsScrollbox.addEventListener('scroll', function(event) {
|
||||||
|
checkScrollboxTrigger(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
termsScrollbox.addEventListener("mouseenter", function(event) {
|
||||||
|
checkScrollboxTrigger(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to the sendingmultiple event. In this case, it's the sendingmultiple event instead
|
||||||
|
// of the sending event because uploadMultiple is set to true.
|
||||||
|
this.on("sendingmultiple", function() {
|
||||||
|
// Gets triggered when the form is actually being sent.
|
||||||
|
// Hide the success button or the complete form.
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("successmultiple", function(files, response) {
|
||||||
|
// Gets triggered when the files have successfully been sent.
|
||||||
|
// Redirect user or notify of success.
|
||||||
|
alert("Odgovor strežnika: " + response);
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("errormultiple", function(files, response) {
|
||||||
|
// Gets triggered when there was an error sending the files.
|
||||||
|
// Maybe show form again, and notify user of error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user