diff --git a/Dockerfile b/Dockerfile index b0fd78d..0509ad2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,10 @@ FROM python:3.9 COPY app.py /usr/src/portal-webapp/ +COPY config.ini /usr/src/portal-webapp/ COPY templates /usr/src/portal-webapp/templates +COPY static /usr/src/portal-webapp/static +COPY contract/ /usr/src/portal-webapp/contract WORKDIR /usr/src/portal-webapp RUN pip install --no-cache-dir flask flask-dropzone gunicorn diff --git a/app.py b/app.py index 81da579..da32e53 100644 --- a/app.py +++ b/app.py @@ -2,31 +2,110 @@ import os import re import hashlib import time +import ssl +import configparser from pathlib import Path from flask import Flask, render_template, request from flask_dropzone import Dropzone -from smtplib import SMTP_SSL as SMTP - - -enabled_filetypes = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'] -regex_email = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$') -basedir = Path(__file__).resolve().parent -upload_dir = basedir / 'uploads' -if not upload_dir.exists: - upload_dir.mkdir() +import imaplib +from smtplib import SMTP_SSL + +import email +from email import encoders +from email.mime.base import MIMEBase +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.application import MIMEApplication + +import pdfkit +from jinja2 import Environment, FileSystemLoader + + +class ContractCreator: + + def __init__(self): + template_loader = FileSystemLoader(searchpath="./") + template_env = Environment(loader=template_loader) + self.template = template_env.get_template('contract/template.html') + + self.pdfkit_options = { + 'page-size': 'A4', + 'margin-top': '0.75in', + 'margin-right': '0.75in', + 'margin-bottom': '0.75in', + 'margin-left': '0.75in', + 'encoding': "UTF-8", + 'custom-header' : [ + ('Accept-Encoding', 'gzip') + ] + } + + def fill_template(self, **kwargs): + return self.template.render(**kwargs) + + def create_pdf(self, out_f, fields_dict): + html_str = self.fill_template(**fields_dict) + pdfkit.from_string(html_str, out_f, options=self.pdfkit_options) + + +ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'] +REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$') + +###################### +# Load configuration # +###################### +config = configparser.ConfigParser() +config.read('config.ini') +config = config['DEFAULT'] + +MAIL_HOST = config['MAIL_HOST'] +MAIL_LOGIN = config['MAIL_LOGIN'] +MAIL_PASS = config['MAIL_PASS'] +SMTP_PORT = int(config['SMTP_PORT']) +IMAP_PORT = int(config['IMAP_PORT']) +MAX_UPLOAD_SIZE = int(config['MAX_UPLOAD_SIZE']) # Bytes +CONTRACT_CLIENT_CONTACT = config['CONTRACT_CLIENT_CONTACT'] +if 'BASE_DIR' in config: + BASE_DIR = Path(config['BASE_DIR']) +else: + BASE_DIR = Path(__file__).resolve().parent + +# Override configs with environment variables, if set +if 'PORTALDS4DS1_MAIL_HOST' in os.environ: + MAIL_HOST = os.environ('PORTALDS4DS1_MAIL_HOST') +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_SMTP_PORT' in os.environ: + SMTP_PORT = int(os.environ('PORTALDS4DS1_SMTP_PORT')) +if 'PORTALDS4DS1_IMAP_PORT' in os.environ: + IMAP_PORT = int(os.environ('PORTALDS4DS1_IMAP_PORT')) +if 'MAX_UPLOAD_SIZE' in os.environ: + MAX_UPLOAD_SIZE = int(os.environ('PORTALDS4DS1_MAX_UPLOAD_SIZE')) +if 'CONTRACT_CLIENT_CONTACT' in os.environ: + CONTRACT_CLIENT_CONTACT = os.environ('PORTALDS4DS1_CONTRACT_CLIENT_CONTACT') + +UPLOAD_DIR = BASE_DIR / 'uploads' +if not UPLOAD_DIR.exists: + UPLOAD_DIR.mkdir(parents=True) + +###################### app = Flask(__name__) app.config.update( - UPLOADED_PATH = upload_dir, - MAX_CONTENT_LENGTH = 1000000000, # 1GB + UPLOADED_PATH = UPLOAD_DIR, + MAX_CONTENT_LENGTH = MAX_UPLOAD_SIZE, TEMPLATES_AUTO_RELOAD = True ) dropzone = Dropzone(app) +contract_creator = ContractCreator() + @app.route('/') def index(): @@ -49,28 +128,75 @@ def handle_upload(): if err: return err, 400 - file_hashes = create_file_hashes(files) - - store_metadata(request.form, file_hashes) - store_datafiles(files, file_hashes) - send_confirm_mail(request.form.get('email')) + upload_metadata = get_upload_metadata(request) + contract_file_name = generate_contract_pdf(upload_metadata) + # Add contract_file_name to metadata TODO: move somewhere else + upload_metadata['contract'] = contract_file_name + store_datafiles(files, upload_metadata) + store_metadata(upload_metadata) + send_confirm_mail(upload_metadata) return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(files)) +def get_upload_metadata(request): + upload_metadata = dict() + + file_hashes = create_file_hashes(request.files) + form_data = request.form.copy() + upload_timestamp = int(time.time()) + upload_id = create_upload_id(form_data, upload_timestamp, file_hashes) + + upload_metadata['form_data'] = form_data + upload_metadata['upload_id'] = upload_id + upload_metadata['timestamp'] = upload_timestamp + upload_metadata['file_hashes'] = file_hashes + + return upload_metadata + 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: + if suffix not in ENABLED_FILETYPES: return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename) return None +def get_subdir(dir_name): + subdir = app.config['UPLOADED_PATH'] / dir_name + if not subdir.exists(): + subdir.mkdir() + return subdir + + +def create_upload_id(form_data, upload_timestamp, file_hashes): + tip = form_data.get('tip') + ime = form_data.get('ime') + podjetje = form_data.get('podjetje') + naslov = form_data.get('naslov') + posta = form_data.get('posta') + email = form_data.get('email') + telefon = form_data.get('telefon') + + # This hash serves as an unique identifier for the whole upload. + metahash = hashlib.md5((tip+ime+podjetje+naslov+posta+email+telefon).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() + + return metahash + + def check_form(form): tip = form.get('tip') ime = form.get('ime') podjetje = form.get('podjetje') + naslov = form.get('naslov') + posta = form.get('posta') email = form.get('email') telefon = form.get('telefon') @@ -81,16 +207,22 @@ def check_form(form): return 'Predolgo ime.' if len(podjetje) > 100: - return 'Predolgo ime institucije' + return 'Predolgo ime institucije.' if len(email) > 100: return 'Predolgi email naslov' - elif not re.search(regex_email, email): + 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 @@ -105,42 +237,35 @@ def create_file_hashes(files): return res -def store_metadata(form, file_hashes): - base = app.config['UPLOADED_PATH'] / 'meta' - if not base.exists(): - base.mkdir() +def store_metadata(upload_metadata): + base = get_subdir('meta') - tip = form.get('tip') - ime = form.get('ime') - podjetje = form.get('podjetje') - email = form.get('email') - telefon = form.get('telefon') + timestamp = upload_metadata['timestamp'] + upload_id = upload_metadata['upload_id'] + 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' - # This hash serves as an identifier for the whole upload. - metahash = hashlib.md5((tip+ime+podjetje+email+telefon).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() - - timestamp = int(time.time()) - filename = str(timestamp) + '-' + email + '-' + metahash + '.meta' path = base / filename with path.open('w') as f: - f.write('tip=' + tip) - f.write('\nime=' + ime) - f.write('\npodjetje=' + podjetje) - f.write('\nemail=' + email) + f.write('tip=' + form_data['tip']) + 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(files, file_hashes): - base = app.config['UPLOADED_PATH'] / 'files' - if not base.exists(): - base.mkdir() +def store_datafiles(files, upload_metadata): + base = get_subdir('files') + file_hashes = upload_metadata['file_hashes'] for key, f in files.items(): if key.startswith('file'): @@ -149,19 +274,55 @@ def store_datafiles(files, file_hashes): path.mkdir() f.save(path / f.filename) -def send_confirm_mail(email): - #msg = MIMEText(content, text_subtype) - #msg['Subject'] = "TEST" - #msg['From'] = sender # some SMTP servers will do this automatically, not all - - #conn = SMTP(SMTPserver) - #conn.set_debuglevel(False) - #conn.login(USERNAME, PASSWORD) - #try: - # conn.sendmail(sender, destination, msg.as_string()) - #finally: - # conn.quit() - pass +def generate_contract_pdf(upload_metadata): + base = get_subdir('contracts') + contract_file_name = upload_metadata['upload_id'] + '.pdf' + form_data = upload_metadata['form_data'] + data = { + 'ime_priimek': form_data['ime'], + 'naslov': form_data['naslov'], + 'posta': form_data['posta'], + 'kontakt_narocnik': CONTRACT_CLIENT_CONTACT, + 'kontakt_imetnikpravic': form_data['ime'] + } + + contract_creator.create_pdf(base / contract_file_name, data) + return contract_file_name + +def send_confirm_mail(upload_metadata): + body = 'Usprešno ste oddali besedila. V prilogi vam pošiljamo pogodbo.' + + message = MIMEMultipart() + message['From'] = MAIL_LOGIN + message['To'] = upload_metadata['form_data']['email'] + message['Subject'] = 'Pogodba za oddana besedila ' + upload_metadata['upload_id'] + message.attach(MIMEText(body, "plain")) + + contracts_dir = get_subdir('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() if __name__ == '__main__': diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..d340504 --- /dev/null +++ b/config.ini @@ -0,0 +1,9 @@ +[DEFAULT] +MAIL_HOST=posta.cjvt.si +MAIL_LOGIN=oddaja-besedil@cjvt.si +MAIL_PASS=randompass123 +SMTP_PORT=465 +IMAP_PORT=993 +MAX_UPLOAD_SIZE=1000000000 +BASE_DIR=./ +CONTRACT_CLIENT_CONTACT=Testko Tester diff --git a/contract/out.pdf b/contract/out.pdf new file mode 100644 index 0000000..34c3b9a Binary files /dev/null and b/contract/out.pdf differ diff --git a/contract/pdf.py b/contract/pdf.py new file mode 100644 index 0000000..c8b55e8 --- /dev/null +++ b/contract/pdf.py @@ -0,0 +1,43 @@ +import pdfkit +from jinja2 import Environment, FileSystemLoader + + +class ContractCreator: + + def __init__(self): + template_loader = FileSystemLoader(searchpath="./") + template_env = Environment(loader=template_loader) + self.template = template_env.get_template('template.html') + + self.pdfkit_options = { + 'page-size': 'A4', + 'margin-top': '0.75in', + 'margin-right': '0.75in', + 'margin-bottom': '0.75in', + 'margin-left': '0.75in', + 'encoding': "UTF-8", + 'custom-header' : [ + ('Accept-Encoding', 'gzip') + ] + } + + def fill_template(self, **kwargs): + return self.template.render(**kwargs) + + def create_pdf(self, out_f, fields_dict): + html_str = self.fill_template(**fields_dict) + pdfkit.from_string(html_str, out_f, options=self.pdfkit_options) + + +if __name__ == '__main__': + test_data = { + 'ime_priimek': 'Testko Tester', + 'naslov': 'Testovci 10', + 'posta': '1123', + 'kontakt_narocnik': 'Testica Testkovič', + 'kontakt_imetnikpravic': 'Testko Tester', + 'date': '16.2.2021' + } + + contract_creator = ContractCreator() + contract_creator.create_pdf('out.pdf', test_data) diff --git a/contract/template.html b/contract/template.html new file mode 100644 index 0000000..1bf585e --- /dev/null +++ b/contract/template.html @@ -0,0 +1,202 @@ + + + + + + +

Univerza v Ljubljani
+ Kongresni trg 12
+ 1000 Ljubljana
+ matična številka: 5085063000
+ davčna številka: 54162513
+
+ (v nadaljevanju naročnik)
+
+ in
+
+ + {{ime_priimek}}
+ {{naslov}}
+ {{posta}}
+
+
+ (v nadaljevanju imetnik pravic)
+
+ v nadaljevanju skupaj stranki
+
+ sklepata naslednjo
+ +

+ +

POGODBO O PRENOSU AVTORSKIH PRAVIC

+ +

UVODNE DOLOČBE

+

1. člen

+ +

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.

+ +

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.

+

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).

+ +

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.

+ +

PREDMET POGODBE

+

2. člen

+ +

2.1. Predmet pogodbe so vsa avtorska dela imetnika pravic, ki so navedena v prilogi k tej + pogodbi.

+ +

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.

+ +

PRENOS AVTORSKIH PRAVIC

+

3. člen

+ +

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).

+ +

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.

+ +

JAMČEVANJE IMETNIKA PRAVIC

+ +

4. člen

+ +

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.

+ +

4.2. Določbe te pogodbe ne vplivajo na prenos moralnih avtorskih pravic, ki so v skladu z + določbami ZASP neprenosljive.

+ +

OSEBNI PODATKI

