diff --git a/app.py b/app.py
index e60ff93..1dd90a8 100644
--- a/app.py
+++ b/app.py
@@ -1,57 +1,14 @@
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
-import imaplib
-from smtplib import SMTP_SSL
+import portal.base
-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
+# 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.
-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 #
@@ -67,10 +24,22 @@ 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'])
+MAIL_SUBJECT = config['MAIL_SUBJECT']
+MAIL_BODY = config['MAIL_BODY']
+
+if 'UPLOADS_DIR' in config:
+ UPLOADS_DIR = Path(config['UPLOADS_DIR'])
+else:
+ UPLOADS_DIR = Path(__file__).resolve().parent / 'uploads'
+if not UPLOADS_DIR.exists:
+ UPLOADS_DIR.mkdir(parents=True)
+
+if 'DATA_DIR' in config:
+ DATA_DIR = Path(config['DATA_DIR'])
else:
- BASE_DIR = Path(__file__).resolve().parent
+ DATA_DIR = Path(__file__).resolve().parent / 'data'
+if not DATA_DIR.exists:
+ DATA_DIR.mkdir(parents=True)
# Override configs with environment variables, if set
if 'PORTALDS4DS1_MAIL_HOST' in os.environ:
@@ -87,243 +56,86 @@ if 'PORTALDS4DS1_MAX_UPLOAD_SIZE' in os.environ:
MAX_UPLOAD_SIZE = int(os.environ['PORTALDS4DS1_MAX_UPLOAD_SIZE'])
if 'PORTALDS4DS1_CONTRACT_CLIENT_CONTACT' in os.environ:
CONTRACT_CLIENT_CONTACT = os.environ['PORTALDS4DS1_CONTRACT_CLIENT_CONTACT']
+if 'PORTALDS4DS1_UPLOADS_DIR' in os.environ:
+ 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:
+ MAIL_SUBJECT = os.environ['PORTALDS4DS1_MAIL_SUBJECT']
+if 'PORTALDS4DS1_MAIL_BODY' in os.environ:
+ MAIL_BODY = os.environ['PORTALDS4DS1_MAIL_BODY']
+
+VALID_CORPUS_NAMES = ['prevodi', 'gigafida', 'solar']
-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,
+ UPLOADED_PATH = UPLOADS_DIR,
MAX_CONTENT_LENGTH = MAX_UPLOAD_SIZE,
TEMPLATES_AUTO_RELOAD = True
)
dropzone = Dropzone(app)
-contract_creator = ContractCreator()
-
@app.route('/')
def index():
return render_template('index.html')
-@app.route('/upload', methods=['POST'])
-def handle_upload():
+@app.route('/
- Ime, naslov ali oznaka dela
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {{files_table_str}}
-
diff --git a/portal/base.py b/portal/base.py new file mode 100644 index 0000000..3af7875 --- /dev/null +++ b/portal/base.py @@ -0,0 +1,250 @@ +import re +import hashlib +import time +import ssl +from pathlib import Path + +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 + + +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: + + 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) + + +contract_creator = ContractCreator() + + +def get_upload_metadata(corpus_name, request): + upload_metadata = dict() + + file_hashes = create_file_hashes(request.files) + file_names = file_hashes.keys() + 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): + 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 get_subdir(uploads_path, dir_name): + subdir = uploads_path / dir_name + if not subdir.exists(): + subdir.mkdir(parents=True) + return subdir + + +def create_upload_id(corpus_name, form_data, upload_timestamp, file_hashes): + 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((corpus_name+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): + 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 + + +def create_file_hashes(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 store_metadata(uploads_path, upload_metadata): + base = get_subdir(uploads_path, 'meta') + + 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' + + 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('
') + 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() + diff --git a/static/image/logo.svg b/static/image/logo.svg new file mode 100644 index 0000000..ac104c9 --- /dev/null +++ b/static/image/logo.svg @@ -0,0 +1,3 @@ + diff --git a/static/style.css b/static/style.css index 2adeb80..f542623 100644 --- a/static/style.css +++ b/static/style.css @@ -37,6 +37,18 @@ html { margin-left: -388px; } +#logo-container { + position: absolute; + top: -9%; + left: 35%; + background: #F5F5F5; + border-radius: 50%; + padding-top: 5%; + padding-bottom: 5%; + padding-left: 7%; + padding-right: 7%; +} + #izjava { float: left; width: 32px; @@ -51,10 +63,22 @@ html { font-size: 36px; line-height: 42px; margin-block-start: 0.4em; + z-index: 5; color: #006cb7; } +#subtitle { + font-family: Roboto; + font-style: bold; + font-weight: 400; + font-size: 15px; + line-height: 20px; + margin-block-start: 0.4em; + border-bottom: 3px solid #006cb7; + color: #006cb7; +} + label { font-family: Roboto; font-style: normal; @@ -160,8 +184,8 @@ input { position: relative; display: inline-block; vertical-align: top; - margin-top: 16px; - margin-bottom: 16px; + margin-top: 26px; + margin-bottom: 26px; min-height: 100px; max-height: 500px; top: -530px; @@ -329,6 +353,7 @@ input { #popup-terms { position: absolute; + z-index: 9999; top: 100px; bottom: 100px; left: 10%; diff --git a/templates/basic.html b/templates/basic.html new file mode 100644 index 0000000..ea60ca3 --- /dev/null +++ b/templates/basic.html @@ -0,0 +1,300 @@ + + +
+ +
+ + + {{ 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;') }} + + +
+