Skip to content
Snippets Groups Projects
Commit be760fa3 authored by Valentin Groues's avatar Valentin Groues :eyes:
Browse files

mail generation - #20

parent 6cbf0e47
No related branches found
No related tags found
1 merge request!45Resolve "Automatic post mail creation"
Pipeline #
Showing
with 465 additions and 55 deletions
...@@ -21,3 +21,4 @@ out ...@@ -21,3 +21,4 @@ out
smash/htmlcov/ smash/htmlcov/
run-coverage.bat run-coverage.bat
media
...@@ -7,7 +7,7 @@ variables: ...@@ -7,7 +7,7 @@ variables:
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: password
before_script: before_script:
- apt-get update && apt-get install -y libsasl2-dev libssl-dev - apt-get update && apt-get install -y libsasl2-dev libssl-dev locales locales-all
- pip install -r requirements.txt --default-timeout=180 - pip install -r requirements.txt --default-timeout=180
- pip install -r requirements-dev.txt --default-timeout=180 - pip install -r requirements-dev.txt --default-timeout=180
......
...@@ -4,4 +4,5 @@ Pillow==3.4.2 ...@@ -4,4 +4,5 @@ Pillow==3.4.2
psycopg2==2.6.2 psycopg2==2.6.2
pytz==2016.10 pytz==2016.10
lxml==3.7.3 lxml==3.7.3
python-docx==0.8.6 python-docx==0.8.6
\ No newline at end of file django-cleanup==0.4.2
\ No newline at end of file
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'Paste long random string here' # Insert long random string SECRET_KEY = 'Paste long random string here' # Insert long random string
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
...@@ -21,11 +21,11 @@ DEFAULT_FROM_EMAIL = 'prc-scheduling-admin@uni.lu' ...@@ -21,11 +21,11 @@ DEFAULT_FROM_EMAIL = 'prc-scheduling-admin@uni.lu'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', 'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'smashdb', # Insert your database's name 'NAME': 'smashdb', # Insert your database's name
'USER': 'postgresmashuser', # Insert your database's user 'USER': 'postgresmashuser', # Insert your database's user
'PASSWORD': 'thePOSTGRESpassword', # Insert your user's password 'PASSWORD': 'thePOSTGRESpassword', # Insert your user's password
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': '' # '' === default one # Empty string is OK 'PORT': '' # '' === default one # Empty string is OK
# If to use sqlite # If to use sqlite
# 'ENGINE': 'django.db.backends.sqlite3', # 'ENGINE': 'django.db.backends.sqlite3',
...@@ -33,5 +33,7 @@ DATABASES = { ...@@ -33,5 +33,7 @@ DATABASES = {
} }
} }
STATIC_ROOT = '/tmp/static' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static STATIC_ROOT = '/tmp/static' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static
MEDIA_ROOT = '/tmp/media' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media MEDIA_ROOT = '/tmp/media' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
...@@ -38,6 +38,7 @@ INSTALLED_APPS = [ ...@@ -38,6 +38,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django_cleanup',
'debug_toolbar', 'debug_toolbar',
'web' 'web'
] ]
......
import copy
from docx import Document from docx import Document
def dfs_update(node, replace_dict):
# If this is a non-empty text node
if node.text != None and len(node.text) > 0:
# Apply transformations to node's text
for what_to_replace in replace_dict:
node.text = node.text.replace(what_to_replace, replace_dict[what_to_replace])
# Update child elements
for child in node:
dfs_update(child, replace_dict)
def process_file(path_to_docx, path_to_new_docx, changes_to_apply): def process_file(path_to_docx, path_to_new_docx, changes_to_apply):
""" """
Tries to open the docx document using given path to file. Tries to open the docx document using given path to file.
Then, applies the transformations- replaces template tags Then, applies the transformations- replaces template tags
in format of ##name## to values specified in the second in format of ##name## to values specified in the second
argument. argument.
""" """
generated = Document() doc = Document(path_to_docx)
doc = Document(path_to_docx) for paragraph in doc.paragraphs:
for c in changes_to_apply: for placeholder, replacement in changes_to_apply.items():
for element in doc.part.element[0]: if placeholder in paragraph.text:
w = copy.deepcopy(element) paragraph.text = paragraph.text.replace(placeholder, replacement)
dfs_update(w, c)
generated._body._element.append(w)
generated.save(path_to_new_docx) doc.save(path_to_new_docx)
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2017-04-07 07:10
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
def update_languages(apps, schema_editor):
Language = apps.get_model("web", "Language")
updates_to_perform = [
("French", 10, "fr_FR"),
("Dutch", 100, "nl_NL"),
("Swedish", 200, "se_SE"),
("Spanish", 110, "es_ES"),
("Slovak", 200, "es_ES"),
("Romanian", 200, "ro_RO"),
("Polish", 200, "pl_PL"),
("Italian", 100, "it_IT"),
("Hungarian", 200, "hu_HU"),
("Greek", 200, "el_GR"),
("Finnish", 200, "fi_FI"),
("Danish", 200, "da_DK"),
("Arabic", 200, "ar_DZ"),
("Portuguese", 80, "pt_PT"),
("Luxembourgish", 20, "lb_LU"),
("English", 50, "en_GB"),
("German", 30, "de_DE"),
]
for update_to_perform in updates_to_perform:
name, order, locale = update_to_perform
language = Language.objects.filter(name=name).first()
if language is not None:
language.order = order
language.locale = locale
language.save()
class Migration(migrations.Migration):
dependencies = [
('web', '0033_auto_20170406_1146'),
]
operations = [
migrations.CreateModel(
name='MailTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('context', models.CharField(choices=[(b'A', b'Appointment'), (b'S', b'Subject'), (b'V', b'Visit')],
max_length=1)),
('template_file', models.FileField(upload_to=b'templates/')),
],
),
migrations.AlterModelOptions(
name='language',
options={'ordering': ['order']},
),
migrations.AddField(
model_name='language',
name='locale',
field=models.CharField(
choices=[(b'af_ZA', b'af_ZA'), (b'am_ET', b'am_ET'), (b'ar_AE', b'ar_AE'), (b'ar_BH', b'ar_BH'),
(b'ar_DZ', b'ar_DZ'), (b'ar_EG', b'ar_EG'), (b'ar_IQ', b'ar_IQ'), (b'ar_JO', b'ar_JO'),
(b'ar_KW', b'ar_KW'), (b'ar_LB', b'ar_LB'), (b'ar_LY', b'ar_LY'), (b'ar_MA', b'ar_MA'),
(b'ar_OM', b'ar_OM'), (b'ar_QA', b'ar_QA'), (b'ar_SA', b'ar_SA'), (b'ar_SY', b'ar_SY'),
(b'ar_TN', b'ar_TN'), (b'ar_YE', b'ar_YE'), (b'arn_CL', b'arn_CL'), (b'as_IN', b'as_IN'),
(b'az_AZ', b'az_AZ'), (b'az_AZ', b'az_AZ'), (b'ba_RU', b'ba_RU'), (b'be_BY', b'be_BY'),
(b'bg_BG', b'bg_BG'), (b'bn_IN', b'bn_IN'), (b'bo_BT', b'bo_BT'), (b'bo_CN', b'bo_CN'),
(b'br_FR', b'br_FR'), (b'bs_BA', b'bs_BA'), (b'bs_BA', b'bs_BA'), (b'ca_ES', b'ca_ES'),
(b'co_FR', b'co_FR'), (b'cs_CZ', b'cs_CZ'), (b'cy_GB', b'cy_GB'), (b'da_DK', b'da_DK'),
(b'de_AT', b'de_AT'), (b'de_CH', b'de_CH'), (b'de_DE', b'de_DE'), (b'de_LI', b'de_LI'),
(b'de_LU', b'de_LU'), (b'div_MV', b'div_MV'), (b'dsb_DE', b'dsb_DE'), (b'el_GR', b'el_GR'),
(b'en_AU', b'en_AU'), (b'en_BZ', b'en_BZ'), (b'en_CA', b'en_CA'), (b'en_CB', b'en_CB'),
(b'en_GB', b'en_GB'), (b'en_IE', b'en_IE'), (b'en_IN', b'en_IN'), (b'en_IN', b'en_IN'),
(b'en_JA', b'en_JA'), (b'en_MY', b'en_MY'), (b'en_NZ', b'en_NZ'), (b'en_PH', b'en_PH'),
(b'en_TT', b'en_TT'), (b'en_US', b'en_US'), (b'en_ZA', b'en_ZA'), (b'en_ZW', b'en_ZW'),
(b'es_AR', b'es_AR'), (b'es_BO', b'es_BO'), (b'es_CL', b'es_CL'), (b'es_CO', b'es_CO'),
(b'es_CR', b'es_CR'), (b'es_DO', b'es_DO'), (b'es_EC', b'es_EC'), (b'es_ES', b'es_ES'),
(b'es_ES', b'es_ES'), (b'es_GT', b'es_GT'), (b'es_HN', b'es_HN'), (b'es_MX', b'es_MX'),
(b'es_NI', b'es_NI'), (b'es_PA', b'es_PA'), (b'es_PE', b'es_PE'), (b'es_PR', b'es_PR'),
(b'es_PY', b'es_PY'), (b'es_SV', b'es_SV'), (b'es_UR', b'es_UR'), (b'es_US', b'es_US'),
(b'es_VE', b'es_VE'), (b'et_EE', b'et_EE'), (b'eu_ES', b'eu_ES'), (b'fa_IR', b'fa_IR'),
(b'fi_FI', b'fi_FI'), (b'fil_PH', b'fil_PH'), (b'fo_FO', b'fo_FO'), (b'fr_BE', b'fr_BE'),
(b'fr_CA', b'fr_CA'), (b'fr_CH', b'fr_CH'), (b'fr_FR', b'fr_FR'), (b'fr_LU', b'fr_LU'),
(b'fr_MC', b'fr_MC'), (b'fy_NL', b'fy_NL'), (b'ga_IE', b'ga_IE'), (b'gbz_AF', b'gbz_AF'),
(b'gl_ES', b'gl_ES'), (b'gsw_FR', b'gsw_FR'), (b'gu_IN', b'gu_IN'), (b'ha_NG', b'ha_NG'),
(b'he_IL', b'he_IL'), (b'hi_IN', b'hi_IN'), (b'hr_BA', b'hr_BA'), (b'hr_HR', b'hr_HR'),
(b'hu_HU', b'hu_HU'), (b'hy_AM', b'hy_AM'), (b'id_ID', b'id_ID'), (b'ii_CN', b'ii_CN'),
(b'is_IS', b'is_IS'), (b'it_CH', b'it_CH'), (b'it_IT', b'it_IT'), (b'iu_CA', b'iu_CA'),
(b'iu_CA', b'iu_CA'), (b'ja_JP', b'ja_JP'), (b'ka_GE', b'ka_GE'), (b'kh_KH', b'kh_KH'),
(b'kk_KZ', b'kk_KZ'), (b'kl_GL', b'kl_GL'), (b'kn_IN', b'kn_IN'), (b'ko_KR', b'ko_KR'),
(b'kok_IN', b'kok_IN'), (b'ky_KG', b'ky_KG'), (b'lb_LU', b'lb_LU'), (b'lo_LA', b'lo_LA'),
(b'lt_LT', b'lt_LT'), (b'lv_LV', b'lv_LV'), (b'mi_NZ', b'mi_NZ'), (b'mk_MK', b'mk_MK'),
(b'ml_IN', b'ml_IN'), (b'mn_CN', b'mn_CN'), (b'mn_MN', b'mn_MN'), (b'moh_CA', b'moh_CA'),
(b'mr_IN', b'mr_IN'), (b'ms_BN', b'ms_BN'), (b'ms_MY', b'ms_MY'), (b'mt_MT', b'mt_MT'),
(b'nb_NO', b'nb_NO'), (b'ne_NP', b'ne_NP'), (b'nl_BE', b'nl_BE'), (b'nl_NL', b'nl_NL'),
(b'nn_NO', b'nn_NO'), (b'ns_ZA', b'ns_ZA'), (b'oc_FR', b'oc_FR'), (b'or_IN', b'or_IN'),
(b'pa_IN', b'pa_IN'), (b'pl_PL', b'pl_PL'), (b'ps_AF', b'ps_AF'), (b'pt_BR', b'pt_BR'),
(b'pt_PT', b'pt_PT'), (b'qut_GT', b'qut_GT'), (b'quz_BO', b'quz_BO'), (b'quz_EC', b'quz_EC'),
(b'quz_PE', b'quz_PE'), (b'rm_CH', b'rm_CH'), (b'ro_RO', b'ro_RO'), (b'ru_RU', b'ru_RU'),
(b'rw_RW', b'rw_RW'), (b'sa_IN', b'sa_IN'), (b'sah_RU', b'sah_RU'), (b'se_FI', b'se_FI'),
(b'se_NO', b'se_NO'), (b'se_SE', b'se_SE'), (b'si_LK', b'si_LK'), (b'sk_SK', b'sk_SK'),
(b'sl_SI', b'sl_SI'), (b'sma_NO', b'sma_NO'), (b'sma_SE', b'sma_SE'), (b'smj_NO', b'smj_NO'),
(b'smj_SE', b'smj_SE'), (b'smn_FI', b'smn_FI'), (b'sms_FI', b'sms_FI'), (b'sq_AL', b'sq_AL'),
(b'sr_BA', b'sr_BA'), (b'sr_BA', b'sr_BA'), (b'sr_SP', b'sr_SP'), (b'sr_SP', b'sr_SP'),
(b'sv_FI', b'sv_FI'), (b'sv_SE', b'sv_SE'), (b'sw_KE', b'sw_KE'), (b'syr_SY', b'syr_SY'),
(b'ta_IN', b'ta_IN'), (b'te_IN', b'te_IN'), (b'tg_TJ', b'tg_TJ'), (b'th_TH', b'th_TH'),
(b'tk_TM', b'tk_TM'), (b'tmz_DZ', b'tmz_DZ'), (b'tn_ZA', b'tn_ZA'), (b'tr_TR', b'tr_TR'),
(b'tt_RU', b'tt_RU'), (b'ug_CN', b'ug_CN'), (b'uk_UA', b'uk_UA'), (b'ur_IN', b'ur_IN'),
(b'ur_PK', b'ur_PK'), (b'uz_UZ', b'uz_UZ'), (b'uz_UZ', b'uz_UZ'), (b'vi_VN', b'vi_VN'),
(b'wen_DE', b'wen_DE'), (b'wo_SN', b'wo_SN'), (b'xh_ZA', b'xh_ZA'), (b'yo_NG', b'yo_NG'),
(b'zh_CHS', b'zh_CHS'), (b'zh_CHT', b'zh_CHT'), (b'zh_CN', b'zh_CN'), (b'zh_HK', b'zh_HK'),
(b'zh_MO', b'zh_MO'), (b'zh_SG', b'zh_SG'), (b'zh_TW', b'zh_TW'), (b'zu_ZA', b'zu_ZA')],
default=b'fr_FR', max_length=10),
),
migrations.AddField(
model_name='language',
name='order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='mailtemplate',
name='language',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Language'),
),
migrations.RunPython(update_languages, reverse_code=lambda x, y: None),
]
...@@ -19,6 +19,7 @@ from item import Item ...@@ -19,6 +19,7 @@ from item import Item
from language import Language from language import Language
from subject import Subject from subject import Subject
from contact_attempt import ContactAttempt from contact_attempt import ContactAttempt
from mail_template import MailTemplate
def get_current_year(): def get_current_year():
...@@ -26,4 +27,4 @@ def get_current_year(): ...@@ -26,4 +27,4 @@ def get_current_year():
__all__ = [FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject, __all__ = [FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject,
Visit, Worker, ContactAttempt, ConfigurationItem] Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate]
...@@ -5,7 +5,6 @@ from django.db import models ...@@ -5,7 +5,6 @@ from django.db import models
from constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_FONT_COLOR, \ from constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_FONT_COLOR, \
CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE
from . import FlyingTeam, Location, Room, Visit, Worker
from . import ConfigurationItem from . import ConfigurationItem
...@@ -24,11 +23,11 @@ class Appointment(models.Model): ...@@ -24,11 +23,11 @@ class Appointment(models.Model):
APPOINTMENT_STATUS_NO_SHOW: 'No Show', APPOINTMENT_STATUS_NO_SHOW: 'No Show',
} }
flying_team = models.ForeignKey(FlyingTeam, flying_team = models.ForeignKey("web.FlyingTeam",
verbose_name='Flying team (if applicable)', verbose_name='Flying team (if applicable)',
null=True, blank=True null=True, blank=True
) )
worker_assigned = models.ForeignKey(Worker, worker_assigned = models.ForeignKey("web.Worker",
verbose_name='Worker conducting the assessment (if applicable)', verbose_name='Worker conducting the assessment (if applicable)',
null=True, blank=True null=True, blank=True
) )
...@@ -36,15 +35,15 @@ class Appointment(models.Model): ...@@ -36,15 +35,15 @@ class Appointment(models.Model):
verbose_name='Appointment types', verbose_name='Appointment types',
blank=True blank=True
) )
room = models.ForeignKey(Room, room = models.ForeignKey("web.Room",
verbose_name='Room ID', verbose_name='Room ID',
null=True, null=True,
blank=True blank=True
) )
location = models.ForeignKey(Location, location = models.ForeignKey("web.Location",
verbose_name='Location', verbose_name='Location',
) )
visit = models.ForeignKey(Visit, visit = models.ForeignKey("web.Visit",
verbose_name='Visit ID', verbose_name='Visit ID',
editable=False, editable=False,
null=True, null=True,
......
...@@ -7,6 +7,7 @@ from constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_F ...@@ -7,6 +7,7 @@ from constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_F
class AppointmentType(models.Model): class AppointmentType(models.Model):
class Meta: class Meta:
app_label = 'web' app_label = 'web'
ordering = ['description']
required_equipment = models.ManyToManyField("web.Item", required_equipment = models.ManyToManyField("web.Item",
verbose_name='Required equipment', verbose_name='Required equipment',
...@@ -52,9 +53,6 @@ class AppointmentType(models.Model): ...@@ -52,9 +53,6 @@ class AppointmentType(models.Model):
default='ANY' default='ANY'
) )
class Meta:
ordering = ['description']
def __str__(self): def __str__(self):
return self.description return self.description
......
# coding=utf-8 # coding=utf-8
import locale
BOOL_CHOICES = ((True, 'Yes'), (False, 'No')) BOOL_CHOICES = ((True, 'Yes'), (False, 'No'))
SEX_CHOICES_MALE = 'M' SEX_CHOICES_MALE = 'M'
SEX_CHOICES_FEMALE = 'F' SEX_CHOICES_FEMALE = 'F'
...@@ -34,3 +36,14 @@ NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE = "NO_SHOW_APPOINTMENT_COLOR" ...@@ -34,3 +36,14 @@ NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE = "NO_SHOW_APPOINTMENT_COLOR"
KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE = "KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE" KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE = "KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE"
KIT_EMAIL_HOUR_CONFIGURATION_TYPE = "KIT_DAILY_EMAIL_HOUR_CONFIGURATION_TYPE" KIT_EMAIL_HOUR_CONFIGURATION_TYPE = "KIT_DAILY_EMAIL_HOUR_CONFIGURATION_TYPE"
KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE = "KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE" KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE = "KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE"
MAIL_TEMPLATE_CONTEXT_SUBJECT = 'S'
MAIL_TEMPLATE_CONTEXT_APPOINTMENT = 'A'
MAIL_TEMPLATE_CONTEXT_VISIT = 'V'
MAIL_TEMPLATE_CONTEXT_CHOICES = (
(MAIL_TEMPLATE_CONTEXT_APPOINTMENT, 'Appointment'),
(MAIL_TEMPLATE_CONTEXT_SUBJECT, 'Subject'),
(MAIL_TEMPLATE_CONTEXT_VISIT, 'Visit'),
)
LOCALE_CHOICES = [(value, value) for value in sorted(locale.windows_locale.values())]
# coding=utf-8 # coding=utf-8
from django.db import models from django.db import models
from .constants import LOCALE_CHOICES
class Language(models.Model): class Language(models.Model):
class Meta: class Meta:
app_label = 'web' app_label = 'web'
ordering = ["order"]
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
image = models.ImageField() image = models.ImageField()
order = models.IntegerField(default=0)
locale = models.CharField(max_length=10, choices=LOCALE_CHOICES, null=False, blank=False, default="fr_FR")
def __str__(self): def __str__(self):
return self.name return self.name
......
# coding=utf-8
import datetime
import locale
from contextlib import contextmanager
from django.db import models
from .constants import MAIL_TEMPLATE_CONTEXT_CHOICES, MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_SUBJECT, \
MAIL_TEMPLATE_CONTEXT_VISIT
from ..docx_helper import process_file
from ..models import Appointment, Visit, Subject, Worker
DATE_FORMAT_FULL = "%A %-d %B %Y"
DATETIME_FORMAT = "%A %-d %B %Y, %H:%m"
DATE_FORMAT_SHORT = "%d.%m.%Y"
DATE_FORMAT_TIME = "%H:%M"
now = datetime.datetime.now()
@contextmanager
def setlocale(name):
saved = locale.setlocale(locale.LC_TIME)
try:
yield locale.setlocale(locale.LC_TIME, name)
finally:
locale.setlocale(locale.LC_TIME, saved)
class MailTemplate(models.Model):
MAILS_TEMPLATE_GENERIC_TAGS = [
("##DATE_FULL##", "Current date when the mail will be generated (long format)", now.strftime(DATE_FORMAT_FULL)),
("##DATE_SHORT##", "Current date when the mail will be generated (short format)",
now.strftime(DATE_FORMAT_SHORT)),
("##WORKER##", "The full name of the currently logged in user", "")
]
MAILS_TEMPLATE_SUBJECT_TAGS = [
("##S_FULL_NAME##", "Subject's full name", "first_name last_name"),
("##S_FIRST_NAME##", "Subject's first name", ""),
("##S_LAST_NAME##", "Subject's last name", ""),
("##S_ADDRESS##", "Subject's address", "street name and number"),
("##S_CITY##", "Subject's city of residence", ""),
("##S_POST_CODE##", "Subject's post code of residence", ""),
("##S_COUNTRY##", "Subject's country of residence", ""),
("##S_SEX##", "Subject's gender", "Male/Female"),
("##S_TYPE##", "Subject's type", "CONTROL/PATIENT"),
("##S_DATE_BORN##", "Subject's date of birth", now.strftime(DATE_FORMAT_SHORT)),
("##S_EMAIL##", "Subject's email address", ""),
("##S_PHONE_NUMBER##", "Subject's phone number", ""),
("##S_PHONE_NUMBER_2##", "Subject's second phone number", ""),
("##S_PHONE_NUMBER_3##", "Subject's third phone number", ""),
("##S_MAIL_LANGUAGE##", "Subject's preferred language for written communication", ""),
("##S_KNOWN_LANGUAGES##", "List of languages known by the subject", "comma separated"),
("##S_SCREENING_NUMBER##", "Subject's screening number", ""),
("##S_DIAGNOSIS##", "Subject's diagnosis", ""),
("##S_DIAGNOSIS_YEAR##", "Subject's year of diagnosis", ""),
("##S_MPOWER_ID##", "Subject's mPower identifier", ""),
("##S_ND_NUMBER##", "Subject's ND number", ""),
("##S_DATE_ADDED##", "Subject's date of creation", now.strftime(DATE_FORMAT_SHORT)),
]
MAILS_TEMPLATE_VISIT_TAGS = [
("##V_DATE_START_FULL##", "Visit's start date", now.strftime(DATETIME_FORMAT)),
("##V_DATE_START_SHORT##", "Visit's start date", now.strftime(DATE_FORMAT_SHORT)),
("##V_DATE_ENDS_FULL##", "Visit's end date", now.strftime(DATETIME_FORMAT)),
("##V_DATE_ENDS_SHORT##", "Visit's end date", now.strftime(DATE_FORMAT_SHORT)),
]
MAILS_TEMPLATE_APPOINTMENT_TAGS = [
("##A_DATE_FULL##", "Appointment's date and time", now.strftime(DATETIME_FORMAT)),
("##A_DATE_SHORT##", "Appointment's date", now.strftime(DATE_FORMAT_SHORT)),
("##A_TIME##", "Appointment's time", now.strftime(DATE_FORMAT_TIME)),
("##A_FLYING_TEAM##", "Appointment's flying team location", ""),
("##A_LOCATION##", "Appointment's location", "value can be 'Flying Team'"),
("##A_LOCATION_OR_FLYINGTEAM##", "Appointment's real location",
"if flying team then returns flying team exact location, otherwise returns location name"),
("##A_STATUS##", "Appointment's status", ""),
("##A_WORKER##", "Worker conducting the assessment", "first_name last_name"),
("##A_WORKER_PHONE##", "Phone number of the worker conducting the assessment", ""),
("##A_WORKER_EMAIL##", "Email address of the worker conducting the assessment", ""),
("##A_ROOM##", "Appointment's room", 'room_number address city'),
("##A_LENGTH##", "Appointment's duration", 'integer, value in minutes'),
("##A_TYPES##", "Appointment's types", "comma separated"),
]
name = models.CharField(max_length=255)
context = models.CharField(max_length=1, choices=MAIL_TEMPLATE_CONTEXT_CHOICES)
language = models.ForeignKey("web.Language", on_delete=models.CASCADE)
template_file = models.FileField(upload_to='templates/')
@staticmethod
def get_appointment_mail_templates(languages):
return MailTemplate.get_mail_templates_for_context(languages, MAIL_TEMPLATE_CONTEXT_APPOINTMENT)
@staticmethod
def get_subject_mail_templates(languages):
return MailTemplate.get_mail_templates_for_context(languages, MAIL_TEMPLATE_CONTEXT_SUBJECT)
@staticmethod
def get_visit_mail_templates(languages):
return MailTemplate.get_mail_templates_for_context(languages, MAIL_TEMPLATE_CONTEXT_VISIT)
@staticmethod
def get_mail_templates_for_context(languages, context):
languages_names = [language.name for language in languages]
templates = list(MailTemplate.objects.filter(context=context).all())
active_templates = []
disabled_templates = []
for template in templates:
if template.language.name in languages_names:
active_templates.append(template)
else:
disabled_templates.append(template)
active_templates.sort(key=lambda x: languages_names.index(x.language.name))
return active_templates, disabled_templates
def apply(self, instance, user, stream):
appointment = None
visit = None
subject = None
if isinstance(instance, Appointment):
appointment = instance
visit = instance.visit
subject = visit.subject
elif isinstance(instance, Visit):
visit = instance
subject = visit.subject
elif isinstance(instance, Subject):
subject = instance
# set locale to get correct date format
with setlocale(self.language.locale.encode('utf8')):
replacements = {}
self._add_generic_replacements(replacements, Worker.get_by_user(user))
self._add_appointment_replacements(replacements, appointment)
self._add_visit_replacements(replacements, visit)
self._add_subject_replacements(replacements, subject)
process_file(self.template_file.path, stream, replacements)
return stream
def _add_generic_replacements(self, replacements, worker):
current_datetime = datetime.datetime.now()
replacements.update({
"##DATE_FULL##": current_datetime.strftime(DATE_FORMAT_FULL),
"##DATE_SHORT##": current_datetime.strftime(DATE_FORMAT_SHORT),
"##WORKER##": str(worker)
})
def _add_appointment_replacements(self, replacements, appointment):
if appointment is not None:
if appointment.worker_assigned is not None:
worker_phone_number = appointment.worker_assigned.phone_number
worker_email_address = appointment.worker_assigned.email
else:
worker_phone_number = ""
worker_email_address = ""
if appointment.datetime_when is not None:
appointment_date_full = appointment.datetime_when.strftime(DATETIME_FORMAT)
appointment_date_short = appointment.datetime_when.strftime(DATE_FORMAT_SHORT)
appointment_date_time = appointment.datetime_when.strftime(DATE_FORMAT_TIME)
else:
appointment_date_full = appointment_date_short = appointment_date_time = ""
replacements.update({
"##A_DATE_FULL##": appointment_date_full,
"##A_DATE_SHORT##": appointment_date_short,
"##A_TIME##": appointment_date_time,
"##A_FLYING_TEAM##": appointment.flying_team,
"##A_STATUS##": appointment.get_status_display(),
"##A_LOCATION##": appointment.location.name,
"##A_LOCATION_OR_FLYINGTEAM##": appointment.flying_team or appointment.location.name,
"##A_WORKER##": str(appointment.worker_assigned),
'##A_WORKER_PHONE##': worker_phone_number,
'##A_WORKER_EMAIL##': worker_email_address,
"##A_ROOM##": str(appointment.room),
"##A_LENGTH##": appointment.length,
"##A_TYPES##": ", ".join([a.description for a in appointment.appointment_types.all()])
})
def _add_visit_replacements(self, replacements, visit):
if visit is not None:
replacements.update({
"##V_DATE_START_FULL##": visit.datetime_begin.strftime(DATETIME_FORMAT),
"##V_DATE_START_SHORT##": visit.datetime_begin.strftime(DATE_FORMAT_SHORT),
"##V_DATE_ENDS_FULL##": visit.datetime_end.strftime(DATETIME_FORMAT),
"##V_DATE_ENDS_SHORT##": visit.datetime_end.strftime(DATE_FORMAT_SHORT),
})
def _add_subject_replacements(self, replacements, subject):
if subject is not None:
if subject.date_born is not None:
date_born = subject.date_born.strftime(DATE_FORMAT_SHORT)
else:
date_born = None
replacements.update({
"##S_FULL_NAME##": str(subject),
"##S_FIRST_NAME##": subject.first_name,
"##S_LAST_NAME##": subject.last_name,
"##S_ADDRESS##": subject.address,
"##S_CITY##": subject.city,
"##S_COUNTRY##": subject.country,
"##S_DIAGNOSIS_YEAR##": subject.year_of_diagnosis,
"##S_DATE_ADDED##": subject.date_added.strftime(DATE_FORMAT_SHORT),
"##S_DATE_BORN##": date_born,
"##S_DIAGNOSIS##": subject.diagnosis,
"##S_EMAIL##": subject.email,
"##S_SEX##": subject.get_sex_display(),
"##S_MPOWER_ID##": subject.mpower_id,
"##S_ND_NUMBER##": subject.nd_number,
"##S_PHONE_NUMBER##": subject.phone_number,
"##S_PHONE_NUMBER_2##": subject.phone_number_2,
"##S_PHONE_NUMBER_3##": subject.phone_number_3,
"##S_POST_CODE##": subject.postal_code,
"##S_SCREENING_NUMBER##": subject.screening_number,
"##S_TYPE##": subject.get_type_display(),
'##S_MAIL_LANGUAGE##': subject.default_written_communication_language,
'##S_KNOWN_LANGUAGES##': ", ".join([l.name for l in subject.languages.all()])
})
...@@ -144,11 +144,6 @@ class Subject(models.Model): ...@@ -144,11 +144,6 @@ class Subject(models.Model):
verbose_name='Year of diagnosis (YYYY)' verbose_name='Year of diagnosis (YYYY)'
) )
dead = models.BooleanField(
verbose_name='Deceased',
default=False,
editable=True
)
information_sent = models.BooleanField( information_sent = models.BooleanField(
verbose_name='Information sent', verbose_name='Information sent',
default=False default=False
...@@ -157,6 +152,11 @@ class Subject(models.Model): ...@@ -157,6 +152,11 @@ class Subject(models.Model):
verbose_name='PD in family', verbose_name='PD in family',
default=False, default=False,
) )
dead = models.BooleanField(
verbose_name='Deceased',
default=False,
editable=True
)
resigned = models.BooleanField( resigned = models.BooleanField(
verbose_name='Resigned', verbose_name='Resigned',
default=False, default=False,
......
# coding=utf-8 # coding=utf-8
from django.conf import settings from django.conf import settings
from django.core.mail import send_mail
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
class EmailSender(object): class EmailSender(object):
def send_email(self, subject, body, recipients, cc_recipients=[]): def send_email(self, subject, body, recipients, cc_recipients=None):
if cc_recipients is None:
cc_recipients = []
email_from = getattr(settings, "DEFAULT_FROM_EMAIL", None) email_from = getattr(settings, "DEFAULT_FROM_EMAIL", None)
recipient_list = [] recipient_list = []
for recipient in recipients.split(";"): for recipient in recipients.split(";"):
...@@ -23,4 +24,4 @@ class EmailSender(object): ...@@ -23,4 +24,4 @@ class EmailSender(object):
message.content_subtype = "html" message.content_subtype = "html"
message.send() message.send()
# send_mail(subject, "", email_from, recipient_list, cc=cc_recipients, html_message=body) # send_mail(subject, "", email_from, recipient_list, cc=cc_recipients, html_message=body)
...@@ -16,4 +16,10 @@ ...@@ -16,4 +16,10 @@
.checkbox { .checkbox {
margin-top: 10px !important; margin-top: 10px !important;
}
.table-separator {
background-color: #f4f4f4;
font-variant: small-caps;
font-size: 1.5em;
} }
\ No newline at end of file
$(document).ready(function () {
$("#save-and-continue").click(function () {
var form = $(this).parents("form");
var hidden_field = $("<input type='hidden' name='_continue' value='1' />");
form.append(hidden_field);
form.submit();
});
});
\ No newline at end of file
...@@ -366,10 +366,12 @@ desired effect ...@@ -366,10 +366,12 @@ desired effect
<script src="{% static 'AdminLTE/js/bootstrap.min.js' %}"></script> <script src="{% static 'AdminLTE/js/bootstrap.min.js' %}"></script>
<!-- AdminLTE Template Helpers (for example- left side bar) --> <!-- AdminLTE Template Helpers (for example- left side bar) -->
<script src="{% static 'AdminLTE/js/app.min.js' %}"></script> <script src="{% static 'AdminLTE/js/app.min.js' %}"></script>
<!-- Smash js -->
<script src="{% static 'js/smash.js' %}"></script>
<script> <script>
var activate = function (page_to_activate) { var activate = function (page_to_activate) {
var $e = $(".sidebar-menu > li[data-desc='" + page_to_activate + "']"); var $e = $(".sidebar-menu li[data-desc='" + page_to_activate + "']");
$e.addClass("active"); $e.addClass("active");
}; };
......
...@@ -87,16 +87,22 @@ ...@@ -87,16 +87,22 @@
</fieldset> </fieldset>
{% endif %} {% endif %}
<div class="box-footer"> <div class="box-footer">
<div class="col-sm-6"> <div class="col-sm-4">
<button type="submit" class="btn btn-block btn-success">Save</button> <button type="submit" class="btn btn-block btn-success">Save</button>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-4">
<button id="save-and-continue" type="button" class="btn btn-block btn-success">Save and
Continue
</button>
</div>
<div class="col-sm-4">
<a href="{% url 'web.views.appointments' %}" class="btn btn-block btn-default" <a href="{% url 'web.views.appointments' %}" class="btn btn-block btn-default"
onclick="history.back()">Cancel</a> onclick="history.back()">Cancel</a>
</div> </div>
</div><!-- /.box-footer --> </div><!-- /.box-footer -->
</form> </form>
</div> </div>
{% include 'includes/mail_templates_box.html' with instance_id=appointment.id %}
{% endblock %} {% endblock %}
......
<div class="box box-success">
<div class="box-header with-border">
<h3>Generate Mail</h3>
</div>
<div class="box-body">
<h4>Available templates:</h4>
<p>
{% for mail_template in mail_templates.0 %}
<a href="{% url "web.views.mail_template_generate" mail_template.id instance_id %}"
class="btn btn-app">
<i class="fa"> {% autoescape off %}{{ mail_template.language.image_img }}{% endautoescape %}</i>
{{ mail_template.name }}
</a>
{% endfor %}
</p>
<h4>Unavailable templates:</h4>
<p>
{% for mail_template in mail_templates.1 %}
<span
class="btn btn-app disabled">
<i class="fa"> {% autoescape off %}{{ mail_template.language.image_img }}{% endautoescape %}</i>
{{ mail_template.name }}
</span>
{% endfor %}
<p></p>
</div>
</div>
\ No newline at end of file
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