Skip to content
Snippets Groups Projects
Commit d91527c2 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '81-email-to-technician' into 'master'

Resolve "Email to technician"

Closes #81

See merge request !47
parents 14f33c68 0188d785
No related branches found
No related tags found
1 merge request!47Resolve "Email to technician"
Pipeline #
......@@ -175,6 +175,16 @@ server {
- extract static files and make them available via nginx: `./manage.py collectstatic`
- you start application by starting gunicorn and nginx: `service gunicorn start`, `service nginx start`
## Cron jobs (weekly emails)
If weekly emails are required then cron must be edited to fire periodically django function that send emails.
```
> crontab -e
SHELL=/bin/bash
*/30 * * * * source /var/www/scheduling-system/env/bin/activate && python /var/www/scheduling-system/smash/manage.py runcrons >> /var/log/django-cronjob.log 2>&1
```
## Operations
### Public holidays
......@@ -191,4 +201,4 @@ example:
```
./manage.py holidays 2017 2018 2019
```
\ No newline at end of file
```
......@@ -5,4 +5,5 @@ psycopg2==2.6.2
pytz==2016.10
lxml==3.7.3
python-docx==0.8.6
django-cleanup==0.4.2
\ No newline at end of file
django-cleanup==0.4.2
django_cron==0.5.0
......@@ -22,13 +22,6 @@ ALLOWED_HOSTS = ['prc.parkinson.lu', 'localhost']
DEBUG = True
EMAIL_HOST = 'smtp.uni.lu'
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_PORT = 25
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
DEFAULT_FROM_EMAIL = 'prc-scheduling-admin@uni.lu'
# Application definition
INSTALLED_APPS = [
......@@ -39,6 +32,7 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'django_cleanup',
'django_cron',
'debug_toolbar',
'web'
]
......@@ -72,6 +66,10 @@ TEMPLATES = [
},
]
CRON_CLASSES = [
'web.views.kit.KitRequestEmailSendJob'
]
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
......
......@@ -52,7 +52,13 @@ def update_configuration_item(request):
})
item = items[0]
item.value = value
item.save()
return JsonResponse({
"status": "ok",
})
if ConfigurationItem.is_valid(item):
item.save()
return JsonResponse({
"status": "ok",
})
else:
return JsonResponse({
"status": "error",
"message": ConfigurationItem.validation_error(item)
})
# coding=utf-8
import re
from django.db import models
from web.models.constants import CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, \
NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE, KIT_EMAIL_HOUR_CONFIGURATION_TYPE, \
KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE
class ConfigurationItem(models.Model):
type = models.CharField(max_length=50,
......@@ -21,3 +26,24 @@ class ConfigurationItem(models.Model):
def __unicode__(self):
return "%s %s" % (self.name, self.value)
@staticmethod
def is_valid(item):
message = ConfigurationItem.validation_error(item)
return message == ""
@staticmethod
def validation_error(item):
pattern = None
if item.type == CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE \
or item.type == NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE:
pattern = "^#[0-9a-fA-F]+$"
if item.type == KIT_EMAIL_HOUR_CONFIGURATION_TYPE:
pattern = "^[0-9]{2}:[0-9]{2}$"
if item.type == KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE:
pattern = "^(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)$"
if pattern is not None:
if not re.compile(pattern).match(item.value):
return "Invalid value of param: " + item.name + ". It should match regex pattern: " + pattern
return ""
......@@ -53,3 +53,11 @@ MAIL_TEMPLATE_CONTEXT_CHOICES = (
(MAIL_TEMPLATE_CONTEXT_VISIT, 'Visit'),
)
LOCALE_CHOICES = [(value, value) for value in sorted(locale.windows_locale.values())]
MONDAY_AS_DAY_OF_WEEK = 1
TUESDAY_AS_DAY_OF_WEEK = 2
WEDNESDAY_AS_DAY_OF_WEEK = 3
THURSDAY_AS_DAY_OF_WEEK = 4
FRIDAY_AS_DAY_OF_WEEK = 5
SATURDAY_AS_DAY_OF_WEEK = 6
SUNDAY_AS_DAY_OF_WEEK = 7
......@@ -88,10 +88,10 @@
<div class="col-sm-12">
{% if end_date == None %}
<a href="{% url 'web.views.kit_requests_send_mail' start_date|date:"Y-m-d" %}"
class="btn btn-block btn-default">Show email content</a>
class="btn btn-block btn-default">Send email</a>
{% else %}
<a href="{% url 'web.views.kit_requests_send_mail' start_date|date:"Y-m-d" end_date|date:"Y-m-d" %}"
class="btn btn-block btn-default">Show email content</a>
class="btn btn-block btn-default">Send email</a>
{% endif %}
</div>
</div><!-- /.box-footer -->
......
......@@ -6,6 +6,9 @@ from django.urls import reverse
from web.models import ConfigurationItem
from web.tests.functions import create_configuration_item
from . import LoggedInTestCase
from web.models.constants import CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, \
NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE, KIT_EMAIL_HOUR_CONFIGURATION_TYPE, \
KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE
class TestConfigurationItemApi(LoggedInTestCase):
......@@ -31,3 +34,21 @@ class TestConfigurationItemApi(LoggedInTestCase):
self.assertEqual(response.status_code, 200)
updated_item = ConfigurationItem.objects.get(id=item.id)
self.assertEqual(new_val, updated_item.value)
def test_configuration_modify_CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE_invalid_value(self):
item = ConfigurationItem.objects.get(type=CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE)
invalid_val = 'invalid color'
response = self.client.get(reverse('web.api.update_configuration_item'), {'id': item.id, 'value': invalid_val})
self.assertEqual(response.status_code, 200)
updated_item = ConfigurationItem.objects.get(type=CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE)
self.assertNotEqual(invalid_val, updated_item.value)
def test_configuration_modify_CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE_valid_value(self):
item = ConfigurationItem.objects.get(type=CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE)
invalid_val = '#FFFFFF'
response = self.client.get(reverse('web.api.update_configuration_item'), {'id': item.id, 'value': invalid_val})
self.assertEqual(response.status_code, 200)
updated_item = ConfigurationItem.objects.get(type=CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE)
self.assertEqual(invalid_val, updated_item.value)
......@@ -26,7 +26,7 @@ class TestDocxProcessor(TestCase):
"##ADDRESS2##": "61-234, Poznan",
"##COUNTRY##": "POLAND",
"##CONTENT##": "1",
"##DATE##": datetime.datetime.now().date().strftime("%A %-d %B %Y"),
"##DATE##": datetime.datetime.now().date().strftime("%A %d %B %Y"),
}
process_file(template_path, output_path, changes)
self.assertTrue(os.path.isfile(output_path))
......
import datetime
from django.core import mail
from django.urls import reverse
from functions import create_appointment_type, create_appointment
from functions import create_appointment_type, create_appointment, create_visit
from web.models import Item, Appointment, AppointmentTypeLink
from web.views.kit import get_kit_requests
from web.views.notifications import get_today_midnight_date
from . import LoggedInTestCase
......@@ -25,7 +27,6 @@ class ViewFunctionsTests(LoggedInTestCase):
appointment.save()
AppointmentTypeLink.objects.create(appointment=appointment, appointment_type=appointment_type)
response = self.client.get(reverse('web.views.kit_requests'))
self.assertEqual(response.status_code, 200)
......@@ -61,8 +62,57 @@ class ViewFunctionsTests(LoggedInTestCase):
appointment.save()
AppointmentTypeLink.objects.create(appointment=appointment, appointment_type=appointment_type)
response = self.client.get(reverse('web.views.kit_requests'))
self.assertEqual(response.status_code, 200)
self.assertTrue(item_name in response.content)
def test_kit_requests_order(self):
item_name = "Test item to be ordered"
item = Item.objects.create(disposable=True, name=item_name)
appointment_type = create_appointment_type()
appointment_type.required_equipment.add(item)
appointment_type.save()
visit = create_visit();
appointment1 = create_appointment(visit)
appointment1.datetime_when = get_today_midnight_date() + datetime.timedelta(days=3)
appointment1.save()
AppointmentTypeLink.objects.create(appointment=appointment1, appointment_type=appointment_type)
appointment2 = create_appointment(visit)
appointment2.datetime_when = get_today_midnight_date() + datetime.timedelta(days=4)
appointment2.save()
AppointmentTypeLink.objects.create(appointment=appointment2, appointment_type=appointment_type)
appointment3 = create_appointment(visit)
appointment3.datetime_when = get_today_midnight_date() + datetime.timedelta(days=2)
appointment3.save()
AppointmentTypeLink.objects.create(appointment=appointment3, appointment_type=appointment_type)
result = get_kit_requests(self.user)
self.assertEqual(appointment3, result['appointments'][0])
self.assertEqual(appointment1, result['appointments'][1])
self.assertEqual(appointment2, result['appointments'][2])
def test_kit_requests_send_email(self):
item_name = "Test item to be ordered"
item = Item.objects.create(disposable=True, name=item_name)
appointment_type = create_appointment_type()
appointment_type.required_equipment.add(item)
appointment_type.save()
appointment = create_appointment()
appointment.datetime_when = get_today_midnight_date() + datetime.timedelta(days=2)
appointment.save()
AppointmentTypeLink.objects.create(appointment=appointment, appointment_type=appointment_type)
response = self.client.get(reverse('web.views.kit_requests_send_mail',
kwargs={'start_date': str(get_today_midnight_date().strftime("%Y-%m-%d"))}))
self.assertEqual(response.status_code, 200)
self.assertTrue(item_name in response.content)
self.assertEqual(1, len(mail.outbox))
......@@ -4,15 +4,8 @@ from django.shortcuts import redirect, get_object_or_404
from . import wrap_response
from ..forms import WorkerAddForm, WorkerEditForm, WorkerDetailForm
from ..models import Worker, Availability
MONDAY_AS_DAY_OF_WEEK = 1
TUESDAY_AS_DAY_OF_WEEK = 2
WEDNESDAY_AS_DAY_OF_WEEK = 3
THURSDAY_AS_DAY_OF_WEEK = 4
FRIDAY_AS_DAY_OF_WEEK = 5
SATURDAY_AS_DAY_OF_WEEK = 6
SUNDAY_AS_DAY_OF_WEEK = 7
from ..models.constants import MONDAY_AS_DAY_OF_WEEK, TUESDAY_AS_DAY_OF_WEEK, WEDNESDAY_AS_DAY_OF_WEEK, \
THURSDAY_AS_DAY_OF_WEEK, FRIDAY_AS_DAY_OF_WEEK, SATURDAY_AS_DAY_OF_WEEK, SUNDAY_AS_DAY_OF_WEEK
def doctors(request):
doctors_list = Worker.objects.order_by('-last_name')
......
# coding=utf-8
import datetime
import locale
import platform
import sys
import time
import traceback
import pytz
from django.contrib import messages
from django.utils.dateparse import parse_datetime
from django_cron import CronJobBase, Schedule
from django_cron.models import CronJobLog
from notifications import get_filter_locations, get_today_midnight_date
from web.models import ConfigurationItem, Language, Worker
from web.models.constants import KIT_EMAIL_HOUR_CONFIGURATION_TYPE, \
KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE
from web.models.constants import KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE
from . import wrap_response
from ..forms import KitRequestForm
from ..models import AppointmentType, Appointment
from ..smash_email import EmailSender
def get_kit_requests(user, start_date=None, end_date=None):
......@@ -16,8 +31,12 @@ def get_kit_requests(user, start_date=None, end_date=None):
else:
if isinstance(start_date, str):
start_date = parse_datetime(start_date)
if isinstance(start_date, unicode):
start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d')
if (end_date is not None) and (isinstance(end_date, str)):
end_date = parse_datetime(end_date)
if (end_date is not None) and (isinstance(end_date, unicode)):
end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d')
appointment_types = AppointmentType.objects.filter(required_equipment__disposable=True)
......@@ -26,7 +45,8 @@ def get_kit_requests(user, start_date=None, end_date=None):
datetime_when__gt=start_date,
location__in=get_filter_locations(user),
status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
)
).order_by('datetime_when')
if end_date is not None:
appointments = appointments.filter(datetime_when__lt=end_date)
......@@ -58,6 +78,98 @@ def kit_requests(request):
return wrap_response(request, 'equipment_and_rooms/kit_requests.html', get_kit_requests_data(request))
def send_mail(data):
end_date_str = " end of time"
if data["end_date"] is not None:
end_date_str = data["end_date"].strftime('%Y-%m-%d')
title = "Kits required between " + data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
cell_style = "padding: 8px; line-height: 1.42857143; vertical-align: top; " \
"font-size: 14px; font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;"
email_body = "<h1>" + title + "</h1>"
email_body += '<table style="border: 1px solid #f4f4f4;border-spacing: 0;border-collapse: collapse;">' \
'<thead><tr><th>Date</th><th>Kits</th><th>Location</th><th>Person responsible</th></tr></thead>'
email_body += "<tbody>"
even = True
for appointment in data["appointments"]:
row_style = ""
even = not even
if even:
row_style = ' background-color: #f9f9f9;'
email_body += "<tr style='" + row_style + "'>"
email_body += "<td style='" + cell_style + "'>" + appointment.datetime_when.strftime('%Y-%m-%d %H:%M') + "</td>"
email_body += "<td style='" + cell_style + "'>"
for type in appointment.appointment_types.all():
for item in type.required_equipment.all():
if item.disposable:
email_body += item.name + ", "
email_body += "</td>"
email_body += "<td style='" + cell_style + "'>" + str(appointment.location) + "</td>"
email_body += "<td style='" + cell_style + "'>" + str(appointment.worker_assigned) + "</td>"
email_body += "</tr>"
email_body += "</tbody></table>"
recipients = ConfigurationItem.objects.get(type=KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE).value
cc_recipients = []
EmailSender().send_email(title, email_body, recipients, cc_recipients)
def kit_requests_send_mail(request, start_date, end_date=None):
return wrap_response(request, 'equipment_and_rooms/kit_requests_send_mail.html',
get_kit_requests_data(request, start_date, end_date))
data = get_kit_requests_data(request, start_date, end_date)
try:
send_mail(data)
messages.add_message(request, messages.SUCCESS, 'Mail sent')
except:
messages.add_message(request, messages.ERROR, 'There was problem with sending email')
return wrap_response(request, 'equipment_and_rooms/kit_requests.html', get_kit_requests_data(request))
class KitRequestEmailSendJob(CronJobBase):
RUN_EVERY_MINS = 1
schedule = Schedule(run_every_mins=RUN_EVERY_MINS)
code = 'web.kit_request_weekly_email' # a unique code
def do(self):
now = datetime.datetime.utcnow()
hour = int(ConfigurationItem.objects.get(
type=KIT_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(":")[0])
minute = int(ConfigurationItem.objects.get(
type=KIT_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(":")[1])
# check if we sent email this day already
date = now.replace(hour=hour, minute=minute)
# TODO it's a hack assuming that we are in CEST
date = pytz.utc.localize(date - datetime.timedelta(minutes=122))
jobs = CronJobLog.objects.filter(code=KitRequestEmailSendJob.code, message="mail sent", start_time__gte=date)
if jobs.count() == 0:
print date
print datetime.datetime.now()
if pytz.utc.localize(datetime.datetime.utcnow()) > date:
if self.match_day_of_week():
data = get_kit_requests(Worker.objects.create());
send_mail(data);
return "mail sent"
else:
return "day of week doesn't match"
else:
return "too early"
else:
return "mail already sent"
def match_day_of_week(self):
user_day_of_week = ConfigurationItem.objects.get(type=KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE).value
language = Language.objects.get(name="English");
locale_name = language.locale
if platform.system() == 'Windows':
locale_name = language.windows_locale_name
try:
locale.setlocale(locale.LC_TIME, locale_name)
except:
print locale_name
traceback.print_exc(file=sys.stdout)
user_day_of_week_int = int(time.strptime(user_day_of_week, '%A').tm_wday) + 1
current_day_of_week_int = int(datetime.datetime.now().strftime("%w"));
return user_day_of_week_int == current_day_of_week_int
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment