diff --git a/back/.env b/back/.env new file mode 100644 index 0000000..b47f7a4 --- /dev/null +++ b/back/.env @@ -0,0 +1,4 @@ +EMAIL_HOST_USER=accountemail@yourmail.com +EMAIL_HOST_PASSWORD=accountpasswordhere +SUBMIT_REPORT_DESTINATION_EMAIL=to-address@yourmail.com +SUBMIT_REPORT_FROM_EMAIL=from-address@yourmail.com diff --git a/back/Pipfile b/back/Pipfile index c985465..5ad300d 100644 --- a/back/Pipfile +++ b/back/Pipfile @@ -12,6 +12,7 @@ djangorestframework = "==3.8.2" django-rest-auth = "==0.9.3" django-allauth = "==0.37.1" gunicorn = "==19.6.0" +python-decouple = "==3.1" [requires] python_version = "3.5" diff --git a/back/Pipfile.lock b/back/Pipfile.lock index a6ed173..eb9c181 100644 --- a/back/Pipfile.lock +++ b/back/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b1fc6b06ec8daa4efd9573865bc6c1732ae9354309e036bfe3ce0ab76b1a3bcd" + "sha256": "b8697c2d821e12f9f3e7bbad8a765782fc8a89df3f4260bc5f2b6f2e3d454510" }, "pipfile-spec": 6, "requires": { @@ -39,11 +39,11 @@ }, "django": { "hashes": [ - "sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8", - "sha256:d6393918da830530a9516bbbcbf7f1214c3d733738779f06b0f649f49cc698c3" + "sha256:275bec66fd2588dd517ada59b8bfb23d4a9abc5a362349139ddda3c7ff6f5ade", + "sha256:939652e9d34d7d53d74d5d8ef82a19e5f8bb2de75618f7e5360691b6e9667963" ], "index": "pypi", - "version": "==2.1.5" + "version": "==2.1.7" }, "django-allauth": { "hashes": [ @@ -97,6 +97,13 @@ ], "version": "==3.0.1" }, + "python-decouple": { + "hashes": [ + "sha256:1317df14b43efee4337a4aa02914bf004f010cd56d6c4bd894e6474ec8c4fe2d" + ], + "index": "pypi", + "version": "==3.1" + }, "python3-openid": { "hashes": [ "sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa", diff --git a/back/backend/policy.py b/back/backend/policy.py index 6366018..41f8b73 100644 --- a/back/backend/policy.py +++ b/back/backend/policy.py @@ -114,8 +114,8 @@ def fare_limit_rule(report, fields): planning_section.add_rule(title="Fare limits", rule=fare_limit_rule) def lowest_fare_rule(report, fields): - diff = field['lowest_fare_duration'] - field['preferred_flight_duration'] - lowest_Fare = field['lowest_fare'] + diff = fields['lowest_fare_duration'] - fields['preferred_flight_duration'] + lowest_fare = fields['lowest_fare'] maximum = 0 if diff <= 0: maximum = lowest_fare + 100 @@ -127,14 +127,14 @@ def lowest_fare_rule(report, fields): maximum = lowest_fare + 350 else: maximum = lowest_fare + 600 - if field['preferred_fare'] > maximum: + if fields['preferred_fare'] > maximum: return "For the lowest fare you have provided, your maximum in-policy fare amount is {} USD.".format(maximum) return None planning_section.add_rule(title="Lowest fare check", rule=lowest_fare_rule) def departure_date_limit_rule(report, fields): - days_to_departure = date(field['departure_date']) - date(field['screenshot_date']) + days_to_departure = date(fields['departure_date']) - date(fields['screenshot_date']) if days_to_departure < 14: return "Flights must be booked at least 14 days in advance." if days_to_departure > 365: @@ -174,7 +174,7 @@ flight_section.add_rule(title="Fare limits", rule=actual_fare_limit_rule) def request_date_rule(report, fields): now = date.today() - last_travel = date(field['return_date']) + last_travel = date(fields['return_date']) if now - last_travel > 90: return "Reimbursement requests must be made within 90 days of the last day of travel." return None diff --git a/back/backend/templates/backend/email.html b/back/backend/templates/backend/email.html new file mode 100644 index 0000000..b985302 --- /dev/null +++ b/back/backend/templates/backend/email.html @@ -0,0 +1,26 @@ + + + + + +

Title: {{ title }}

+ {% for section in sections %} + {% if section.completed %} +

{{section.title}}

+ + {% for field in section.fields %} + + + + + {% endfor %} + {% for rule in section.rule_violations %} + + + + {% endfor %} +
{{field.label}}{{field.value|default:""}}
{{rule.label}}
{{rule.rule_break_text}}
+ {% endif %} + {% endfor %} + + diff --git a/back/backend/templates/backend/email.txt b/back/backend/templates/backend/email.txt new file mode 100644 index 0000000..43f9df7 --- /dev/null +++ b/back/backend/templates/backend/email.txt @@ -0,0 +1,15 @@ +***Deprecated: + Replaced by converting html template to plaintext + using the html2text library +*** + +Title: {{title}} +{% for section in sections %} + {{section.title}} + {% for field in section.fields %} + {{field.label}}: {{field.value|default:"empty"}} + {% endfor %} + {% for rule in section.rule_violations %} + [RULE] {{rule.label}}: {{rule.rule_break_text}} + {% endfor %} +{% endfor %} diff --git a/back/backend/views.py b/back/backend/views.py index 376bee7..1d55aec 100644 --- a/back/backend/views.py +++ b/back/backend/views.py @@ -3,6 +3,11 @@ from django.http import JsonResponse from .models import * from .policy import pol import os +from rest_framework.exceptions import ParseError +from rest_framework.parsers import FileUploadParser, MultiPartParser +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from decouple import config def get_report(report_pk): """ @@ -199,8 +204,8 @@ def report_detail(request, report_pk): return JsonResponse({"message": "Cannot submit a report that has already been submitted."}, status=409) rep.submitted = True; rep.save() - # Send email here - ################# + # Send email + send_report_to_admin(request, report_pk) return JsonResponse({"message": "Report submitted."}) # DELETE: Deletes a report from the user's account. @@ -365,3 +370,46 @@ def section_complete(section_pk): if i.completed: return True return False + +def send_report_to_admin(request, report_pk): + """ + Sends an email message to admin with html/txt version of report, + along with file attachments. Cc sent to user. + + request -- Request object with user info inside + report_pk -- ID of the report to submit + """ + params = get_report(report_pk) + to_email = config('SUBMIT_REPORT_DESTINATION_EMAIL') + from_email = config('SUBMIT_REPORT_FROM_EMAIL') + cc = request.user.email + msg_html = render_to_string('backend/email.html', params) + msg_plain = render_to_string('backend/email.txt', params) + message = EmailMultiAlternatives( + "Reimbursinator - {}".format(params['title']), + msg_plain, + from_email, + [to_email], + cc=[request.user.email], + ) + message.attach_alternative(msg_html, "text/html") + for f in get_files(report_pk): + message.attach_file(f) + message.send() + +def get_files(report_pk): + """ + collects all the files in a particular report and returns them + in an array. + + report_pk -- ID of the report to collect files for + """ + sections = Section.objects.filter(report_id=report_pk) + files = [] + for section in sections: + fields = Field.objects.filter(section_id=section.id, completed=True) + for field in fields: + if field.field_type == "file": + print("appending {}".format(field.data_file.name)) + files.append(field.data_file.name) + return files diff --git a/back/db.sqlite3 b/back/db.sqlite3 index b0936b9..e1d4159 100644 Binary files a/back/db.sqlite3 and b/back/db.sqlite3 differ diff --git a/back/reimbursinator/settings.py b/back/reimbursinator/settings.py index c306f32..5b074f3 100644 --- a/back/reimbursinator/settings.py +++ b/back/reimbursinator/settings.py @@ -11,12 +11,11 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ """ import os -#from reimbursinator.custom_auth import BearerAuthentication +from decouple import config # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ @@ -28,7 +27,6 @@ DEBUG = True ALLOWED_HOSTS = ['localhost','192.168.99.100', '127.0.0.1'] - # Application definition INSTALLED_APPS = [ @@ -157,7 +155,12 @@ STATIC_URL = 'https://192.168.99.100:8445/' # Email Config -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_USE_TLS = True +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST_USER = config('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD') +EMAIL_PORT = 587 SITE_ID = 1