+

5. člen

+ +

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.

+ +

KONTAKTNE OSEBE

+

6. člen

+ +

7.1 Kontaktna oseba za izvedbo te pogodbe na strani naročnika je {{kontakt_narocnik}}.

+

7.2. Kontaktna oseba za izvedbo te pogodbe na strani imetnika pravic {{kontakt_imetnikpravic}}.

+ +

KONČNE DOLOČBE

+

7. člen

+ +

8.1. Če je katerakoli določba te pogodbe nična, ostanejo druga določila te pogodbe v veljavi.

+ +

8. člen

+ +

9.1. Za razmerja v zvezi s to pogodbo se uporabljajo pravni predpisi Republike Slovenije.

+

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.

+ +

9. člen

+ +

10.1. Ta pogodba nadomešča vsa predhodna pogajanja, ponudbe in druge dogovore med + strankama.

+

10.2. Ta pogodba je sestavljena v [dveh] istovetnih izvodih, od katerih prejme vsaka stranka + po enega.

+

10.3. Pogodbeni stranki s podpisom potrjujeta veljavnost te pogodbe.

+ +

V Ljubljani, dne {{date}}

+ +
+
+ +
+
+

Naročnik:

+

+

_____________________

+
+
+

Imetnik pravic:

+

+

_____________________

+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +

Priloga k pogodbi o prenosu avtorskih pravic: seznam avtorskih del, ki so predmet pogodbe

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Ime, naslov ali oznaka dela
 
 
 
 
 
 
 
+
+ + diff --git a/static/style.css b/static/style.css index 7197625..2adeb80 100644 --- a/static/style.css +++ b/static/style.css @@ -78,7 +78,7 @@ label { background: #006cb7; border-radius: 29px; border: 0px; - top: 430px; + top: 530px; font-family: Roboto; font-style: normal; @@ -164,7 +164,7 @@ input { margin-bottom: 16px; min-height: 100px; max-height: 500px; - top: -430px; + top: -530px; overflow-x: hidden; overflow-y: auto; } @@ -172,7 +172,7 @@ input { #rect1 { position: relative; width: 388px; - height: 531px; + height: 631px; background: #f5f5f5; box-shadow: 0px 4px 40px rgba(0, 0, 0, 0.25); diff --git a/templates/index.html b/templates/index.html index 03993e3..f175766 100644 --- a/templates/index.html +++ b/templates/index.html @@ -8,7 +8,7 @@ {{ dropzone.style('position: absolute; top: -0.5px; width: 388px; - height: 532px; + 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); @@ -43,6 +43,12 @@ + + + + + + diff --git a/uploads/.gitkeep b/uploads/.gitkeep deleted file mode 100644 index e69de29..0000000