2021-03-10 13:55:06 +00:00
|
|
|
|
import datetime
|
2017-09-20 15:21:05 +01:00
|
|
|
|
import itertools
|
2016-01-28 11:42:13 +00:00
|
|
|
|
import uuid
|
2016-09-22 11:56:26 +01:00
|
|
|
|
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from flask import current_app, url_for
|
2018-01-22 10:18:11 +00:00
|
|
|
|
from notifications_utils.columns import Columns
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from notifications_utils.letter_timings import get_letter_timings
|
2016-09-22 11:56:26 +01:00
|
|
|
|
from notifications_utils.recipients import (
|
2021-03-10 13:55:06 +00:00
|
|
|
|
InvalidEmailError,
|
|
|
|
|
|
InvalidPhoneError,
|
|
|
|
|
|
try_validate_and_format_phone_number,
|
2016-09-22 11:56:26 +01:00
|
|
|
|
validate_email_address,
|
|
|
|
|
|
validate_phone_number,
|
|
|
|
|
|
)
|
2017-09-20 10:27:18 +01:00
|
|
|
|
from notifications_utils.template import (
|
2021-03-10 13:55:06 +00:00
|
|
|
|
BroadcastMessageTemplate,
|
|
|
|
|
|
LetterPrintTemplate,
|
2017-09-20 10:27:18 +01:00
|
|
|
|
PlainTextEmailTemplate,
|
|
|
|
|
|
SMSMessageTemplate,
|
|
|
|
|
|
)
|
2020-06-24 07:34:58 +01:00
|
|
|
|
from notifications_utils.timezones import convert_utc_to_bst
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from sqlalchemy import (
|
|
|
|
|
|
CheckConstraint,
|
|
|
|
|
|
Index,
|
|
|
|
|
|
String,
|
|
|
|
|
|
UniqueConstraint,
|
|
|
|
|
|
and_,
|
|
|
|
|
|
func,
|
2016-01-19 11:38:29 +00:00
|
|
|
|
)
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from sqlalchemy.dialects.postgresql import JSON, JSONB, UUID
|
|
|
|
|
|
from sqlalchemy.ext.associationproxy import association_proxy
|
|
|
|
|
|
from sqlalchemy.ext.declarative import declared_attr
|
|
|
|
|
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
|
|
|
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
|
|
|
|
|
from sqlalchemy.schema import Sequence
|
|
|
|
|
|
|
2020-12-18 17:39:35 +00:00
|
|
|
|
from app import db, encryption
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from app.hashing import check_hash, hashpw
|
|
|
|
|
|
from app.history_meta import Versioned
|
2021-01-15 13:15:00 +00:00
|
|
|
|
from app.utils import (
|
|
|
|
|
|
DATETIME_FORMAT,
|
|
|
|
|
|
DATETIME_FORMAT_NO_TIMEZONE,
|
|
|
|
|
|
get_dt_string_or_none,
|
|
|
|
|
|
get_uuid_string_or_none,
|
|
|
|
|
|
)
|
2016-04-14 15:09:59 +01:00
|
|
|
|
|
2017-05-15 12:49:46 +01:00
|
|
|
|
SMS_TYPE = 'sms'
|
|
|
|
|
|
EMAIL_TYPE = 'email'
|
|
|
|
|
|
LETTER_TYPE = 'letter'
|
2020-07-02 12:12:34 +01:00
|
|
|
|
BROADCAST_TYPE = 'broadcast'
|
2017-05-15 12:49:46 +01:00
|
|
|
|
|
2020-07-02 12:12:34 +01:00
|
|
|
|
TEMPLATE_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE, BROADCAST_TYPE]
|
|
|
|
|
|
NOTIFICATION_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE] # not broadcast
|
2017-05-15 12:49:46 +01:00
|
|
|
|
|
|
|
|
|
|
template_types = db.Enum(*TEMPLATE_TYPES, name='template_type')
|
|
|
|
|
|
|
|
|
|
|
|
NORMAL = 'normal'
|
|
|
|
|
|
PRIORITY = 'priority'
|
|
|
|
|
|
TEMPLATE_PROCESS_TYPE = [NORMAL, PRIORITY]
|
|
|
|
|
|
|
2016-01-07 17:31:17 +00:00
|
|
|
|
|
2017-10-27 17:59:51 +01:00
|
|
|
|
SMS_AUTH_TYPE = 'sms_auth'
|
|
|
|
|
|
EMAIL_AUTH_TYPE = 'email_auth'
|
2021-05-12 15:58:13 +01:00
|
|
|
|
WEBAUTHN_AUTH_TYPE = 'webauthn_auth'
|
2021-05-12 17:04:08 +01:00
|
|
|
|
USER_AUTH_TYPES = [SMS_AUTH_TYPE, EMAIL_AUTH_TYPE, WEBAUTHN_AUTH_TYPE]
|
2017-10-27 17:59:51 +01:00
|
|
|
|
|
2018-07-17 16:15:57 +01:00
|
|
|
|
DELIVERY_STATUS_CALLBACK_TYPE = 'delivery_status'
|
|
|
|
|
|
COMPLAINT_CALLBACK_TYPE = 'complaint'
|
|
|
|
|
|
SERVICE_CALLBACK_TYPES = [DELIVERY_STATUS_CALLBACK_TYPE, COMPLAINT_CALLBACK_TYPE]
|
|
|
|
|
|
|
2017-10-27 17:59:51 +01:00
|
|
|
|
|
2016-01-08 17:51:46 +00:00
|
|
|
|
def filter_null_value_fields(obj):
|
|
|
|
|
|
return dict(
|
|
|
|
|
|
filter(lambda x: x[1] is not None, obj.items())
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-12-15 17:11:47 +00:00
|
|
|
|
class HistoryModel:
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def from_original(cls, original):
|
|
|
|
|
|
history = cls()
|
|
|
|
|
|
history.update_from_original(original)
|
|
|
|
|
|
return history
|
|
|
|
|
|
|
|
|
|
|
|
def update_from_original(self, original):
|
|
|
|
|
|
for c in self.__table__.columns:
|
2017-05-04 17:09:04 +01:00
|
|
|
|
# in some cases, columns may have different names to their underlying db column - so only copy those
|
|
|
|
|
|
# that we can, and leave it up to subclasses to deal with any oddities/properties etc.
|
|
|
|
|
|
if hasattr(original, c.name):
|
|
|
|
|
|
setattr(self, c.name, getattr(original, c.name))
|
|
|
|
|
|
else:
|
|
|
|
|
|
current_app.logger.debug('{} has no column {} to copy from'.format(original, c.name))
|
2016-12-15 17:11:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-01-07 17:31:17 +00:00
|
|
|
|
class User(db.Model):
|
|
|
|
|
|
__tablename__ = 'users'
|
|
|
|
|
|
|
2016-04-08 13:34:46 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2016-01-19 11:38:29 +00:00
|
|
|
|
name = db.Column(db.String, nullable=False, index=True, unique=False)
|
2016-01-07 17:31:17 +00:00
|
|
|
|
email_address = db.Column(db.String(255), nullable=False, index=True, unique=True)
|
2016-01-11 15:07:13 +00:00
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-04-27 10:27:05 +01:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-01-11 15:07:13 +00:00
|
|
|
|
updated_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True,
|
2016-04-27 10:27:05 +01:00
|
|
|
|
onupdate=datetime.datetime.utcnow)
|
2016-01-19 11:38:29 +00:00
|
|
|
|
_password = db.Column(db.String, index=False, unique=False, nullable=False)
|
2017-11-09 14:18:47 +00:00
|
|
|
|
mobile_number = db.Column(db.String, index=False, unique=False, nullable=True)
|
2016-06-28 11:24:08 +01:00
|
|
|
|
password_changed_at = db.Column(db.DateTime, index=False, unique=False, nullable=False,
|
|
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-01-19 11:38:29 +00:00
|
|
|
|
logged_in_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
failed_login_count = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
|
|
state = db.Column(db.String, nullable=False, default='pending')
|
2016-03-17 10:37:24 +00:00
|
|
|
|
platform_admin = db.Column(db.Boolean, nullable=False, default=False)
|
2017-02-17 14:06:16 +00:00
|
|
|
|
current_session_id = db.Column(UUID(as_uuid=True), nullable=True)
|
2017-10-27 17:59:51 +01:00
|
|
|
|
auth_type = db.Column(db.String, db.ForeignKey('auth_type.name'), index=True, nullable=False, default=SMS_AUTH_TYPE)
|
2020-02-04 16:45:09 +00:00
|
|
|
|
email_access_validated_at = db.Column(
|
|
|
|
|
|
db.DateTime, index=False, unique=False, nullable=False, default=datetime.datetime.utcnow
|
|
|
|
|
|
)
|
2016-01-19 11:38:29 +00:00
|
|
|
|
|
2017-11-09 14:18:47 +00:00
|
|
|
|
# either email auth or a mobile number must be provided
|
2021-05-12 15:58:13 +01:00
|
|
|
|
CheckConstraint("auth_type in ('email_auth', 'webauthn_auth') or mobile_number is not null")
|
2017-11-09 14:18:47 +00:00
|
|
|
|
|
2017-06-16 16:30:03 +01:00
|
|
|
|
services = db.relationship(
|
|
|
|
|
|
'Service',
|
|
|
|
|
|
secondary='user_to_service',
|
2019-02-20 16:18:48 +00:00
|
|
|
|
backref='users')
|
2018-02-20 17:09:16 +00:00
|
|
|
|
organisations = db.relationship(
|
|
|
|
|
|
'Organisation',
|
|
|
|
|
|
secondary='user_to_organisation',
|
2018-03-13 13:07:02 +00:00
|
|
|
|
backref='users')
|
2017-06-16 16:30:03 +01:00
|
|
|
|
|
2016-01-19 11:38:29 +00:00
|
|
|
|
@property
|
|
|
|
|
|
def password(self):
|
|
|
|
|
|
raise AttributeError("Password not readable")
|
|
|
|
|
|
|
2021-06-25 17:29:19 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def can_use_webauthn(self):
|
|
|
|
|
|
if self.platform_admin:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2021-06-30 15:41:43 +01:00
|
|
|
|
if self.auth_type == 'webauthn_auth':
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2021-06-25 17:29:19 +01:00
|
|
|
|
return any(
|
|
|
|
|
|
str(service.organisation_id) == current_app.config['BROADCAST_ORGANISATION_ID'] or
|
|
|
|
|
|
str(service.id) == current_app.config['NOTIFY_SERVICE_ID']
|
|
|
|
|
|
for service in self.services
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2016-01-19 11:38:29 +00:00
|
|
|
|
@password.setter
|
|
|
|
|
|
def password(self, password):
|
|
|
|
|
|
self._password = hashpw(password)
|
|
|
|
|
|
|
|
|
|
|
|
def check_password(self, password):
|
|
|
|
|
|
return check_hash(password, self._password)
|
2016-01-08 17:51:46 +00:00
|
|
|
|
|
2019-05-21 15:53:48 +01:00
|
|
|
|
def get_permissions(self, service_id=None):
|
2018-03-06 17:47:29 +00:00
|
|
|
|
from app.dao.permissions_dao import permission_dao
|
2019-05-21 15:53:48 +01:00
|
|
|
|
|
|
|
|
|
|
if service_id:
|
|
|
|
|
|
return [
|
|
|
|
|
|
x.permission for x in permission_dao.get_permissions_by_user_id_and_service_id(self.id, service_id)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2018-03-06 17:47:29 +00:00
|
|
|
|
retval = {}
|
|
|
|
|
|
for x in permission_dao.get_permissions_by_user_id(self.id):
|
|
|
|
|
|
service_id = str(x.service_id)
|
|
|
|
|
|
if service_id not in retval:
|
|
|
|
|
|
retval[service_id] = []
|
|
|
|
|
|
retval[service_id].append(x.permission)
|
|
|
|
|
|
return retval
|
|
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': self.id,
|
|
|
|
|
|
'name': self.name,
|
|
|
|
|
|
'email_address': self.email_address,
|
|
|
|
|
|
'auth_type': self.auth_type,
|
|
|
|
|
|
'current_session_id': self.current_session_id,
|
|
|
|
|
|
'failed_login_count': self.failed_login_count,
|
2020-02-04 16:45:09 +00:00
|
|
|
|
'email_access_validated_at': self.email_access_validated_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'logged_in_at': get_dt_string_or_none(self.logged_in_at),
|
2018-03-06 17:47:29 +00:00
|
|
|
|
'mobile_number': self.mobile_number,
|
|
|
|
|
|
'organisations': [x.id for x in self.organisations if x.active],
|
2020-02-04 16:45:09 +00:00
|
|
|
|
'password_changed_at': self.password_changed_at.strftime(DATETIME_FORMAT_NO_TIMEZONE),
|
2018-03-06 17:47:29 +00:00
|
|
|
|
'permissions': self.get_permissions(),
|
|
|
|
|
|
'platform_admin': self.platform_admin,
|
|
|
|
|
|
'services': [x.id for x in self.services if x.active],
|
2021-06-25 17:29:19 +01:00
|
|
|
|
'can_use_webauthn': self.can_use_webauthn,
|
2018-03-06 17:47:29 +00:00
|
|
|
|
'state': self.state,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 13:31:29 +01:00
|
|
|
|
def serialize_for_users_list(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': self.id,
|
|
|
|
|
|
'name': self.name,
|
|
|
|
|
|
'email_address': self.email_address,
|
|
|
|
|
|
'mobile_number': self.mobile_number,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-07 17:31:17 +00:00
|
|
|
|
|
2019-02-20 16:18:48 +00:00
|
|
|
|
class ServiceUser(db.Model):
|
|
|
|
|
|
__tablename__ = 'user_to_service'
|
|
|
|
|
|
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), primary_key=True)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
UniqueConstraint('user_id', 'service_id', name='uix_user_to_service'),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2018-02-15 14:16:16 +00:00
|
|
|
|
|
|
|
|
|
|
user_to_organisation = db.Table(
|
|
|
|
|
|
'user_to_organisation',
|
|
|
|
|
|
db.Model.metadata,
|
|
|
|
|
|
db.Column('user_id', UUID(as_uuid=True), db.ForeignKey('users.id')),
|
|
|
|
|
|
db.Column('organisation_id', UUID(as_uuid=True), db.ForeignKey('organisation.id')),
|
|
|
|
|
|
UniqueConstraint('user_id', 'organisation_id', name='uix_user_to_organisation')
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-20 16:18:48 +00:00
|
|
|
|
user_folder_permissions = db.Table(
|
|
|
|
|
|
'user_folder_permissions',
|
|
|
|
|
|
db.Model.metadata,
|
|
|
|
|
|
db.Column('user_id', UUID(as_uuid=True), primary_key=True),
|
|
|
|
|
|
db.Column('template_folder_id', UUID(as_uuid=True), db.ForeignKey('template_folder.id'), primary_key=True),
|
|
|
|
|
|
db.Column('service_id', UUID(as_uuid=True), primary_key=True),
|
|
|
|
|
|
db.ForeignKeyConstraint(['user_id', 'service_id'], ['user_to_service.user_id', 'user_to_service.service_id']),
|
|
|
|
|
|
db.ForeignKeyConstraint(['template_folder_id', 'service_id'], ['template_folder.id', 'template_folder.service_id'])
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-08-30 16:22:59 +01:00
|
|
|
|
BRANDING_GOVUK = 'govuk' # Deprecated outside migrations
|
2016-08-04 12:35:47 +01:00
|
|
|
|
BRANDING_ORG = 'org'
|
|
|
|
|
|
BRANDING_BOTH = 'both'
|
2017-09-19 13:18:59 +01:00
|
|
|
|
BRANDING_ORG_BANNER = 'org_banner'
|
2018-08-30 16:22:59 +01:00
|
|
|
|
BRANDING_TYPES = [BRANDING_ORG, BRANDING_BOTH, BRANDING_ORG_BANNER]
|
2016-08-04 12:35:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BrandingTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'branding_type'
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-01 17:16:48 +00:00
|
|
|
|
class EmailBranding(db.Model):
|
|
|
|
|
|
__tablename__ = 'email_branding'
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
colour = db.Column(db.String(7), nullable=True)
|
|
|
|
|
|
logo = db.Column(db.String(255), nullable=True)
|
2019-04-09 14:33:38 +01:00
|
|
|
|
name = db.Column(db.String(255), unique=True, nullable=False)
|
2018-07-25 16:06:08 +01:00
|
|
|
|
text = db.Column(db.String(255), nullable=True)
|
2018-08-23 13:53:05 +01:00
|
|
|
|
brand_type = db.Column(
|
|
|
|
|
|
db.String(255),
|
|
|
|
|
|
db.ForeignKey('branding_type.name'),
|
|
|
|
|
|
index=True,
|
2018-09-19 10:49:11 +01:00
|
|
|
|
nullable=False,
|
2018-08-30 16:22:59 +01:00
|
|
|
|
default=BRANDING_ORG
|
2018-08-23 13:53:05 +01:00
|
|
|
|
)
|
2018-02-01 17:16:48 +00:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
serialized = {
|
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"colour": self.colour,
|
|
|
|
|
|
"logo": self.logo,
|
|
|
|
|
|
"name": self.name,
|
2018-07-25 16:06:08 +01:00
|
|
|
|
"text": self.text,
|
2018-08-23 13:53:05 +01:00
|
|
|
|
"brand_type": self.brand_type
|
2018-02-01 17:16:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return serialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
service_email_branding = db.Table(
|
|
|
|
|
|
'service_email_branding',
|
|
|
|
|
|
db.Model.metadata,
|
|
|
|
|
|
# service_id is a primary key as you can only have one email branding per service
|
|
|
|
|
|
db.Column('service_id', UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True, nullable=False),
|
|
|
|
|
|
db.Column('email_branding_id', UUID(as_uuid=True), db.ForeignKey('email_branding.id'), nullable=False),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-01-17 17:06:01 +00:00
|
|
|
|
class LetterBranding(db.Model):
|
|
|
|
|
|
__tablename__ = 'letter_branding'
|
2019-01-22 17:27:00 +00:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2019-01-17 17:06:01 +00:00
|
|
|
|
name = db.Column(db.String(255), unique=True, nullable=False)
|
|
|
|
|
|
filename = db.Column(db.String(255), unique=True, nullable=False)
|
|
|
|
|
|
|
2019-01-24 16:38:52 +00:00
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
2019-01-25 15:03:01 +00:00
|
|
|
|
"id": str(self.id),
|
2019-01-24 16:38:52 +00:00
|
|
|
|
"name": self.name,
|
|
|
|
|
|
"filename": self.filename,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-17 17:06:01 +00:00
|
|
|
|
|
|
|
|
|
|
service_letter_branding = db.Table(
|
|
|
|
|
|
'service_letter_branding',
|
|
|
|
|
|
db.Model.metadata,
|
|
|
|
|
|
# service_id is a primary key as you can only have one letter branding per service
|
|
|
|
|
|
db.Column('service_id', UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True, nullable=False),
|
|
|
|
|
|
db.Column('letter_branding_id', UUID(as_uuid=True), db.ForeignKey('letter_branding.id'), nullable=False),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-05-17 14:09:18 +01:00
|
|
|
|
INTERNATIONAL_SMS_TYPE = 'international_sms'
|
2017-05-22 17:25:58 +01:00
|
|
|
|
INBOUND_SMS_TYPE = 'inbound_sms'
|
2017-05-26 15:41:14 +01:00
|
|
|
|
SCHEDULE_NOTIFICATIONS = 'schedule_notifications'
|
2017-10-26 16:39:33 +01:00
|
|
|
|
EMAIL_AUTH = 'email_auth'
|
2017-12-01 13:45:49 +00:00
|
|
|
|
LETTERS_AS_PDF = 'letters_as_pdf'
|
2018-02-21 16:26:49 +00:00
|
|
|
|
PRECOMPILED_LETTER = 'precompiled_letter'
|
2018-03-23 16:43:40 +00:00
|
|
|
|
UPLOAD_DOCUMENT = 'upload_document'
|
2019-02-14 11:29:57 +00:00
|
|
|
|
EDIT_FOLDER_PERMISSIONS = 'edit_folder_permissions'
|
2020-10-23 15:14:37 +01:00
|
|
|
|
UPLOAD_LETTERS = 'upload_letters'
|
2020-03-09 13:38:14 +00:00
|
|
|
|
INTERNATIONAL_LETTERS = 'international_letters'
|
2017-05-17 14:09:18 +01:00
|
|
|
|
|
2017-10-26 16:39:33 +01:00
|
|
|
|
SERVICE_PERMISSION_TYPES = [
|
|
|
|
|
|
EMAIL_TYPE,
|
|
|
|
|
|
SMS_TYPE,
|
|
|
|
|
|
LETTER_TYPE,
|
2020-07-02 12:12:34 +01:00
|
|
|
|
BROADCAST_TYPE,
|
2017-10-26 16:39:33 +01:00
|
|
|
|
INTERNATIONAL_SMS_TYPE,
|
|
|
|
|
|
INBOUND_SMS_TYPE,
|
|
|
|
|
|
SCHEDULE_NOTIFICATIONS,
|
|
|
|
|
|
EMAIL_AUTH,
|
2017-12-01 13:45:49 +00:00
|
|
|
|
LETTERS_AS_PDF,
|
2018-03-23 16:43:40 +00:00
|
|
|
|
UPLOAD_DOCUMENT,
|
2019-02-14 11:29:57 +00:00
|
|
|
|
EDIT_FOLDER_PERMISSIONS,
|
2020-10-23 15:14:37 +01:00
|
|
|
|
UPLOAD_LETTERS,
|
2020-03-09 13:38:14 +00:00
|
|
|
|
INTERNATIONAL_LETTERS,
|
2017-10-26 16:39:33 +01:00
|
|
|
|
]
|
2017-05-17 14:09:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServicePermissionTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'service_permission_types'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-19 11:47:30 +00:00
|
|
|
|
class Domain(db.Model):
|
|
|
|
|
|
__tablename__ = "domain"
|
|
|
|
|
|
domain = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
organisation_id = db.Column('organisation_id', UUID(as_uuid=True), db.ForeignKey('organisation.id'), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-07-10 18:17:33 +01:00
|
|
|
|
ORGANISATION_TYPES = [
|
2019-08-28 15:21:59 +01:00
|
|
|
|
"central", "local", "nhs_central", "nhs_local", "nhs_gp", "emergency_service", "school_or_college", "other",
|
2019-07-10 18:17:33 +01:00
|
|
|
|
]
|
2019-07-16 14:56:04 +01:00
|
|
|
|
|
|
|
|
|
|
CROWN_ORGANISATION_TYPES = ["nhs_central"]
|
2019-08-28 15:21:59 +01:00
|
|
|
|
NON_CROWN_ORGANISATION_TYPES = ["local", "nhs_local", "nhs_gp", "emergency_service", "school_or_college"]
|
2019-08-28 15:33:00 +01:00
|
|
|
|
NHS_ORGANISATION_TYPES = ["nhs_central", "nhs_local", "nhs_gp"]
|
2019-07-10 18:17:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OrganisationTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'organisation_types'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
is_crown = db.Column(db.Boolean, nullable=True)
|
|
|
|
|
|
annual_free_sms_fragment_limit = db.Column(db.BigInteger, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-10 01:31:24 +00:00
|
|
|
|
class Organisation(db.Model):
|
|
|
|
|
|
__tablename__ = "organisation"
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=False)
|
|
|
|
|
|
name = db.Column(db.String(255), nullable=False, unique=True, index=True)
|
|
|
|
|
|
active = db.Column(db.Boolean, nullable=False, default=True)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2019-02-19 11:47:30 +00:00
|
|
|
|
agreement_signed = db.Column(db.Boolean, nullable=True)
|
|
|
|
|
|
agreement_signed_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
agreement_signed_by_id = db.Column(
|
|
|
|
|
|
UUID(as_uuid=True),
|
|
|
|
|
|
db.ForeignKey('users.id'),
|
|
|
|
|
|
nullable=True,
|
|
|
|
|
|
)
|
2019-07-09 11:59:33 +01:00
|
|
|
|
agreement_signed_by = db.relationship('User')
|
2019-06-13 16:43:34 +01:00
|
|
|
|
agreement_signed_on_behalf_of_name = db.Column(db.String(255), nullable=True)
|
|
|
|
|
|
agreement_signed_on_behalf_of_email_address = db.Column(db.String(255), nullable=True)
|
2019-02-19 11:47:30 +00:00
|
|
|
|
agreement_signed_version = db.Column(db.Float, nullable=True)
|
|
|
|
|
|
crown = db.Column(db.Boolean, nullable=True)
|
2019-07-24 16:37:23 +01:00
|
|
|
|
organisation_type = db.Column(
|
|
|
|
|
|
db.String(255),
|
|
|
|
|
|
db.ForeignKey('organisation_types.name'),
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True,
|
|
|
|
|
|
)
|
2019-05-10 11:47:42 +01:00
|
|
|
|
request_to_go_live_notes = db.Column(db.Text)
|
2019-02-19 11:47:30 +00:00
|
|
|
|
|
|
|
|
|
|
domains = db.relationship(
|
|
|
|
|
|
'Domain',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
email_branding = db.relationship('EmailBranding')
|
|
|
|
|
|
email_branding_id = db.Column(
|
|
|
|
|
|
UUID(as_uuid=True),
|
|
|
|
|
|
db.ForeignKey('email_branding.id'),
|
|
|
|
|
|
nullable=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
letter_branding = db.relationship('LetterBranding')
|
|
|
|
|
|
letter_branding_id = db.Column(
|
|
|
|
|
|
UUID(as_uuid=True),
|
|
|
|
|
|
db.ForeignKey('letter_branding.id'),
|
|
|
|
|
|
nullable=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2021-02-01 14:43:25 +00:00
|
|
|
|
notes = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
purchase_order_number = db.Column(db.String(255), nullable=True)
|
|
|
|
|
|
billing_contact_names = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
billing_contact_email_addresses = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
billing_reference = db.Column(db.String(255), nullable=True)
|
|
|
|
|
|
|
2019-06-12 13:15:25 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def live_services(self):
|
|
|
|
|
|
return [
|
|
|
|
|
|
service for service in self.services
|
|
|
|
|
|
if service.active and not service.restricted
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2019-06-13 15:54:57 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def domain_list(self):
|
|
|
|
|
|
return [
|
|
|
|
|
|
domain.domain for domain in self.domains
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2018-02-10 01:31:24 +00:00
|
|
|
|
def serialize(self):
|
2019-02-19 11:47:30 +00:00
|
|
|
|
return {
|
2018-02-10 01:31:24 +00:00
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"name": self.name,
|
|
|
|
|
|
"active": self.active,
|
2019-02-19 11:47:30 +00:00
|
|
|
|
"crown": self.crown,
|
|
|
|
|
|
"organisation_type": self.organisation_type,
|
|
|
|
|
|
"letter_branding_id": self.letter_branding_id,
|
|
|
|
|
|
"email_branding_id": self.email_branding_id,
|
|
|
|
|
|
"agreement_signed": self.agreement_signed,
|
|
|
|
|
|
"agreement_signed_at": self.agreement_signed_at,
|
|
|
|
|
|
"agreement_signed_by_id": self.agreement_signed_by_id,
|
2019-06-13 16:43:34 +01:00
|
|
|
|
"agreement_signed_on_behalf_of_name": self.agreement_signed_on_behalf_of_name,
|
|
|
|
|
|
"agreement_signed_on_behalf_of_email_address": self.agreement_signed_on_behalf_of_email_address,
|
2019-02-19 11:47:30 +00:00
|
|
|
|
"agreement_signed_version": self.agreement_signed_version,
|
2019-06-13 15:54:57 +01:00
|
|
|
|
"domains": self.domain_list,
|
2019-05-10 11:47:42 +01:00
|
|
|
|
"request_to_go_live_notes": self.request_to_go_live_notes,
|
2019-06-12 13:15:25 +01:00
|
|
|
|
"count_of_live_services": len(self.live_services),
|
2021-02-04 17:33:46 +00:00
|
|
|
|
"notes": self.notes,
|
|
|
|
|
|
"purchase_order_number": self.purchase_order_number,
|
|
|
|
|
|
"billing_contact_names": self.billing_contact_names,
|
|
|
|
|
|
"billing_contact_email_addresses": self.billing_contact_email_addresses,
|
|
|
|
|
|
"billing_reference": self.billing_reference,
|
2018-02-10 01:31:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-06-13 15:54:57 +01:00
|
|
|
|
def serialize_for_list(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'name': self.name,
|
|
|
|
|
|
'id': str(self.id),
|
|
|
|
|
|
'active': self.active,
|
|
|
|
|
|
'count_of_live_services': len(self.live_services),
|
|
|
|
|
|
'domains': self.domain_list,
|
2019-09-05 16:04:14 +01:00
|
|
|
|
'organisation_type': self.organisation_type,
|
2019-06-13 15:54:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-02-10 01:31:24 +00:00
|
|
|
|
|
2016-04-14 15:09:59 +01:00
|
|
|
|
class Service(db.Model, Versioned):
|
2016-01-07 17:31:17 +00:00
|
|
|
|
__tablename__ = 'services'
|
|
|
|
|
|
|
2016-02-02 14:16:08 +00:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2016-02-19 15:52:19 +00:00
|
|
|
|
name = db.Column(db.String(255), nullable=False, unique=True)
|
2016-01-11 15:07:13 +00:00
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-05-11 10:56:24 +01:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-01-11 15:07:13 +00:00
|
|
|
|
updated_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True,
|
2016-05-11 10:56:24 +01:00
|
|
|
|
onupdate=datetime.datetime.utcnow)
|
2016-11-10 10:38:36 +00:00
|
|
|
|
active = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=True)
|
2016-04-08 16:13:10 +01:00
|
|
|
|
message_limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False)
|
2016-01-07 17:31:17 +00:00
|
|
|
|
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
|
2016-08-04 12:35:47 +01:00
|
|
|
|
research_mode = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=False)
|
2016-02-19 15:52:19 +00:00
|
|
|
|
email_from = db.Column(db.Text, index=False, unique=True, nullable=False)
|
2016-04-14 15:09:59 +01:00
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
2019-04-15 17:01:12 +01:00
|
|
|
|
created_by = db.relationship('User', foreign_keys=[created_by_id])
|
2017-12-01 16:51:09 +00:00
|
|
|
|
prefix_sms = db.Column(db.Boolean, nullable=False, default=True)
|
2017-10-05 15:14:27 +01:00
|
|
|
|
organisation_type = db.Column(
|
|
|
|
|
|
db.String(255),
|
2019-07-24 16:37:23 +01:00
|
|
|
|
db.ForeignKey('organisation_types.name'),
|
|
|
|
|
|
unique=False,
|
2017-10-05 15:14:27 +01:00
|
|
|
|
nullable=True,
|
|
|
|
|
|
)
|
2019-07-10 18:17:33 +01:00
|
|
|
|
crown = db.Column(db.Boolean, index=False, nullable=True)
|
2018-01-09 13:24:54 +00:00
|
|
|
|
rate_limit = db.Column(db.Integer, index=False, nullable=False, default=3000)
|
2018-05-31 15:13:31 +01:00
|
|
|
|
contact_link = db.Column(db.String(255), nullable=True, unique=False)
|
2019-02-14 11:32:50 +00:00
|
|
|
|
volume_sms = db.Column(db.Integer(), nullable=True, unique=False)
|
|
|
|
|
|
volume_email = db.Column(db.Integer(), nullable=True, unique=False)
|
|
|
|
|
|
volume_letter = db.Column(db.Integer(), nullable=True, unique=False)
|
2019-03-01 13:53:02 +00:00
|
|
|
|
consent_to_research = db.Column(db.Boolean, nullable=True)
|
2019-03-25 12:21:02 +00:00
|
|
|
|
count_as_live = db.Column(db.Boolean, nullable=False, default=True)
|
2019-04-15 17:01:12 +01:00
|
|
|
|
go_live_user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
|
|
|
|
|
|
go_live_user = db.relationship('User', foreign_keys=[go_live_user_id])
|
|
|
|
|
|
go_live_at = db.Column(db.DateTime, nullable=True)
|
2017-05-22 17:25:58 +01:00
|
|
|
|
|
2019-08-12 13:24:27 +01:00
|
|
|
|
organisation_id = db.Column(UUID(as_uuid=True), db.ForeignKey('organisation.id'), index=True, nullable=True)
|
2019-08-14 13:23:20 +01:00
|
|
|
|
organisation = db.relationship('Organisation', backref='services')
|
2016-04-14 15:09:59 +01:00
|
|
|
|
|
2021-01-13 11:53:16 +00:00
|
|
|
|
notes = db.Column(db.Text, nullable=True)
|
2021-01-20 18:00:43 +00:00
|
|
|
|
purchase_order_number = db.Column(db.String(255), nullable=True)
|
2021-01-25 17:53:22 +00:00
|
|
|
|
billing_contact_names = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
billing_contact_email_addresses = db.Column(db.Text, nullable=True)
|
2021-01-20 18:00:43 +00:00
|
|
|
|
billing_reference = db.Column(db.String(255), nullable=True)
|
2021-01-13 11:53:16 +00:00
|
|
|
|
|
2018-02-01 17:16:48 +00:00
|
|
|
|
email_branding = db.relationship(
|
|
|
|
|
|
'EmailBranding',
|
|
|
|
|
|
secondary=service_email_branding,
|
|
|
|
|
|
uselist=False,
|
|
|
|
|
|
backref=db.backref('services', lazy='dynamic'))
|
2019-01-22 17:27:00 +00:00
|
|
|
|
letter_branding = db.relationship(
|
|
|
|
|
|
'LetterBranding',
|
|
|
|
|
|
secondary=service_letter_branding,
|
|
|
|
|
|
uselist=False,
|
|
|
|
|
|
backref=db.backref('services', lazy='dynamic'))
|
2018-02-01 17:16:48 +00:00
|
|
|
|
|
2021-02-09 09:38:43 +00:00
|
|
|
|
allowed_broadcast_provider = association_proxy('service_broadcast_settings', 'provider')
|
2021-01-28 13:57:33 +00:00
|
|
|
|
broadcast_channel = association_proxy('service_broadcast_settings', 'channel')
|
2020-12-01 17:41:39 +00:00
|
|
|
|
|
2017-05-24 16:27:12 +01:00
|
|
|
|
@classmethod
|
|
|
|
|
|
def from_json(cls, data):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Assumption: data has been validated appropriately.
|
|
|
|
|
|
|
|
|
|
|
|
Returns a Service object based on the provided data. Deserialises created_by to created_by_id as marshmallow
|
|
|
|
|
|
would.
|
|
|
|
|
|
"""
|
|
|
|
|
|
# validate json with marshmallow
|
|
|
|
|
|
fields = data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
fields['created_by_id'] = fields.pop('created_by')
|
|
|
|
|
|
|
|
|
|
|
|
return cls(**fields)
|
|
|
|
|
|
|
2017-08-14 19:47:09 +01:00
|
|
|
|
def get_inbound_number(self):
|
|
|
|
|
|
if self.inbound_number and self.inbound_number.active:
|
|
|
|
|
|
return self.inbound_number.number
|
2017-09-21 16:41:10 +01:00
|
|
|
|
|
|
|
|
|
|
def get_default_sms_sender(self):
|
|
|
|
|
|
default_sms_sender = [x for x in self.service_sms_senders if x.is_default]
|
|
|
|
|
|
return default_sms_sender[0].sms_sender
|
2017-08-14 19:47:09 +01:00
|
|
|
|
|
2017-09-20 10:45:35 +01:00
|
|
|
|
def get_default_reply_to_email_address(self):
|
|
|
|
|
|
default_reply_to = [x for x in self.reply_to_email_addresses if x.is_default]
|
2017-10-04 14:51:02 +01:00
|
|
|
|
return default_reply_to[0].email_address if default_reply_to else None
|
2017-09-20 10:45:35 +01:00
|
|
|
|
|
2017-09-21 16:08:49 +01:00
|
|
|
|
def get_default_letter_contact(self):
|
|
|
|
|
|
default_letter_contact = [x for x in self.letter_contacts if x.is_default]
|
2017-10-04 14:51:02 +01:00
|
|
|
|
return default_letter_contact[0].contact_block if default_letter_contact else None
|
2017-09-21 16:08:49 +01:00
|
|
|
|
|
2017-12-11 11:00:27 +00:00
|
|
|
|
def has_permission(self, permission):
|
|
|
|
|
|
return permission in [p.permission for p in self.permissions]
|
|
|
|
|
|
|
2018-02-13 09:28:48 +00:00
|
|
|
|
def serialize_for_org_dashboard(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': str(self.id),
|
|
|
|
|
|
'name': self.name,
|
|
|
|
|
|
'active': self.active,
|
|
|
|
|
|
'restricted': self.restricted,
|
|
|
|
|
|
'research_mode': self.research_mode
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-03 17:44:32 +00:00
|
|
|
|
def get_available_broadcast_providers(self):
|
|
|
|
|
|
# There may be future checks here if we add, for example, platform admin level provider killswitches.
|
2021-05-10 15:58:31 +01:00
|
|
|
|
if self.allowed_broadcast_provider != ALL_BROADCAST_PROVIDERS:
|
2020-12-03 17:44:32 +00:00
|
|
|
|
return [x for x in current_app.config['ENABLED_CBCS'] if x == self.allowed_broadcast_provider]
|
|
|
|
|
|
else:
|
|
|
|
|
|
return current_app.config['ENABLED_CBCS']
|
|
|
|
|
|
|
2017-05-15 12:49:46 +01:00
|
|
|
|
|
2017-10-25 11:35:13 +01:00
|
|
|
|
class AnnualBilling(db.Model):
|
|
|
|
|
|
__tablename__ = "annual_billing"
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=False)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
|
|
|
|
|
financial_year_start = db.Column(db.Integer, nullable=False, default=True, unique=False)
|
|
|
|
|
|
free_sms_fragment_limit = db.Column(db.Integer, nullable=False, index=False, unique=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
UniqueConstraint('financial_year_start', 'service_id', name='ix_annual_billing_service_id')
|
|
|
|
|
|
service = db.relationship(Service, backref=db.backref("annual_billing", uselist=True))
|
|
|
|
|
|
|
2021-04-20 13:42:20 +01:00
|
|
|
|
__table_args__ = (UniqueConstraint(
|
|
|
|
|
|
'service_id', 'financial_year_start', name='uix_service_id_financial_year_start'),)
|
|
|
|
|
|
|
2017-10-26 13:25:11 +01:00
|
|
|
|
def serialize_free_sms_items(self):
|
2017-10-25 11:35:13 +01:00
|
|
|
|
return {
|
|
|
|
|
|
'free_sms_fragment_limit': self.free_sms_fragment_limit,
|
|
|
|
|
|
'financial_year_start': self.financial_year_start,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-26 13:25:11 +01:00
|
|
|
|
def serialize(self):
|
|
|
|
|
|
def serialize_service():
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": str(self.service_id),
|
|
|
|
|
|
"name": self.service.name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return{
|
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
'free_sms_fragment_limit': self.free_sms_fragment_limit,
|
|
|
|
|
|
'service_id': self.service_id,
|
|
|
|
|
|
'financial_year_start': self.financial_year_start,
|
|
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2017-10-26 13:25:11 +01:00
|
|
|
|
"service": serialize_service() if self.service else None,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-25 11:35:13 +01:00
|
|
|
|
|
2017-08-03 14:05:13 +01:00
|
|
|
|
class InboundNumber(db.Model):
|
|
|
|
|
|
__tablename__ = "inbound_numbers"
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
number = db.Column(db.String(11), unique=True, nullable=False)
|
|
|
|
|
|
provider = db.Column(db.String(), nullable=False)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=True, index=True, nullable=True)
|
2017-08-04 12:13:10 +01:00
|
|
|
|
service = db.relationship(Service, backref=db.backref("inbound_number", uselist=False))
|
2017-08-03 14:05:13 +01:00
|
|
|
|
active = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=True)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
2017-08-10 17:51:47 +01:00
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2017-08-03 14:05:13 +01:00
|
|
|
|
|
2017-08-04 12:13:10 +01:00
|
|
|
|
def serialize(self):
|
2017-08-04 19:06:37 +01:00
|
|
|
|
def serialize_service():
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": str(self.service_id),
|
|
|
|
|
|
"name": self.service.name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-04 16:05:03 +01:00
|
|
|
|
return {
|
2017-08-04 12:13:10 +01:00
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"number": self.number,
|
|
|
|
|
|
"provider": self.provider,
|
2017-08-04 19:06:37 +01:00
|
|
|
|
"service": serialize_service() if self.service else None,
|
2017-08-04 12:13:10 +01:00
|
|
|
|
"active": self.active,
|
|
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2017-08-04 12:13:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-03 14:05:13 +01:00
|
|
|
|
|
2017-09-05 17:53:47 +01:00
|
|
|
|
class ServiceSmsSender(db.Model):
|
|
|
|
|
|
__tablename__ = "service_sms_senders"
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
sms_sender = db.Column(db.String(11), nullable=False)
|
2017-10-26 15:25:38 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False, unique=False)
|
2017-09-21 16:41:10 +01:00
|
|
|
|
service = db.relationship(Service, backref=db.backref("service_sms_senders", uselist=True))
|
2017-09-05 17:53:47 +01:00
|
|
|
|
is_default = db.Column(db.Boolean, nullable=False, default=True)
|
2018-04-25 10:42:00 +01:00
|
|
|
|
archived = db.Column(db.Boolean, nullable=False, default=False)
|
2017-09-05 17:53:47 +01:00
|
|
|
|
inbound_number_id = db.Column(UUID(as_uuid=True), db.ForeignKey('inbound_numbers.id'),
|
|
|
|
|
|
unique=True, index=True, nullable=True)
|
|
|
|
|
|
inbound_number = db.relationship(InboundNumber, backref=db.backref("inbound_number", uselist=False))
|
|
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
|
2017-12-18 16:16:33 +00:00
|
|
|
|
def get_reply_to_text(self):
|
|
|
|
|
|
return try_validate_and_format_phone_number(self.sms_sender)
|
|
|
|
|
|
|
2017-10-19 09:58:23 +01:00
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"sms_sender": self.sms_sender,
|
2017-10-19 10:43:49 +01:00
|
|
|
|
"service_id": str(self.service_id),
|
2017-10-19 09:58:23 +01:00
|
|
|
|
"is_default": self.is_default,
|
2018-04-25 10:42:00 +01:00
|
|
|
|
"archived": self.archived,
|
2017-10-19 10:43:49 +01:00
|
|
|
|
"inbound_number_id": str(self.inbound_number_id) if self.inbound_number_id else None,
|
2017-10-19 09:58:23 +01:00
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2017-10-19 09:58:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-09-05 17:53:47 +01:00
|
|
|
|
|
2017-05-22 17:25:58 +01:00
|
|
|
|
class ServicePermission(db.Model):
|
|
|
|
|
|
__tablename__ = "service_permissions"
|
|
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'),
|
|
|
|
|
|
primary_key=True, index=True, nullable=False)
|
|
|
|
|
|
permission = db.Column(db.String(255), db.ForeignKey('service_permission_types.name'),
|
|
|
|
|
|
index=True, primary_key=True, nullable=False)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
|
|
|
|
|
2017-05-23 14:24:07 +01:00
|
|
|
|
service_permission_types = db.relationship(
|
|
|
|
|
|
Service, backref=db.backref("permissions", cascade="all, delete-orphan"))
|
2017-05-22 17:25:58 +01:00
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
return '<{} has service permission: {}>'.format(self.service_id, self.permission)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-09-27 13:44:29 +01:00
|
|
|
|
MOBILE_TYPE = 'mobile'
|
|
|
|
|
|
EMAIL_TYPE = 'email'
|
|
|
|
|
|
|
2020-07-28 10:22:13 +01:00
|
|
|
|
GUEST_LIST_RECIPIENT_TYPE = [MOBILE_TYPE, EMAIL_TYPE]
|
|
|
|
|
|
guest_list_recipient_types = db.Enum(*GUEST_LIST_RECIPIENT_TYPE, name='recipient_type')
|
2016-01-13 11:04:13 +00:00
|
|
|
|
|
2016-09-27 14:16:35 +01:00
|
|
|
|
|
2020-07-28 10:22:13 +01:00
|
|
|
|
class ServiceGuestList(db.Model):
|
2016-09-20 15:41:53 +01:00
|
|
|
|
__tablename__ = 'service_whitelist'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
|
2020-07-28 11:23:30 +01:00
|
|
|
|
service = db.relationship('Service', backref='guest_list')
|
2020-07-28 10:22:13 +01:00
|
|
|
|
recipient_type = db.Column(guest_list_recipient_types, nullable=False)
|
2016-09-27 13:44:29 +01:00
|
|
|
|
recipient = db.Column(db.String(255), nullable=False)
|
2016-09-20 15:41:53 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
|
|
|
|
|
|
2016-09-22 11:56:26 +01:00
|
|
|
|
@classmethod
|
2016-09-27 13:44:29 +01:00
|
|
|
|
def from_string(cls, service_id, recipient_type, recipient):
|
|
|
|
|
|
instance = cls(service_id=service_id, recipient_type=recipient_type)
|
2016-09-22 11:56:26 +01:00
|
|
|
|
|
2016-09-27 13:44:29 +01:00
|
|
|
|
try:
|
|
|
|
|
|
if recipient_type == MOBILE_TYPE:
|
2017-08-30 10:55:18 +01:00
|
|
|
|
validate_phone_number(recipient, international=True)
|
2016-09-27 13:44:29 +01:00
|
|
|
|
instance.recipient = recipient
|
|
|
|
|
|
elif recipient_type == EMAIL_TYPE:
|
|
|
|
|
|
validate_email_address(recipient)
|
|
|
|
|
|
instance.recipient = recipient
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError('Invalid recipient type')
|
|
|
|
|
|
except InvalidPhoneError:
|
2020-07-28 10:23:22 +01:00
|
|
|
|
raise ValueError('Invalid guest list: "{}"'.format(recipient))
|
2016-09-27 13:44:29 +01:00
|
|
|
|
except InvalidEmailError:
|
2020-07-28 10:23:22 +01:00
|
|
|
|
raise ValueError('Invalid guest list: "{}"'.format(recipient))
|
2016-09-27 13:44:29 +01:00
|
|
|
|
else:
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
2016-11-25 16:58:46 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
return 'Recipient {} of type: {}'.format(self.recipient, self.recipient_type)
|
2016-09-20 15:41:53 +01:00
|
|
|
|
|
2016-09-22 17:18:52 +01:00
|
|
|
|
|
2017-06-15 11:32:51 +01:00
|
|
|
|
class ServiceInboundApi(db.Model, Versioned):
|
2017-06-13 15:27:13 +01:00
|
|
|
|
__tablename__ = 'service_inbound_api'
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2017-06-15 16:19:12 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False, unique=True)
|
|
|
|
|
|
service = db.relationship('Service', backref='inbound_api')
|
2017-06-19 12:25:05 +01:00
|
|
|
|
url = db.Column(db.String(), nullable=False)
|
2017-06-19 14:32:22 +01:00
|
|
|
|
_bearer_token = db.Column("bearer_token", db.String(), nullable=False)
|
2017-06-15 11:32:51 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
updated_by = db.relationship('User')
|
|
|
|
|
|
updated_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
2017-06-13 15:27:13 +01:00
|
|
|
|
|
|
|
|
|
|
@property
|
2017-06-19 14:32:22 +01:00
|
|
|
|
def bearer_token(self):
|
|
|
|
|
|
if self._bearer_token:
|
|
|
|
|
|
return encryption.decrypt(self._bearer_token)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@bearer_token.setter
|
|
|
|
|
|
def bearer_token(self, bearer_token):
|
|
|
|
|
|
if bearer_token:
|
|
|
|
|
|
self._bearer_token = encryption.encrypt(str(bearer_token))
|
2017-06-13 15:27:13 +01:00
|
|
|
|
|
2017-06-15 11:32:51 +01:00
|
|
|
|
def serialize(self):
|
2017-11-28 15:25:15 +00:00
|
|
|
|
return {
|
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"service_id": str(self.service_id),
|
|
|
|
|
|
"url": self.url,
|
|
|
|
|
|
"updated_by_id": str(self.updated_by_id),
|
|
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2017-11-28 15:25:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceCallbackApi(db.Model, Versioned):
|
|
|
|
|
|
__tablename__ = 'service_callback_api'
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2018-07-25 14:12:13 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
|
2017-11-28 15:25:15 +00:00
|
|
|
|
service = db.relationship('Service', backref='service_callback_api')
|
|
|
|
|
|
url = db.Column(db.String(), nullable=False)
|
2018-07-17 16:15:57 +01:00
|
|
|
|
callback_type = db.Column(db.String(), db.ForeignKey('service_callback_type.name'), nullable=True)
|
2017-11-28 15:25:15 +00:00
|
|
|
|
_bearer_token = db.Column("bearer_token", db.String(), nullable=False)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
updated_by = db.relationship('User')
|
|
|
|
|
|
updated_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
|
|
|
|
|
|
2018-07-25 14:12:13 +01:00
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
UniqueConstraint('service_id', 'callback_type', name='uix_service_callback_type'),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2017-11-28 15:25:15 +00:00
|
|
|
|
@property
|
|
|
|
|
|
def bearer_token(self):
|
|
|
|
|
|
if self._bearer_token:
|
|
|
|
|
|
return encryption.decrypt(self._bearer_token)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@bearer_token.setter
|
|
|
|
|
|
def bearer_token(self, bearer_token):
|
|
|
|
|
|
if bearer_token:
|
|
|
|
|
|
self._bearer_token = encryption.encrypt(str(bearer_token))
|
|
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
2017-06-15 11:32:51 +01:00
|
|
|
|
return {
|
2017-06-15 16:19:12 +01:00
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"service_id": str(self.service_id),
|
2017-06-15 11:32:51 +01:00
|
|
|
|
"url": self.url,
|
2017-06-15 16:19:12 +01:00
|
|
|
|
"updated_by_id": str(self.updated_by_id),
|
2017-06-15 11:32:51 +01:00
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2017-06-15 11:32:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-06-13 15:27:13 +01:00
|
|
|
|
|
2018-07-17 16:15:57 +01:00
|
|
|
|
class ServiceCallbackType(db.Model):
|
|
|
|
|
|
__tablename__ = 'service_callback_type'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String, primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-20 17:25:20 +01:00
|
|
|
|
class ApiKey(db.Model, Versioned):
|
2016-04-08 13:34:46 +01:00
|
|
|
|
__tablename__ = 'api_keys'
|
2016-01-13 09:25:46 +00:00
|
|
|
|
|
2016-04-08 13:34:46 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2016-01-19 12:07:00 +00:00
|
|
|
|
name = db.Column(db.String(255), nullable=False)
|
2017-06-19 14:32:22 +01:00
|
|
|
|
_secret = db.Column("secret", db.String(255), unique=True, nullable=False)
|
2016-02-02 14:16:08 +00:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
|
2016-11-10 11:07:12 +00:00
|
|
|
|
service = db.relationship('Service', backref='api_keys')
|
2016-06-23 16:45:20 +01:00
|
|
|
|
key_type = db.Column(db.String(255), db.ForeignKey('key_types.name'), index=True, nullable=False)
|
2016-01-13 09:25:46 +00:00
|
|
|
|
expiry_date = db.Column(db.DateTime)
|
2016-04-20 17:25:20 +01:00
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-05-11 10:56:24 +01:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-04-20 17:25:20 +01:00
|
|
|
|
updated_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True,
|
2016-05-11 10:56:24 +01:00
|
|
|
|
onupdate=datetime.datetime.utcnow)
|
2016-04-20 17:25:20 +01:00
|
|
|
|
created_by = db.relationship('User')
|
|
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
2016-01-13 09:25:46 +00:00
|
|
|
|
|
2016-01-21 16:53:53 +00:00
|
|
|
|
__table_args__ = (
|
2019-06-04 15:30:27 +01:00
|
|
|
|
Index('uix_service_to_key_name', 'service_id', 'name', unique=True, postgresql_where=expiry_date.is_(None)),
|
2016-01-21 16:53:53 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2016-06-29 14:15:32 +01:00
|
|
|
|
@property
|
2017-06-19 14:32:22 +01:00
|
|
|
|
def secret(self):
|
|
|
|
|
|
if self._secret:
|
|
|
|
|
|
return encryption.decrypt(self._secret)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@secret.setter
|
|
|
|
|
|
def secret(self, secret):
|
|
|
|
|
|
if secret:
|
|
|
|
|
|
self._secret = encryption.encrypt(str(secret))
|
2016-06-29 14:15:32 +01:00
|
|
|
|
|
2016-01-13 09:25:46 +00:00
|
|
|
|
|
2016-06-23 16:45:20 +01:00
|
|
|
|
KEY_TYPE_NORMAL = 'normal'
|
|
|
|
|
|
KEY_TYPE_TEAM = 'team'
|
2016-07-05 10:47:47 +01:00
|
|
|
|
KEY_TYPE_TEST = 'test'
|
2016-06-23 16:45:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KeyTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'key_types'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-01-13 12:14:34 +00:00
|
|
|
|
class TemplateProcessTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'template_process_type'
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-10-26 16:01:31 +01:00
|
|
|
|
class TemplateFolder(db.Model):
|
|
|
|
|
|
__tablename__ = 'template_folder'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), nullable=False)
|
|
|
|
|
|
name = db.Column(db.String, nullable=False)
|
|
|
|
|
|
parent_id = db.Column(UUID(as_uuid=True), db.ForeignKey('template_folder.id'), nullable=True)
|
|
|
|
|
|
|
2018-10-30 16:26:25 +00:00
|
|
|
|
service = db.relationship('Service', backref='all_template_folders')
|
|
|
|
|
|
parent = db.relationship('TemplateFolder', remote_side=[id], backref='subfolders')
|
2019-02-20 16:18:48 +00:00
|
|
|
|
users = db.relationship(
|
|
|
|
|
|
'ServiceUser',
|
|
|
|
|
|
uselist=True,
|
|
|
|
|
|
backref=db.backref('folders', foreign_keys='user_folder_permissions.c.template_folder_id'),
|
|
|
|
|
|
secondary='user_folder_permissions',
|
|
|
|
|
|
primaryjoin='TemplateFolder.id == user_folder_permissions.c.template_folder_id'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
UniqueConstraint('id', 'service_id', name='ix_id_service_id'), {}
|
|
|
|
|
|
)
|
2018-10-30 16:26:25 +00:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': self.id,
|
|
|
|
|
|
'name': self.name,
|
|
|
|
|
|
'parent_id': self.parent_id,
|
2019-02-22 13:26:20 +00:00
|
|
|
|
'service_id': self.service_id,
|
|
|
|
|
|
'users_with_permission': self.get_users_with_permission()
|
2018-10-30 16:26:25 +00:00
|
|
|
|
}
|
2018-10-26 16:01:31 +01:00
|
|
|
|
|
2018-11-08 16:44:57 +00:00
|
|
|
|
def is_parent_of(self, other):
|
|
|
|
|
|
while other.parent is not None:
|
|
|
|
|
|
if other.parent == self:
|
|
|
|
|
|
return True
|
|
|
|
|
|
other = other.parent
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2019-02-22 13:26:20 +00:00
|
|
|
|
def get_users_with_permission(self):
|
|
|
|
|
|
service_users = self.users
|
|
|
|
|
|
users_with_permission = [str(service_user.user_id) for service_user in service_users]
|
|
|
|
|
|
|
|
|
|
|
|
return users_with_permission
|
|
|
|
|
|
|
2018-10-26 16:01:31 +01:00
|
|
|
|
|
|
|
|
|
|
template_folder_map = db.Table(
|
|
|
|
|
|
'template_folder_map',
|
|
|
|
|
|
db.Model.metadata,
|
|
|
|
|
|
# template_id is a primary key as a template can only belong in one folder
|
|
|
|
|
|
db.Column('template_id', UUID(as_uuid=True), db.ForeignKey('templates.id'), primary_key=True, nullable=False),
|
|
|
|
|
|
db.Column('template_folder_id', UUID(as_uuid=True), db.ForeignKey('template_folder.id'), nullable=False),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-03-07 15:42:59 +00:00
|
|
|
|
PRECOMPILED_TEMPLATE_NAME = 'Pre-compiled PDF'
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-11-21 14:43:07 +00:00
|
|
|
|
class TemplateBase(db.Model):
|
|
|
|
|
|
__abstract__ = True
|
2016-01-13 11:04:13 +00:00
|
|
|
|
|
2017-11-22 15:55:11 +00:00
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
|
if 'template_type' in kwargs:
|
|
|
|
|
|
self.template_type = kwargs.pop('template_type')
|
|
|
|
|
|
|
|
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
|
2016-04-08 13:34:46 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2016-01-13 11:04:13 +00:00
|
|
|
|
name = db.Column(db.String(255), nullable=False)
|
2016-06-29 11:50:54 +01:00
|
|
|
|
template_type = db.Column(template_types, nullable=False)
|
2017-11-21 14:43:07 +00:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
content = db.Column(db.Text, nullable=False)
|
|
|
|
|
|
archived = db.Column(db.Boolean, nullable=False, default=False)
|
2018-02-22 11:53:42 +00:00
|
|
|
|
hidden = db.Column(db.Boolean, nullable=False, default=False)
|
2017-11-21 14:43:07 +00:00
|
|
|
|
subject = db.Column(db.Text)
|
2018-12-14 12:45:58 +00:00
|
|
|
|
postage = db.Column(db.String, nullable=True)
|
2020-07-02 12:12:34 +01:00
|
|
|
|
broadcast_data = db.Column(JSONB(none_as_null=True), nullable=True)
|
2017-11-21 14:43:07 +00:00
|
|
|
|
|
|
|
|
|
|
@declared_attr
|
|
|
|
|
|
def service_id(cls):
|
|
|
|
|
|
return db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
@declared_attr
|
|
|
|
|
|
def created_by_id(cls):
|
|
|
|
|
|
return db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
@declared_attr
|
|
|
|
|
|
def created_by(cls):
|
|
|
|
|
|
return db.relationship('User')
|
|
|
|
|
|
|
|
|
|
|
|
@declared_attr
|
|
|
|
|
|
def process_type(cls):
|
|
|
|
|
|
return db.Column(
|
|
|
|
|
|
db.String(255),
|
|
|
|
|
|
db.ForeignKey('template_process_type.name'),
|
|
|
|
|
|
index=True,
|
|
|
|
|
|
nullable=False,
|
|
|
|
|
|
default=NORMAL
|
|
|
|
|
|
)
|
2016-08-02 16:23:14 +01:00
|
|
|
|
|
2017-06-28 16:10:22 +01:00
|
|
|
|
redact_personalisation = association_proxy('template_redacted', 'redact_personalisation')
|
|
|
|
|
|
|
2017-11-21 14:46:08 +00:00
|
|
|
|
@declared_attr
|
|
|
|
|
|
def service_letter_contact_id(cls):
|
|
|
|
|
|
return db.Column(UUID(as_uuid=True), db.ForeignKey('service_letter_contacts.id'), nullable=True)
|
|
|
|
|
|
|
2017-12-15 17:10:45 +00:00
|
|
|
|
@declared_attr
|
|
|
|
|
|
def service_letter_contact(cls):
|
|
|
|
|
|
return db.relationship('ServiceLetterContact', viewonly=True)
|
|
|
|
|
|
|
2017-11-21 14:46:08 +00:00
|
|
|
|
@property
|
|
|
|
|
|
def reply_to(self):
|
|
|
|
|
|
if self.template_type == LETTER_TYPE:
|
|
|
|
|
|
return self.service_letter_contact_id
|
|
|
|
|
|
else:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@reply_to.setter
|
|
|
|
|
|
def reply_to(self, value):
|
|
|
|
|
|
if self.template_type == LETTER_TYPE:
|
|
|
|
|
|
self.service_letter_contact_id = value
|
|
|
|
|
|
elif value is None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError('Unable to set sender for {} template'.format(self.template_type))
|
|
|
|
|
|
|
2017-12-15 17:10:45 +00:00
|
|
|
|
def get_reply_to_text(self):
|
|
|
|
|
|
if self.template_type == LETTER_TYPE:
|
2018-01-08 16:54:19 +00:00
|
|
|
|
return self.service_letter_contact.contact_block if self.service_letter_contact else None
|
2017-12-15 17:10:45 +00:00
|
|
|
|
elif self.template_type == EMAIL_TYPE:
|
|
|
|
|
|
return self.service.get_default_reply_to_email_address()
|
|
|
|
|
|
elif self.template_type == SMS_TYPE:
|
2017-12-18 16:14:09 +00:00
|
|
|
|
return try_validate_and_format_phone_number(self.service.get_default_sms_sender())
|
2017-12-15 17:10:45 +00:00
|
|
|
|
else:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2018-03-07 15:42:59 +00:00
|
|
|
|
@hybrid_property
|
|
|
|
|
|
def is_precompiled_letter(self):
|
|
|
|
|
|
return self.hidden and self.name == PRECOMPILED_TEMPLATE_NAME and self.template_type == LETTER_TYPE
|
|
|
|
|
|
|
|
|
|
|
|
@is_precompiled_letter.setter
|
|
|
|
|
|
def is_precompiled_letter(self, value):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2017-09-20 10:27:18 +01:00
|
|
|
|
def _as_utils_template(self):
|
|
|
|
|
|
if self.template_type == EMAIL_TYPE:
|
2020-04-06 12:50:22 +01:00
|
|
|
|
return PlainTextEmailTemplate(self.__dict__)
|
2017-09-20 10:27:18 +01:00
|
|
|
|
if self.template_type == SMS_TYPE:
|
2020-04-06 12:50:22 +01:00
|
|
|
|
return SMSMessageTemplate(self.__dict__)
|
2020-07-02 12:12:34 +01:00
|
|
|
|
if self.template_type == BROADCAST_TYPE:
|
|
|
|
|
|
return BroadcastMessageTemplate(self.__dict__)
|
2017-09-20 10:27:18 +01:00
|
|
|
|
if self.template_type == LETTER_TYPE:
|
2018-03-02 14:12:38 +00:00
|
|
|
|
return LetterPrintTemplate(
|
2020-04-06 12:50:22 +01:00
|
|
|
|
self.__dict__,
|
2020-04-06 14:25:43 +01:00
|
|
|
|
contact_block=self.get_reply_to_text(),
|
2017-09-20 10:27:18 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2020-04-06 14:25:43 +01:00
|
|
|
|
def _as_utils_template_with_personalisation(self, values):
|
|
|
|
|
|
template = self._as_utils_template()
|
|
|
|
|
|
template.values = values
|
|
|
|
|
|
return template
|
|
|
|
|
|
|
2020-07-06 16:41:53 +01:00
|
|
|
|
def serialize_for_v2(self):
|
2017-03-14 15:25:36 +00:00
|
|
|
|
serialized = {
|
2017-03-28 10:41:25 +01:00
|
|
|
|
"id": str(self.id),
|
2017-03-14 15:25:36 +00:00
|
|
|
|
"type": self.template_type,
|
|
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2017-03-14 15:25:36 +00:00
|
|
|
|
"created_by": self.created_by.email_address,
|
|
|
|
|
|
"version": self.version,
|
|
|
|
|
|
"body": self.content,
|
2020-07-02 12:12:34 +01:00
|
|
|
|
"subject": self.subject if self.template_type in {EMAIL_TYPE, LETTER_TYPE} else None,
|
2017-08-15 14:34:02 +01:00
|
|
|
|
"name": self.name,
|
2017-09-22 10:12:32 +01:00
|
|
|
|
"personalisation": {
|
|
|
|
|
|
key: {
|
|
|
|
|
|
'required': True,
|
|
|
|
|
|
}
|
|
|
|
|
|
for key in self._as_utils_template().placeholders
|
|
|
|
|
|
},
|
2018-12-14 12:45:58 +00:00
|
|
|
|
"postage": self.postage,
|
2020-07-09 18:10:03 +01:00
|
|
|
|
"letter_contact_block": self.service_letter_contact.contact_block if self.service_letter_contact else None,
|
2017-03-14 15:25:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return serialized
|
|
|
|
|
|
|
2016-08-02 16:23:14 +01:00
|
|
|
|
|
2017-11-21 14:43:07 +00:00
|
|
|
|
class Template(TemplateBase):
|
|
|
|
|
|
__tablename__ = 'templates'
|
|
|
|
|
|
|
|
|
|
|
|
service = db.relationship('Service', backref='templates')
|
|
|
|
|
|
version = db.Column(db.Integer, default=0, nullable=False)
|
|
|
|
|
|
|
2018-10-26 16:01:31 +01:00
|
|
|
|
folder = db.relationship(
|
|
|
|
|
|
'TemplateFolder',
|
|
|
|
|
|
secondary=template_folder_map,
|
|
|
|
|
|
uselist=False,
|
2018-10-29 11:57:24 +00:00
|
|
|
|
# eagerly load the folder whenever the template object is fetched
|
|
|
|
|
|
lazy='joined',
|
2018-10-30 16:26:25 +00:00
|
|
|
|
backref=db.backref('templates')
|
2018-10-26 16:01:31 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2017-11-21 14:43:07 +00:00
|
|
|
|
def get_link(self):
|
|
|
|
|
|
# TODO: use "/v2/" route once available
|
|
|
|
|
|
return url_for(
|
|
|
|
|
|
"template.get_template_by_id_and_service_id",
|
|
|
|
|
|
service_id=self.service_id,
|
|
|
|
|
|
template_id=self.id,
|
|
|
|
|
|
_external=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2018-11-02 16:00:22 +00:00
|
|
|
|
@classmethod
|
2018-11-05 10:54:42 +00:00
|
|
|
|
def from_json(cls, data, folder):
|
2018-11-02 16:00:22 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Assumption: data has been validated appropriately.
|
|
|
|
|
|
Returns a Template object based on the provided data.
|
|
|
|
|
|
"""
|
|
|
|
|
|
fields = data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
fields['created_by_id'] = fields.pop('created_by')
|
|
|
|
|
|
fields['service_id'] = fields.pop('service')
|
2018-11-07 12:48:56 +00:00
|
|
|
|
fields['folder'] = folder
|
2018-11-02 16:00:22 +00:00
|
|
|
|
return cls(**fields)
|
|
|
|
|
|
|
2017-11-21 14:43:07 +00:00
|
|
|
|
|
2017-06-28 10:26:25 +01:00
|
|
|
|
class TemplateRedacted(db.Model):
|
|
|
|
|
|
__tablename__ = 'template_redacted'
|
|
|
|
|
|
|
|
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), db.ForeignKey('templates.id'), primary_key=True, nullable=False)
|
|
|
|
|
|
redact_personalisation = db.Column(db.Boolean, nullable=False, default=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2017-07-10 14:43:46 +01:00
|
|
|
|
updated_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=False, index=True)
|
2017-06-28 10:26:25 +01:00
|
|
|
|
updated_by = db.relationship('User')
|
|
|
|
|
|
|
|
|
|
|
|
# uselist=False as this is a one-to-one relationship
|
|
|
|
|
|
template = db.relationship('Template', uselist=False, backref=db.backref('template_redacted', uselist=False))
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-11-21 14:43:07 +00:00
|
|
|
|
class TemplateHistory(TemplateBase):
|
2016-08-02 16:23:14 +01:00
|
|
|
|
__tablename__ = 'templates_history'
|
|
|
|
|
|
|
|
|
|
|
|
service = db.relationship('Service')
|
2016-10-04 10:47:34 +01:00
|
|
|
|
version = db.Column(db.Integer, primary_key=True, nullable=False)
|
2016-01-15 11:12:05 +00:00
|
|
|
|
|
2017-11-21 14:43:07 +00:00
|
|
|
|
@declared_attr
|
|
|
|
|
|
def template_redacted(cls):
|
|
|
|
|
|
return db.relationship('TemplateRedacted', foreign_keys=[cls.id],
|
|
|
|
|
|
primaryjoin='TemplateRedacted.template_id == TemplateHistory.id')
|
2017-11-09 14:50:18 +00:00
|
|
|
|
|
2017-11-09 14:48:27 +00:00
|
|
|
|
def get_link(self):
|
|
|
|
|
|
return url_for(
|
|
|
|
|
|
"v2_template.get_template_by_id",
|
|
|
|
|
|
template_id=self.id,
|
|
|
|
|
|
version=self.version,
|
|
|
|
|
|
_external=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2017-02-01 09:19:32 +00:00
|
|
|
|
|
2016-04-21 11:37:38 +01:00
|
|
|
|
MMG_PROVIDER = "mmg"
|
|
|
|
|
|
FIRETEXT_PROVIDER = "firetext"
|
|
|
|
|
|
SES_PROVIDER = 'ses'
|
|
|
|
|
|
|
2016-06-06 10:50:36 +01:00
|
|
|
|
SMS_PROVIDERS = [MMG_PROVIDER, FIRETEXT_PROVIDER]
|
2016-04-28 12:01:27 +01:00
|
|
|
|
EMAIL_PROVIDERS = [SES_PROVIDER]
|
|
|
|
|
|
PROVIDERS = SMS_PROVIDERS + EMAIL_PROVIDERS
|
2016-04-21 11:37:38 +01:00
|
|
|
|
|
2016-06-29 11:50:54 +01:00
|
|
|
|
NOTIFICATION_TYPE = [EMAIL_TYPE, SMS_TYPE, LETTER_TYPE]
|
|
|
|
|
|
notification_types = db.Enum(*NOTIFICATION_TYPE, name='notification_type')
|
2016-04-21 11:37:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderRates(db.Model):
|
|
|
|
|
|
__tablename__ = 'provider_rates'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
valid_from = db.Column(db.DateTime, nullable=False)
|
|
|
|
|
|
rate = db.Column(db.Numeric(), nullable=False)
|
2016-05-05 09:55:25 +01:00
|
|
|
|
provider_id = db.Column(UUID(as_uuid=True), db.ForeignKey('provider_details.id'), index=True, nullable=False)
|
2016-05-11 15:36:17 +01:00
|
|
|
|
provider = db.relationship('ProviderDetails', backref=db.backref('provider_rates', lazy='dynamic'))
|
2016-05-05 09:55:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderDetails(db.Model):
|
|
|
|
|
|
__tablename__ = 'provider_details'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
display_name = db.Column(db.String, nullable=False)
|
|
|
|
|
|
identifier = db.Column(db.String, nullable=False)
|
|
|
|
|
|
priority = db.Column(db.Integer, nullable=False)
|
2016-06-29 11:50:54 +01:00
|
|
|
|
notification_type = db.Column(notification_types, nullable=False)
|
2016-12-15 17:11:47 +00:00
|
|
|
|
active = db.Column(db.Boolean, default=False, nullable=False)
|
2016-12-19 16:49:56 +00:00
|
|
|
|
version = db.Column(db.Integer, default=1, nullable=False)
|
2016-12-19 17:45:46 +00:00
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2017-03-02 17:59:02 +00:00
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True)
|
|
|
|
|
|
created_by = db.relationship('User')
|
2017-04-25 10:36:37 +01:00
|
|
|
|
supports_international = db.Column(db.Boolean, nullable=False, default=False)
|
2016-12-15 17:11:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderDetailsHistory(db.Model, HistoryModel):
|
|
|
|
|
|
__tablename__ = 'provider_details_history'
|
|
|
|
|
|
|
2016-12-19 16:49:56 +00:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, nullable=False)
|
2016-12-15 17:11:47 +00:00
|
|
|
|
display_name = db.Column(db.String, nullable=False)
|
|
|
|
|
|
identifier = db.Column(db.String, nullable=False)
|
|
|
|
|
|
priority = db.Column(db.Integer, nullable=False)
|
|
|
|
|
|
notification_type = db.Column(notification_types, nullable=False)
|
|
|
|
|
|
active = db.Column(db.Boolean, nullable=False)
|
2016-12-19 16:49:56 +00:00
|
|
|
|
version = db.Column(db.Integer, primary_key=True, nullable=False)
|
2016-12-19 17:45:46 +00:00
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2017-03-02 17:59:02 +00:00
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True)
|
|
|
|
|
|
created_by = db.relationship('User')
|
2017-04-25 10:36:37 +01:00
|
|
|
|
supports_international = db.Column(db.Boolean, nullable=False, default=False)
|
2016-04-21 11:37:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
2016-08-24 13:34:42 +01:00
|
|
|
|
JOB_STATUS_PENDING = 'pending'
|
|
|
|
|
|
JOB_STATUS_IN_PROGRESS = 'in progress'
|
|
|
|
|
|
JOB_STATUS_FINISHED = 'finished'
|
|
|
|
|
|
JOB_STATUS_SENDING_LIMITS_EXCEEDED = 'sending limits exceeded'
|
|
|
|
|
|
JOB_STATUS_SCHEDULED = 'scheduled'
|
2016-09-01 14:31:01 +01:00
|
|
|
|
JOB_STATUS_CANCELLED = 'cancelled'
|
2017-03-10 16:33:15 +00:00
|
|
|
|
JOB_STATUS_READY_TO_SEND = 'ready to send'
|
|
|
|
|
|
JOB_STATUS_SENT_TO_DVLA = 'sent to dvla'
|
2017-04-18 11:42:48 +01:00
|
|
|
|
JOB_STATUS_ERROR = 'error'
|
2016-09-23 16:34:13 +01:00
|
|
|
|
JOB_STATUS_TYPES = [
|
|
|
|
|
|
JOB_STATUS_PENDING,
|
|
|
|
|
|
JOB_STATUS_IN_PROGRESS,
|
|
|
|
|
|
JOB_STATUS_FINISHED,
|
|
|
|
|
|
JOB_STATUS_SENDING_LIMITS_EXCEEDED,
|
|
|
|
|
|
JOB_STATUS_SCHEDULED,
|
2017-04-06 17:16:08 +01:00
|
|
|
|
JOB_STATUS_CANCELLED,
|
|
|
|
|
|
JOB_STATUS_READY_TO_SEND,
|
2017-04-18 11:42:48 +01:00
|
|
|
|
JOB_STATUS_SENT_TO_DVLA,
|
|
|
|
|
|
JOB_STATUS_ERROR
|
2016-09-23 16:34:13 +01:00
|
|
|
|
]
|
2016-08-24 13:34:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
2016-08-24 14:35:22 +01:00
|
|
|
|
class JobStatus(db.Model):
|
2016-08-24 13:34:42 +01:00
|
|
|
|
__tablename__ = 'job_status'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-01-15 11:12:05 +00:00
|
|
|
|
class Job(db.Model):
|
|
|
|
|
|
__tablename__ = 'jobs'
|
|
|
|
|
|
|
2017-07-26 15:57:30 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2016-01-15 11:12:05 +00:00
|
|
|
|
original_file_name = db.Column(db.String, nullable=False)
|
2016-02-02 14:16:08 +00:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False, nullable=False)
|
2016-01-15 11:12:05 +00:00
|
|
|
|
service = db.relationship('Service', backref=db.backref('jobs', lazy='dynamic'))
|
2016-04-08 13:34:46 +01:00
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), db.ForeignKey('templates.id'), index=True, unique=False)
|
2016-01-15 15:48:05 +00:00
|
|
|
|
template = db.relationship('Template', backref=db.backref('jobs', lazy='dynamic'))
|
2016-05-11 17:04:51 +01:00
|
|
|
|
template_version = db.Column(db.Integer, nullable=False)
|
2016-01-15 11:12:05 +00:00
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-03-15 09:32:43 +00:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-01-15 11:12:05 +00:00
|
|
|
|
updated_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True,
|
2016-03-15 09:32:43 +00:00
|
|
|
|
onupdate=datetime.datetime.utcnow)
|
2016-02-22 14:56:09 +00:00
|
|
|
|
notification_count = db.Column(db.Integer, nullable=False)
|
2016-03-04 14:25:28 +00:00
|
|
|
|
notifications_sent = db.Column(db.Integer, nullable=False, default=0)
|
2016-05-23 15:44:57 +01:00
|
|
|
|
notifications_delivered = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
|
|
notifications_failed = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
|
|
|
2016-02-25 09:59:50 +00:00
|
|
|
|
processing_started = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True)
|
|
|
|
|
|
processing_finished = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True)
|
2016-04-26 16:15:34 +01:00
|
|
|
|
created_by = db.relationship('User')
|
2017-07-27 12:58:13 +01:00
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True)
|
2016-08-24 13:34:42 +01:00
|
|
|
|
scheduled_for = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=True,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True)
|
2016-08-24 14:04:52 +01:00
|
|
|
|
job_status = db.Column(
|
2016-08-25 16:59:38 +01:00
|
|
|
|
db.String(255), db.ForeignKey('job_status.name'), index=True, nullable=False, default='pending'
|
2016-08-24 14:16:39 +01:00
|
|
|
|
)
|
2018-11-22 15:51:10 +00:00
|
|
|
|
archived = db.Column(db.Boolean, nullable=False, default=False)
|
2020-03-12 13:53:57 +00:00
|
|
|
|
contact_list_id = db.Column(UUID(as_uuid=True), db.ForeignKey('service_contact_list.id'), nullable=True)
|
2016-01-21 17:29:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-06-30 15:41:51 +01:00
|
|
|
|
VERIFY_CODE_TYPES = [EMAIL_TYPE, SMS_TYPE]
|
2016-02-01 10:54:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-01-21 17:29:24 +00:00
|
|
|
|
class VerifyCode(db.Model):
|
|
|
|
|
|
__tablename__ = 'verify_codes'
|
|
|
|
|
|
|
2016-04-08 13:34:46 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
2016-01-21 17:29:24 +00:00
|
|
|
|
user = db.relationship('User', backref=db.backref('verify_codes', lazy='dynamic'))
|
|
|
|
|
|
_code = db.Column(db.String, nullable=False)
|
2016-02-01 10:54:32 +00:00
|
|
|
|
code_type = db.Column(db.Enum(*VERIFY_CODE_TYPES, name='verify_code_types'),
|
|
|
|
|
|
index=False, unique=False, nullable=False)
|
2016-01-21 17:29:24 +00:00
|
|
|
|
expiry_datetime = db.Column(db.DateTime, nullable=False)
|
|
|
|
|
|
code_used = db.Column(db.Boolean, default=False)
|
|
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-03-15 09:32:43 +00:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-01-21 17:29:24 +00:00
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def code(self):
|
|
|
|
|
|
raise AttributeError("Code not readable")
|
|
|
|
|
|
|
|
|
|
|
|
@code.setter
|
|
|
|
|
|
def code(self, cde):
|
|
|
|
|
|
self._code = hashpw(cde)
|
|
|
|
|
|
|
|
|
|
|
|
def check_code(self, cde):
|
|
|
|
|
|
return check_hash(cde, self._code)
|
2016-02-09 12:01:17 +00:00
|
|
|
|
|
2016-09-07 13:44:56 +01:00
|
|
|
|
|
2018-07-31 13:52:04 +01:00
|
|
|
|
NOTIFICATION_CANCELLED = 'cancelled'
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_CREATED = 'created'
|
|
|
|
|
|
NOTIFICATION_SENDING = 'sending'
|
2017-04-25 17:08:29 +01:00
|
|
|
|
NOTIFICATION_SENT = 'sent'
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_DELIVERED = 'delivered'
|
|
|
|
|
|
NOTIFICATION_PENDING = 'pending'
|
|
|
|
|
|
NOTIFICATION_FAILED = 'failed'
|
|
|
|
|
|
NOTIFICATION_TECHNICAL_FAILURE = 'technical-failure'
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE = 'temporary-failure'
|
|
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE = 'permanent-failure'
|
2018-03-12 11:46:48 +00:00
|
|
|
|
NOTIFICATION_PENDING_VIRUS_CHECK = 'pending-virus-check'
|
2018-09-03 13:24:51 +01:00
|
|
|
|
NOTIFICATION_VALIDATION_FAILED = 'validation-failed'
|
2018-03-12 11:46:48 +00:00
|
|
|
|
NOTIFICATION_VIRUS_SCAN_FAILED = 'virus-scan-failed'
|
2018-08-21 16:45:10 +01:00
|
|
|
|
NOTIFICATION_RETURNED_LETTER = 'returned-letter'
|
2016-02-09 12:01:17 +00:00
|
|
|
|
|
2016-11-25 14:55:29 +00:00
|
|
|
|
NOTIFICATION_STATUS_TYPES_FAILED = [
|
|
|
|
|
|
NOTIFICATION_TECHNICAL_FAILURE,
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE,
|
|
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE,
|
2018-09-03 13:24:51 +01:00
|
|
|
|
NOTIFICATION_VALIDATION_FAILED,
|
2018-03-12 11:46:48 +00:00
|
|
|
|
NOTIFICATION_VIRUS_SCAN_FAILED,
|
2018-08-21 16:45:10 +01:00
|
|
|
|
NOTIFICATION_RETURNED_LETTER,
|
2016-11-25 14:55:29 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
2016-11-25 14:53:21 +00:00
|
|
|
|
NOTIFICATION_STATUS_TYPES_COMPLETED = [
|
2017-04-25 17:08:29 +01:00
|
|
|
|
NOTIFICATION_SENT,
|
2016-11-25 14:53:21 +00:00
|
|
|
|
NOTIFICATION_DELIVERED,
|
|
|
|
|
|
NOTIFICATION_FAILED,
|
|
|
|
|
|
NOTIFICATION_TECHNICAL_FAILURE,
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE,
|
|
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE,
|
2018-08-21 16:45:10 +01:00
|
|
|
|
NOTIFICATION_RETURNED_LETTER,
|
2018-11-22 15:17:17 +00:00
|
|
|
|
NOTIFICATION_CANCELLED,
|
2016-11-25 14:53:21 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
2017-05-12 12:19:44 +01:00
|
|
|
|
NOTIFICATION_STATUS_SUCCESS = [
|
|
|
|
|
|
NOTIFICATION_SENT,
|
|
|
|
|
|
NOTIFICATION_DELIVERED
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_STATUS_TYPES_BILLABLE = [
|
|
|
|
|
|
NOTIFICATION_SENDING,
|
2017-04-25 17:08:29 +01:00
|
|
|
|
NOTIFICATION_SENT,
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_DELIVERED,
|
2019-12-06 16:36:54 +00:00
|
|
|
|
NOTIFICATION_PENDING,
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_FAILED,
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE,
|
2016-11-25 14:55:29 +00:00
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE,
|
2018-08-21 16:45:10 +01:00
|
|
|
|
NOTIFICATION_RETURNED_LETTER,
|
2016-07-29 16:39:51 +01:00
|
|
|
|
]
|
|
|
|
|
|
|
2020-02-19 10:58:06 +00:00
|
|
|
|
NOTIFICATION_STATUS_TYPES_BILLABLE_SMS = [
|
|
|
|
|
|
NOTIFICATION_SENDING,
|
|
|
|
|
|
NOTIFICATION_SENT, # internationally
|
|
|
|
|
|
NOTIFICATION_DELIVERED,
|
|
|
|
|
|
NOTIFICATION_PENDING,
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE,
|
|
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE,
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
NOTIFICATION_STATUS_TYPES_BILLABLE_FOR_LETTERS = [
|
|
|
|
|
|
NOTIFICATION_SENDING,
|
|
|
|
|
|
NOTIFICATION_DELIVERED,
|
|
|
|
|
|
NOTIFICATION_RETURNED_LETTER,
|
|
|
|
|
|
]
|
|
|
|
|
|
# we don't really have a concept of billable emails - however the ft billing table only includes emails that we have
|
|
|
|
|
|
# actually sent.
|
|
|
|
|
|
NOTIFICATION_STATUS_TYPES_SENT_EMAILS = [
|
|
|
|
|
|
NOTIFICATION_SENDING,
|
|
|
|
|
|
NOTIFICATION_DELIVERED,
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE,
|
|
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE,
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_STATUS_TYPES = [
|
2018-07-31 13:52:04 +01:00
|
|
|
|
NOTIFICATION_CANCELLED,
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_CREATED,
|
|
|
|
|
|
NOTIFICATION_SENDING,
|
2017-04-25 17:08:29 +01:00
|
|
|
|
NOTIFICATION_SENT,
|
2016-07-29 16:39:51 +01:00
|
|
|
|
NOTIFICATION_DELIVERED,
|
|
|
|
|
|
NOTIFICATION_PENDING,
|
|
|
|
|
|
NOTIFICATION_FAILED,
|
|
|
|
|
|
NOTIFICATION_TECHNICAL_FAILURE,
|
|
|
|
|
|
NOTIFICATION_TEMPORARY_FAILURE,
|
2016-11-25 14:55:29 +00:00
|
|
|
|
NOTIFICATION_PERMANENT_FAILURE,
|
2018-03-12 11:46:48 +00:00
|
|
|
|
NOTIFICATION_PENDING_VIRUS_CHECK,
|
2018-09-03 13:24:51 +01:00
|
|
|
|
NOTIFICATION_VALIDATION_FAILED,
|
2018-03-12 11:46:48 +00:00
|
|
|
|
NOTIFICATION_VIRUS_SCAN_FAILED,
|
2018-08-21 16:45:10 +01:00
|
|
|
|
NOTIFICATION_RETURNED_LETTER,
|
2016-07-29 16:39:51 +01:00
|
|
|
|
]
|
2017-05-12 12:19:44 +01:00
|
|
|
|
|
2017-05-19 16:42:47 +01:00
|
|
|
|
NOTIFICATION_STATUS_TYPES_NON_BILLABLE = list(set(NOTIFICATION_STATUS_TYPES) - set(NOTIFICATION_STATUS_TYPES_BILLABLE))
|
|
|
|
|
|
|
2016-07-07 16:30:22 +01:00
|
|
|
|
NOTIFICATION_STATUS_TYPES_ENUM = db.Enum(*NOTIFICATION_STATUS_TYPES, name='notify_status_type')
|
2016-02-09 12:01:17 +00:00
|
|
|
|
|
2017-09-11 11:57:33 +01:00
|
|
|
|
NOTIFICATION_STATUS_LETTER_ACCEPTED = 'accepted'
|
2017-10-23 15:57:00 +01:00
|
|
|
|
NOTIFICATION_STATUS_LETTER_RECEIVED = 'received'
|
|
|
|
|
|
|
2017-10-25 15:38:58 +01:00
|
|
|
|
DVLA_RESPONSE_STATUS_SENT = 'Sent'
|
2017-09-08 16:35:13 +01:00
|
|
|
|
|
2018-09-25 11:04:58 +01:00
|
|
|
|
FIRST_CLASS = 'first'
|
|
|
|
|
|
SECOND_CLASS = 'second'
|
2020-06-08 13:38:54 +01:00
|
|
|
|
EUROPE = 'europe'
|
|
|
|
|
|
REST_OF_WORLD = 'rest-of-world'
|
|
|
|
|
|
POSTAGE_TYPES = [FIRST_CLASS, SECOND_CLASS, EUROPE, REST_OF_WORLD]
|
2020-07-10 17:43:40 +01:00
|
|
|
|
UK_POSTAGE_TYPES = [FIRST_CLASS, SECOND_CLASS]
|
|
|
|
|
|
INTERNATIONAL_POSTAGE_TYPES = [EUROPE, REST_OF_WORLD]
|
2018-09-25 11:04:58 +01:00
|
|
|
|
RESOLVE_POSTAGE_FOR_FILE_NAME = {
|
|
|
|
|
|
FIRST_CLASS: 1,
|
2020-06-08 13:38:54 +01:00
|
|
|
|
SECOND_CLASS: 2,
|
|
|
|
|
|
EUROPE: 'E',
|
|
|
|
|
|
REST_OF_WORLD: 'N',
|
2018-09-25 11:04:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-11 16:48:32 +01:00
|
|
|
|
|
2017-05-04 17:09:04 +01:00
|
|
|
|
class NotificationStatusTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'notification_status_types'
|
|
|
|
|
|
|
2017-10-26 15:25:38 +01:00
|
|
|
|
name = db.Column(db.String(), primary_key=True)
|
2017-05-04 17:09:04 +01:00
|
|
|
|
|
|
|
|
|
|
|
2016-02-09 12:01:17 +00:00
|
|
|
|
class Notification(db.Model):
|
|
|
|
|
|
__tablename__ = 'notifications'
|
|
|
|
|
|
|
2016-02-09 18:28:10 +00:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2016-02-09 12:01:17 +00:00
|
|
|
|
to = db.Column(db.String, nullable=False)
|
2017-05-23 10:43:48 +01:00
|
|
|
|
normalised_to = db.Column(db.String, nullable=True)
|
2016-02-10 11:08:24 +00:00
|
|
|
|
job_id = db.Column(UUID(as_uuid=True), db.ForeignKey('jobs.id'), index=True, unique=False)
|
2016-02-09 12:01:17 +00:00
|
|
|
|
job = db.relationship('Job', backref=db.backref('notifications', lazy='dynamic'))
|
2016-05-19 10:46:03 +01:00
|
|
|
|
job_row_number = db.Column(db.Integer, nullable=True)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False)
|
2016-02-09 12:48:27 +00:00
|
|
|
|
service = db.relationship('Service')
|
2017-11-08 10:32:45 +00:00
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), index=True, unique=False)
|
2016-05-11 17:04:51 +01:00
|
|
|
|
template_version = db.Column(db.Integer, nullable=False)
|
2017-11-08 10:32:45 +00:00
|
|
|
|
template = db.relationship('TemplateHistory')
|
2021-06-14 14:43:34 +01:00
|
|
|
|
api_key_id = db.Column(UUID(as_uuid=True), db.ForeignKey('api_keys.id'), unique=False)
|
2016-06-23 16:45:20 +01:00
|
|
|
|
api_key = db.relationship('ApiKey')
|
2021-06-14 14:43:34 +01:00
|
|
|
|
key_type = db.Column(db.String, db.ForeignKey('key_types.name'), unique=False, nullable=False)
|
2016-08-03 14:27:58 +01:00
|
|
|
|
billable_units = db.Column(db.Integer, nullable=False, default=0)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
notification_type = db.Column(notification_types, nullable=False)
|
2016-02-09 12:01:17 +00:00
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
2016-08-02 12:14:42 +01:00
|
|
|
|
index=True,
|
2016-02-09 12:01:17 +00:00
|
|
|
|
unique=False,
|
2016-02-25 09:59:50 +00:00
|
|
|
|
nullable=False)
|
|
|
|
|
|
sent_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True)
|
|
|
|
|
|
sent_by = db.Column(db.String, nullable=True)
|
2016-02-09 12:01:17 +00:00
|
|
|
|
updated_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=True,
|
2016-03-15 09:32:43 +00:00
|
|
|
|
onupdate=datetime.datetime.utcnow)
|
2017-07-06 14:20:24 +01:00
|
|
|
|
status = db.Column(
|
2017-05-04 17:09:04 +01:00
|
|
|
|
'notification_status',
|
2021-06-14 14:43:34 +01:00
|
|
|
|
db.Text,
|
2017-05-04 17:09:04 +01:00
|
|
|
|
db.ForeignKey('notification_status_types.name'),
|
|
|
|
|
|
nullable=True,
|
2017-07-06 14:20:24 +01:00
|
|
|
|
default='created',
|
|
|
|
|
|
key='status' # http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Column
|
2017-05-04 17:09:04 +01:00
|
|
|
|
)
|
2016-03-11 09:40:35 +00:00
|
|
|
|
reference = db.Column(db.String, nullable=True, index=True)
|
2016-11-17 13:42:34 +00:00
|
|
|
|
client_reference = db.Column(db.String, index=True, nullable=True)
|
2016-06-20 16:23:56 +01:00
|
|
|
|
_personalisation = db.Column(db.String, nullable=True)
|
|
|
|
|
|
|
2017-04-26 10:22:20 +01:00
|
|
|
|
client_reference = db.Column(db.String, index=True, nullable=True)
|
|
|
|
|
|
|
|
|
|
|
|
international = db.Column(db.Boolean, nullable=False, default=False)
|
|
|
|
|
|
phone_prefix = db.Column(db.String, nullable=True)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
rate_multiplier = db.Column(db.Numeric(asdecimal=False), nullable=True)
|
2017-04-26 10:22:20 +01:00
|
|
|
|
|
2017-06-13 15:33:33 +01:00
|
|
|
|
created_by = db.relationship('User')
|
|
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
|
|
|
|
|
|
|
2017-11-27 13:39:35 +00:00
|
|
|
|
reply_to_text = db.Column(db.String, nullable=True)
|
|
|
|
|
|
|
2020-02-12 14:38:09 +00:00
|
|
|
|
document_download_count = db.Column(db.Integer, nullable=True)
|
|
|
|
|
|
|
2018-09-19 10:49:11 +01:00
|
|
|
|
postage = db.Column(db.String, nullable=True)
|
|
|
|
|
|
|
2017-11-08 10:32:45 +00:00
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
db.ForeignKeyConstraint(
|
|
|
|
|
|
['template_id', 'template_version'],
|
|
|
|
|
|
['templates_history.id', 'templates_history.version'],
|
|
|
|
|
|
),
|
2021-06-14 14:43:34 +01:00
|
|
|
|
UniqueConstraint('job_id', 'job_row_number', name='uq_notifications_job_row_number'),
|
2021-06-21 12:06:38 +01:00
|
|
|
|
Index(
|
|
|
|
|
|
'ix_notifications_notification_type_composite',
|
|
|
|
|
|
'notification_type',
|
|
|
|
|
|
'status',
|
|
|
|
|
|
'created_at'
|
|
|
|
|
|
),
|
|
|
|
|
|
Index('ix_notifications_service_created_at', 'service_id', 'created_at'),
|
|
|
|
|
|
Index(
|
|
|
|
|
|
"ix_notifications_service_id_composite",
|
|
|
|
|
|
'service_id',
|
|
|
|
|
|
'notification_type',
|
|
|
|
|
|
'status',
|
|
|
|
|
|
'created_at'
|
|
|
|
|
|
)
|
2017-11-08 10:32:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2016-06-20 16:23:56 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def personalisation(self):
|
|
|
|
|
|
if self._personalisation:
|
|
|
|
|
|
return encryption.decrypt(self._personalisation)
|
2017-07-03 16:04:21 +01:00
|
|
|
|
return {}
|
2016-06-20 16:23:56 +01:00
|
|
|
|
|
|
|
|
|
|
@personalisation.setter
|
|
|
|
|
|
def personalisation(self, personalisation):
|
2017-07-03 16:04:21 +01:00
|
|
|
|
self._personalisation = encryption.encrypt(personalisation or {})
|
2016-02-23 16:21:47 +00:00
|
|
|
|
|
2016-11-18 17:36:11 +00:00
|
|
|
|
def completed_at(self):
|
2016-11-25 14:53:21 +00:00
|
|
|
|
if self.status in NOTIFICATION_STATUS_TYPES_COMPLETED:
|
2016-11-18 17:36:11 +00:00
|
|
|
|
return self.updated_at.strftime(DATETIME_FORMAT)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2016-11-25 14:55:29 +00:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def substitute_status(status_or_statuses):
|
|
|
|
|
|
"""
|
|
|
|
|
|
static function that takes a status or list of statuses and substitutes our new failure types if it finds
|
|
|
|
|
|
the deprecated one
|
|
|
|
|
|
|
|
|
|
|
|
> IN
|
|
|
|
|
|
'failed'
|
|
|
|
|
|
|
|
|
|
|
|
< OUT
|
|
|
|
|
|
['technical-failure', 'temporary-failure', 'permanent-failure']
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
> IN
|
2017-09-20 15:21:05 +01:00
|
|
|
|
['failed', 'created', 'accepted']
|
2016-11-25 14:55:29 +00:00
|
|
|
|
|
|
|
|
|
|
< OUT
|
2017-09-20 15:21:05 +01:00
|
|
|
|
['technical-failure', 'temporary-failure', 'permanent-failure', 'created', 'sending']
|
2016-11-25 14:55:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-10-23 15:57:00 +01:00
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
> IN
|
|
|
|
|
|
'delivered'
|
|
|
|
|
|
|
|
|
|
|
|
< OUT
|
|
|
|
|
|
['received']
|
|
|
|
|
|
|
2016-11-25 14:55:29 +00:00
|
|
|
|
:param status_or_statuses: a single status or list of statuses
|
|
|
|
|
|
:return: a single status or list with the current failure statuses substituted for 'failure'
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def _substitute_status_str(_status):
|
2017-09-20 15:21:05 +01:00
|
|
|
|
return (
|
|
|
|
|
|
NOTIFICATION_STATUS_TYPES_FAILED if _status == NOTIFICATION_FAILED else
|
|
|
|
|
|
[NOTIFICATION_CREATED, NOTIFICATION_SENDING] if _status == NOTIFICATION_STATUS_LETTER_ACCEPTED else
|
2017-10-23 15:57:00 +01:00
|
|
|
|
NOTIFICATION_DELIVERED if _status == NOTIFICATION_STATUS_LETTER_RECEIVED else
|
2017-09-20 15:21:05 +01:00
|
|
|
|
[_status]
|
|
|
|
|
|
)
|
2016-11-25 14:55:29 +00:00
|
|
|
|
|
|
|
|
|
|
def _substitute_status_seq(_statuses):
|
2017-09-20 15:21:05 +01:00
|
|
|
|
return list(set(itertools.chain.from_iterable(_substitute_status_str(status) for status in _statuses)))
|
2016-11-25 14:55:29 +00:00
|
|
|
|
|
|
|
|
|
|
if isinstance(status_or_statuses, str):
|
|
|
|
|
|
return _substitute_status_str(status_or_statuses)
|
|
|
|
|
|
return _substitute_status_seq(status_or_statuses)
|
|
|
|
|
|
|
2016-12-15 16:19:55 +00:00
|
|
|
|
@property
|
|
|
|
|
|
def content(self):
|
2020-04-13 13:48:23 +01:00
|
|
|
|
return self.template._as_utils_template_with_personalisation(
|
|
|
|
|
|
self.personalisation
|
|
|
|
|
|
).content_with_placeholders_filled_in
|
2016-12-15 16:19:55 +00:00
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def subject(self):
|
2020-04-13 13:48:23 +01:00
|
|
|
|
template_object = self.template._as_utils_template_with_personalisation(
|
|
|
|
|
|
self.personalisation
|
|
|
|
|
|
)
|
|
|
|
|
|
return getattr(template_object, 'subject', None)
|
2016-12-15 16:19:55 +00:00
|
|
|
|
|
2017-04-20 11:52:00 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def formatted_status(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'email': {
|
|
|
|
|
|
'failed': 'Failed',
|
|
|
|
|
|
'technical-failure': 'Technical failure',
|
|
|
|
|
|
'temporary-failure': 'Inbox not accepting messages right now',
|
|
|
|
|
|
'permanent-failure': 'Email address doesn’t exist',
|
|
|
|
|
|
'delivered': 'Delivered',
|
|
|
|
|
|
'sending': 'Sending',
|
2017-04-28 11:00:55 +01:00
|
|
|
|
'created': 'Sending',
|
|
|
|
|
|
'sent': 'Delivered'
|
2017-04-20 11:52:00 +01:00
|
|
|
|
},
|
|
|
|
|
|
'sms': {
|
|
|
|
|
|
'failed': 'Failed',
|
|
|
|
|
|
'technical-failure': 'Technical failure',
|
|
|
|
|
|
'temporary-failure': 'Phone not accepting messages right now',
|
|
|
|
|
|
'permanent-failure': 'Phone number doesn’t exist',
|
|
|
|
|
|
'delivered': 'Delivered',
|
|
|
|
|
|
'sending': 'Sending',
|
2017-04-28 11:00:55 +01:00
|
|
|
|
'created': 'Sending',
|
|
|
|
|
|
'sent': 'Sent internationally'
|
2017-04-20 11:52:00 +01:00
|
|
|
|
},
|
|
|
|
|
|
'letter': {
|
|
|
|
|
|
'technical-failure': 'Technical failure',
|
2021-06-15 15:12:46 +01:00
|
|
|
|
'permanent-failure': 'Permanent failure',
|
2017-10-25 15:38:58 +01:00
|
|
|
|
'sending': 'Accepted',
|
|
|
|
|
|
'created': 'Accepted',
|
2018-08-21 16:45:10 +01:00
|
|
|
|
'delivered': 'Received',
|
|
|
|
|
|
'returned-letter': 'Returned',
|
2017-04-20 11:52:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
}[self.template.template_type].get(self.status, self.status)
|
2016-11-18 17:36:11 +00:00
|
|
|
|
|
2017-09-08 16:35:13 +01:00
|
|
|
|
def get_letter_status(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Return the notification_status, as we should present for letters. The distinction between created and sending is
|
|
|
|
|
|
a bit more confusing for letters, not to mention that there's no concept of temporary or permanent failure yet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
# this should only ever be called for letter notifications - it makes no sense otherwise and I'd rather not
|
|
|
|
|
|
# get the two code flows mixed up at all
|
|
|
|
|
|
assert self.notification_type == LETTER_TYPE
|
|
|
|
|
|
|
2017-10-23 15:57:00 +01:00
|
|
|
|
if self.status in [NOTIFICATION_CREATED, NOTIFICATION_SENDING]:
|
2017-09-11 11:57:33 +01:00
|
|
|
|
return NOTIFICATION_STATUS_LETTER_ACCEPTED
|
2018-08-31 16:49:06 +01:00
|
|
|
|
elif self.status in [NOTIFICATION_DELIVERED, NOTIFICATION_RETURNED_LETTER]:
|
2017-10-23 15:57:00 +01:00
|
|
|
|
return NOTIFICATION_STATUS_LETTER_RECEIVED
|
2017-09-08 16:35:13 +01:00
|
|
|
|
else:
|
2019-09-03 16:49:03 +01:00
|
|
|
|
# Currently can only be technical-failure OR pending-virus-check OR validation-failed
|
2017-09-21 16:41:10 +01:00
|
|
|
|
return self.status
|
2017-09-08 16:35:13 +01:00
|
|
|
|
|
2018-07-16 13:12:17 +01:00
|
|
|
|
def get_created_by_name(self):
|
|
|
|
|
|
if self.created_by:
|
|
|
|
|
|
return self.created_by.name
|
|
|
|
|
|
else:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2018-12-06 11:53:54 +00:00
|
|
|
|
def get_created_by_email_address(self):
|
|
|
|
|
|
if self.created_by:
|
|
|
|
|
|
return self.created_by.email_address
|
|
|
|
|
|
else:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2017-04-20 11:52:00 +01:00
|
|
|
|
def serialize_for_csv(self):
|
2017-08-10 16:24:48 +01:00
|
|
|
|
created_at_in_bst = convert_utc_to_bst(self.created_at)
|
2017-04-20 11:52:00 +01:00
|
|
|
|
serialized = {
|
|
|
|
|
|
"row_number": '' if self.job_row_number is None else self.job_row_number + 1,
|
|
|
|
|
|
"recipient": self.to,
|
2020-01-07 12:19:41 +00:00
|
|
|
|
"client_reference": self.client_reference or '',
|
2017-04-20 11:52:00 +01:00
|
|
|
|
"template_name": self.template.name,
|
|
|
|
|
|
"template_type": self.template.template_type,
|
|
|
|
|
|
"job_name": self.job.original_file_name if self.job else '',
|
|
|
|
|
|
"status": self.formatted_status,
|
2018-12-06 15:57:22 +00:00
|
|
|
|
"created_at": created_at_in_bst.strftime("%Y-%m-%d %H:%M:%S"),
|
2018-09-07 10:22:45 +01:00
|
|
|
|
"created_by_name": self.get_created_by_name(),
|
2018-12-06 11:53:54 +00:00
|
|
|
|
"created_by_email_address": self.get_created_by_email_address(),
|
2017-04-20 11:52:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return serialized
|
|
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
2016-11-18 17:36:11 +00:00
|
|
|
|
template_dict = {
|
|
|
|
|
|
'version': self.template.version,
|
|
|
|
|
|
'id': self.template.id,
|
|
|
|
|
|
'uri': self.template.get_link()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
serialized = {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"reference": self.client_reference,
|
|
|
|
|
|
"email_address": self.to if self.notification_type == EMAIL_TYPE else None,
|
|
|
|
|
|
"phone_number": self.to if self.notification_type == SMS_TYPE else None,
|
|
|
|
|
|
"line_1": None,
|
|
|
|
|
|
"line_2": None,
|
|
|
|
|
|
"line_3": None,
|
|
|
|
|
|
"line_4": None,
|
|
|
|
|
|
"line_5": None,
|
|
|
|
|
|
"line_6": None,
|
|
|
|
|
|
"postcode": None,
|
|
|
|
|
|
"type": self.notification_type,
|
2017-09-08 16:35:13 +01:00
|
|
|
|
"status": self.get_letter_status() if self.notification_type == LETTER_TYPE else self.status,
|
2016-11-18 17:36:11 +00:00
|
|
|
|
"template": template_dict,
|
2016-12-15 16:19:55 +00:00
|
|
|
|
"body": self.content,
|
|
|
|
|
|
"subject": self.subject,
|
2016-11-18 17:36:11 +00:00
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2018-07-16 13:12:17 +01:00
|
|
|
|
"created_by_name": self.get_created_by_name(),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"sent_at": get_dt_string_or_none(self.sent_at),
|
2017-05-15 17:27:38 +01:00
|
|
|
|
"completed_at": self.completed_at(),
|
2020-06-24 07:34:58 +01:00
|
|
|
|
"scheduled_for": None,
|
2018-09-19 10:49:11 +01:00
|
|
|
|
"postage": self.postage
|
2016-11-18 17:36:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-01 18:23:29 +01:00
|
|
|
|
if self.notification_type == LETTER_TYPE:
|
2018-01-22 10:18:11 +00:00
|
|
|
|
col = Columns(self.personalisation)
|
|
|
|
|
|
serialized['line_1'] = col.get('address_line_1')
|
|
|
|
|
|
serialized['line_2'] = col.get('address_line_2')
|
|
|
|
|
|
serialized['line_3'] = col.get('address_line_3')
|
|
|
|
|
|
serialized['line_4'] = col.get('address_line_4')
|
|
|
|
|
|
serialized['line_5'] = col.get('address_line_5')
|
|
|
|
|
|
serialized['line_6'] = col.get('address_line_6')
|
2018-02-26 13:53:06 +00:00
|
|
|
|
serialized['postcode'] = col.get('postcode')
|
2017-09-11 14:16:04 +01:00
|
|
|
|
serialized['estimated_delivery'] = \
|
2018-09-28 17:30:25 +01:00
|
|
|
|
get_letter_timings(serialized['created_at'], postage=self.postage)\
|
2017-09-11 14:16:04 +01:00
|
|
|
|
.earliest_delivery\
|
|
|
|
|
|
.strftime(DATETIME_FORMAT)
|
2017-08-01 18:23:29 +01:00
|
|
|
|
|
2016-11-18 17:36:11 +00:00
|
|
|
|
return serialized
|
|
|
|
|
|
|
2016-02-23 16:21:47 +00:00
|
|
|
|
|
2016-12-15 17:11:47 +00:00
|
|
|
|
class NotificationHistory(db.Model, HistoryModel):
|
2016-07-07 16:30:22 +01:00
|
|
|
|
__tablename__ = 'notification_history'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True)
|
|
|
|
|
|
job_id = db.Column(UUID(as_uuid=True), db.ForeignKey('jobs.id'), index=True, unique=False)
|
|
|
|
|
|
job = db.relationship('Job')
|
|
|
|
|
|
job_row_number = db.Column(db.Integer, nullable=True)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False)
|
2016-07-07 16:30:22 +01:00
|
|
|
|
service = db.relationship('Service')
|
2021-06-14 14:43:34 +01:00
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), unique=False)
|
2016-07-07 16:30:22 +01:00
|
|
|
|
template_version = db.Column(db.Integer, nullable=False)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
api_key_id = db.Column(UUID(as_uuid=True), db.ForeignKey('api_keys.id'), unique=False)
|
2016-07-07 16:30:22 +01:00
|
|
|
|
api_key = db.relationship('ApiKey')
|
2021-06-14 14:43:34 +01:00
|
|
|
|
key_type = db.Column(db.String, db.ForeignKey('key_types.name'), unique=False, nullable=False)
|
2016-08-03 14:27:58 +01:00
|
|
|
|
billable_units = db.Column(db.Integer, nullable=False, default=0)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
notification_type = db.Column(notification_types, nullable=False)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, unique=False, nullable=False)
|
2016-07-07 16:30:22 +01:00
|
|
|
|
sent_at = db.Column(db.DateTime, index=False, unique=False, nullable=True)
|
|
|
|
|
|
sent_by = db.Column(db.String, nullable=True)
|
2018-09-13 14:09:09 +01:00
|
|
|
|
updated_at = db.Column(db.DateTime, index=False, unique=False, nullable=True, onupdate=datetime.datetime.utcnow)
|
2017-07-06 14:20:24 +01:00
|
|
|
|
status = db.Column(
|
2017-05-04 17:09:04 +01:00
|
|
|
|
'notification_status',
|
2021-06-14 14:43:34 +01:00
|
|
|
|
db.Text,
|
2017-05-04 17:09:04 +01:00
|
|
|
|
db.ForeignKey('notification_status_types.name'),
|
|
|
|
|
|
nullable=True,
|
2017-07-06 14:20:24 +01:00
|
|
|
|
default='created',
|
|
|
|
|
|
key='status' # http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Column
|
2017-05-04 17:09:04 +01:00
|
|
|
|
)
|
2016-07-07 16:30:22 +01:00
|
|
|
|
reference = db.Column(db.String, nullable=True, index=True)
|
2016-11-17 13:42:34 +00:00
|
|
|
|
client_reference = db.Column(db.String, nullable=True)
|
2016-07-07 16:30:22 +01:00
|
|
|
|
|
2021-06-14 14:43:34 +01:00
|
|
|
|
international = db.Column(db.Boolean, nullable=True, default=False)
|
2017-04-26 10:22:20 +01:00
|
|
|
|
phone_prefix = db.Column(db.String, nullable=True)
|
2021-06-14 14:43:34 +01:00
|
|
|
|
rate_multiplier = db.Column(db.Numeric(asdecimal=False), nullable=True)
|
2017-04-26 10:22:20 +01:00
|
|
|
|
|
2019-03-07 16:29:09 +00:00
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), nullable=True)
|
2017-06-23 15:56:47 +01:00
|
|
|
|
|
2018-09-20 12:26:58 +01:00
|
|
|
|
postage = db.Column(db.String, nullable=True)
|
2018-09-19 10:49:11 +01:00
|
|
|
|
|
2020-02-12 14:38:09 +00:00
|
|
|
|
document_download_count = db.Column(db.Integer, nullable=True)
|
|
|
|
|
|
|
2017-11-09 16:04:43 +00:00
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
db.ForeignKeyConstraint(
|
|
|
|
|
|
['template_id', 'template_version'],
|
|
|
|
|
|
['templates_history.id', 'templates_history.version'],
|
|
|
|
|
|
),
|
2021-06-21 12:06:38 +01:00
|
|
|
|
Index(
|
|
|
|
|
|
'ix_notification_history_service_id_composite',
|
|
|
|
|
|
'service_id',
|
|
|
|
|
|
'key_type',
|
|
|
|
|
|
'notification_type',
|
|
|
|
|
|
'created_at'
|
|
|
|
|
|
)
|
2017-11-09 16:04:43 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2016-07-08 16:19:34 +01:00
|
|
|
|
@classmethod
|
2016-12-15 17:11:47 +00:00
|
|
|
|
def from_original(cls, notification):
|
|
|
|
|
|
history = super().from_original(notification)
|
2017-05-04 17:09:04 +01:00
|
|
|
|
history.status = notification.status
|
2016-08-25 11:55:38 +01:00
|
|
|
|
return history
|
2016-07-08 16:19:34 +01:00
|
|
|
|
|
2017-05-09 11:09:16 +01:00
|
|
|
|
def update_from_original(self, original):
|
|
|
|
|
|
super().update_from_original(original)
|
|
|
|
|
|
self.status = original.status
|
|
|
|
|
|
|
2016-07-07 16:30:22 +01:00
|
|
|
|
|
2018-02-15 14:16:16 +00:00
|
|
|
|
INVITE_PENDING = 'pending'
|
|
|
|
|
|
INVITE_ACCEPTED = 'accepted'
|
|
|
|
|
|
INVITE_CANCELLED = 'cancelled'
|
|
|
|
|
|
INVITED_USER_STATUS_TYPES = [INVITE_PENDING, INVITE_ACCEPTED, INVITE_CANCELLED]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InviteStatusType(db.Model):
|
|
|
|
|
|
__tablename__ = 'invite_status_type'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String, primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-02-23 16:21:47 +00:00
|
|
|
|
class InvitedUser(db.Model):
|
|
|
|
|
|
__tablename__ = 'invited_users'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
email_address = db.Column(db.String(255), nullable=False)
|
2016-04-08 13:34:46 +01:00
|
|
|
|
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
2016-02-23 16:21:47 +00:00
|
|
|
|
from_user = db.relationship('User')
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False)
|
|
|
|
|
|
service = db.relationship('Service')
|
|
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-03-15 09:32:43 +00:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-02-23 16:21:47 +00:00
|
|
|
|
status = db.Column(
|
2018-02-15 14:16:16 +00:00
|
|
|
|
db.Enum(*INVITED_USER_STATUS_TYPES, name='invited_users_status_types'), nullable=False, default=INVITE_PENDING)
|
2016-02-29 09:49:12 +00:00
|
|
|
|
permissions = db.Column(db.String, nullable=False)
|
2017-10-27 17:59:51 +01:00
|
|
|
|
auth_type = db.Column(
|
|
|
|
|
|
db.String,
|
|
|
|
|
|
db.ForeignKey('auth_type.name'),
|
|
|
|
|
|
index=True,
|
|
|
|
|
|
nullable=False,
|
|
|
|
|
|
default=SMS_AUTH_TYPE
|
|
|
|
|
|
)
|
2019-03-20 10:29:42 +00:00
|
|
|
|
folder_permissions = db.Column(JSONB(none_as_null=True), nullable=False, default=[])
|
2016-02-29 09:49:12 +00:00
|
|
|
|
|
|
|
|
|
|
# would like to have used properties for this but haven't found a way to make them
|
|
|
|
|
|
# play nice with marshmallow yet
|
|
|
|
|
|
def get_permissions(self):
|
|
|
|
|
|
return self.permissions.split(',')
|
2016-02-26 12:00:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-02-15 14:16:16 +00:00
|
|
|
|
class InvitedOrganisationUser(db.Model):
|
|
|
|
|
|
__tablename__ = 'invited_organisation_users'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
email_address = db.Column(db.String(255), nullable=False)
|
|
|
|
|
|
invited_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=False)
|
|
|
|
|
|
invited_by = db.relationship('User')
|
|
|
|
|
|
organisation_id = db.Column(UUID(as_uuid=True), db.ForeignKey('organisation.id'), nullable=False)
|
|
|
|
|
|
organisation = db.relationship('Organisation')
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
status = db.Column(
|
|
|
|
|
|
db.String,
|
|
|
|
|
|
db.ForeignKey('invite_status_type.name'),
|
|
|
|
|
|
nullable=False,
|
|
|
|
|
|
default=INVITE_PENDING
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2018-02-16 10:56:12 +00:00
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
2018-02-20 17:09:16 +00:00
|
|
|
|
'id': str(self.id),
|
2018-02-16 10:56:12 +00:00
|
|
|
|
'email_address': self.email_address,
|
2018-02-20 17:09:16 +00:00
|
|
|
|
'invited_by': str(self.invited_by_id),
|
|
|
|
|
|
'organisation': str(self.organisation_id),
|
2018-02-16 10:56:12 +00:00
|
|
|
|
'created_at': self.created_at.strftime(DATETIME_FORMAT),
|
|
|
|
|
|
'status': self.status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-02-15 14:16:16 +00:00
|
|
|
|
|
2016-03-01 14:21:28 +00:00
|
|
|
|
# Service Permissions
|
2016-03-02 15:34:26 +00:00
|
|
|
|
MANAGE_USERS = 'manage_users'
|
2016-03-01 14:21:28 +00:00
|
|
|
|
MANAGE_TEMPLATES = 'manage_templates'
|
2016-03-02 15:34:26 +00:00
|
|
|
|
MANAGE_SETTINGS = 'manage_settings'
|
|
|
|
|
|
SEND_TEXTS = 'send_texts'
|
|
|
|
|
|
SEND_EMAILS = 'send_emails'
|
|
|
|
|
|
SEND_LETTERS = 'send_letters'
|
|
|
|
|
|
MANAGE_API_KEYS = 'manage_api_keys'
|
2016-03-17 10:37:24 +00:00
|
|
|
|
PLATFORM_ADMIN = 'platform_admin'
|
2016-03-29 15:35:34 +01:00
|
|
|
|
VIEW_ACTIVITY = 'view_activity'
|
2021-06-16 10:43:19 +01:00
|
|
|
|
CREATE_BROADCASTS = 'create_broadcasts'
|
|
|
|
|
|
APPROVE_BROADCASTS = 'approve_broadcasts'
|
|
|
|
|
|
CANCEL_BROADCASTS = 'cancel_broadcasts'
|
|
|
|
|
|
REJECT_BROADCASTS = 'reject_broadcasts'
|
2016-03-01 14:21:28 +00:00
|
|
|
|
|
|
|
|
|
|
# List of permissions
|
|
|
|
|
|
PERMISSION_LIST = [
|
2016-03-02 15:34:26 +00:00
|
|
|
|
MANAGE_USERS,
|
2016-03-01 17:18:46 +00:00
|
|
|
|
MANAGE_TEMPLATES,
|
2016-03-02 15:34:26 +00:00
|
|
|
|
MANAGE_SETTINGS,
|
|
|
|
|
|
SEND_TEXTS,
|
|
|
|
|
|
SEND_EMAILS,
|
|
|
|
|
|
SEND_LETTERS,
|
|
|
|
|
|
MANAGE_API_KEYS,
|
2016-03-29 15:35:34 +01:00
|
|
|
|
PLATFORM_ADMIN,
|
2021-06-16 10:43:19 +01:00
|
|
|
|
VIEW_ACTIVITY,
|
|
|
|
|
|
CREATE_BROADCASTS,
|
|
|
|
|
|
APPROVE_BROADCASTS,
|
|
|
|
|
|
CANCEL_BROADCASTS,
|
|
|
|
|
|
REJECT_BROADCASTS,
|
|
|
|
|
|
]
|
2016-03-01 14:21:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
2016-02-26 12:00:16 +00:00
|
|
|
|
class Permission(db.Model):
|
|
|
|
|
|
__tablename__ = 'permissions'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
# Service id is optional, if the service is omitted we will assume the permission is not service specific.
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False, nullable=True)
|
|
|
|
|
|
service = db.relationship('Service')
|
2016-04-08 13:34:46 +01:00
|
|
|
|
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
|
2016-02-26 12:00:16 +00:00
|
|
|
|
user = db.relationship('User')
|
2016-03-01 17:18:46 +00:00
|
|
|
|
permission = db.Column(
|
|
|
|
|
|
db.Enum(*PERMISSION_LIST, name='permission_types'),
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False)
|
2016-02-26 12:00:16 +00:00
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
2016-03-15 09:32:43 +00:00
|
|
|
|
default=datetime.datetime.utcnow)
|
2016-02-26 12:00:16 +00:00
|
|
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
UniqueConstraint('service_id', 'user_id', 'permission', name='uix_service_user_permission'),
|
|
|
|
|
|
)
|
2016-03-31 15:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
|
2016-04-27 10:27:05 +01:00
|
|
|
|
class Event(db.Model):
|
|
|
|
|
|
__tablename__ = 'events'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
event_type = db.Column(db.String(255), nullable=False)
|
|
|
|
|
|
created_at = db.Column(
|
|
|
|
|
|
db.DateTime,
|
|
|
|
|
|
index=False,
|
|
|
|
|
|
unique=False,
|
|
|
|
|
|
nullable=False,
|
|
|
|
|
|
default=datetime.datetime.utcnow)
|
|
|
|
|
|
data = db.Column(JSON, nullable=False)
|
2017-04-24 16:20:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-04-25 09:53:43 +01:00
|
|
|
|
class Rate(db.Model):
|
2017-04-24 16:20:03 +01:00
|
|
|
|
__tablename__ = 'rates'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
valid_from = db.Column(db.DateTime, nullable=False)
|
2017-04-27 15:43:57 +01:00
|
|
|
|
rate = db.Column(db.Float(asdecimal=False), nullable=False)
|
2017-04-24 16:20:03 +01:00
|
|
|
|
notification_type = db.Column(notification_types, index=True, nullable=False)
|
2017-05-09 11:22:05 +01:00
|
|
|
|
|
2017-05-24 08:57:11 +01:00
|
|
|
|
def __str__(self):
|
|
|
|
|
|
the_string = "{}".format(self.rate)
|
|
|
|
|
|
the_string += " {}".format(self.notification_type)
|
|
|
|
|
|
the_string += " {}".format(self.valid_from)
|
|
|
|
|
|
return the_string
|
|
|
|
|
|
|
2017-05-09 11:22:05 +01:00
|
|
|
|
|
2017-05-22 11:26:47 +01:00
|
|
|
|
class InboundSms(db.Model):
|
|
|
|
|
|
__tablename__ = 'inbound_sms'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
|
|
|
|
|
|
service = db.relationship('Service', backref='inbound_sms')
|
|
|
|
|
|
|
|
|
|
|
|
notify_number = db.Column(db.String, nullable=False) # the service's number, that the msg was sent to
|
2017-07-10 14:43:46 +01:00
|
|
|
|
user_number = db.Column(db.String, nullable=False, index=True) # the end user's number, that the msg was sent from
|
2017-05-22 11:26:47 +01:00
|
|
|
|
provider_date = db.Column(db.DateTime)
|
|
|
|
|
|
provider_reference = db.Column(db.String)
|
2017-06-02 16:51:27 +01:00
|
|
|
|
provider = db.Column(db.String, nullable=False)
|
2017-05-22 11:26:47 +01:00
|
|
|
|
_content = db.Column('content', db.String, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def content(self):
|
|
|
|
|
|
return encryption.decrypt(self._content)
|
|
|
|
|
|
|
|
|
|
|
|
@content.setter
|
|
|
|
|
|
def content(self, content):
|
|
|
|
|
|
self._content = encryption.encrypt(content)
|
2017-06-02 14:47:28 +01:00
|
|
|
|
|
2017-05-31 14:49:14 +01:00
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': str(self.id),
|
2017-11-03 16:35:22 +00:00
|
|
|
|
'created_at': self.created_at.strftime(DATETIME_FORMAT),
|
2017-05-31 14:49:14 +01:00
|
|
|
|
'service_id': str(self.service_id),
|
|
|
|
|
|
'notify_number': self.notify_number,
|
|
|
|
|
|
'user_number': self.user_number,
|
|
|
|
|
|
'content': self.content,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-06-02 14:47:28 +01:00
|
|
|
|
|
2019-12-16 17:54:47 +00:00
|
|
|
|
class InboundSmsHistory(db.Model, HistoryModel):
|
|
|
|
|
|
__tablename__ = 'inbound_sms_history'
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True)
|
2019-12-20 12:34:59 +00:00
|
|
|
|
created_at = db.Column(db.DateTime, index=True, unique=False, nullable=False)
|
2019-12-16 17:54:47 +00:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False)
|
2019-12-20 12:34:59 +00:00
|
|
|
|
service = db.relationship('Service')
|
2019-12-16 17:54:47 +00:00
|
|
|
|
notify_number = db.Column(db.String, nullable=False)
|
|
|
|
|
|
provider_date = db.Column(db.DateTime)
|
|
|
|
|
|
provider_reference = db.Column(db.String)
|
|
|
|
|
|
provider = db.Column(db.String, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-05-31 13:34:54 +01:00
|
|
|
|
class LetterRate(db.Model):
|
|
|
|
|
|
__tablename__ = 'letter_rates'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2017-12-05 10:32:19 +00:00
|
|
|
|
start_date = db.Column(db.DateTime, nullable=False)
|
|
|
|
|
|
end_date = db.Column(db.DateTime, nullable=True)
|
2017-12-06 16:40:38 +00:00
|
|
|
|
sheet_count = db.Column(db.Integer, nullable=False) # double sided sheet
|
2017-05-31 13:34:54 +01:00
|
|
|
|
rate = db.Column(db.Numeric(), nullable=False)
|
2017-12-05 10:32:19 +00:00
|
|
|
|
crown = db.Column(db.Boolean, nullable=False)
|
|
|
|
|
|
post_class = db.Column(db.String, nullable=False)
|
2017-07-13 17:22:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-09-07 15:41:23 +01:00
|
|
|
|
class ServiceEmailReplyTo(db.Model):
|
|
|
|
|
|
__tablename__ = "service_email_reply_to"
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
2017-09-12 09:50:52 +01:00
|
|
|
|
service = db.relationship(Service, backref=db.backref("reply_to_email_addresses"))
|
2017-09-07 15:41:23 +01:00
|
|
|
|
|
|
|
|
|
|
email_address = db.Column(db.Text, nullable=False, index=False, unique=False)
|
|
|
|
|
|
is_default = db.Column(db.Boolean, nullable=False, default=True)
|
2018-04-25 10:42:00 +01:00
|
|
|
|
archived = db.Column(db.Boolean, nullable=False, default=False)
|
2017-09-07 15:41:23 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2017-09-13 15:27:00 +01:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
2017-09-21 10:21:32 +01:00
|
|
|
|
'id': str(self.id),
|
|
|
|
|
|
'service_id': str(self.service_id),
|
2017-09-13 15:27:00 +01:00
|
|
|
|
'email_address': self.email_address,
|
|
|
|
|
|
'is_default': self.is_default,
|
2018-04-25 10:42:00 +01:00
|
|
|
|
'archived': self.archived,
|
2017-09-14 17:54:38 +01:00
|
|
|
|
'created_at': self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'updated_at': get_dt_string_or_none(self.updated_at),
|
2017-09-13 15:27:00 +01:00
|
|
|
|
}
|
2017-09-21 16:08:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceLetterContact(db.Model):
|
|
|
|
|
|
__tablename__ = "service_letter_contacts"
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
|
|
|
|
|
service = db.relationship(Service, backref=db.backref("letter_contacts"))
|
|
|
|
|
|
|
|
|
|
|
|
contact_block = db.Column(db.Text, nullable=False, index=False, unique=False)
|
|
|
|
|
|
is_default = db.Column(db.Boolean, nullable=False, default=True)
|
2018-04-25 10:42:00 +01:00
|
|
|
|
archived = db.Column(db.Boolean, nullable=False, default=False)
|
2017-09-21 16:08:49 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
2017-09-21 16:38:24 +01:00
|
|
|
|
'id': str(self.id),
|
|
|
|
|
|
'service_id': str(self.service_id),
|
2017-09-21 16:08:49 +01:00
|
|
|
|
'contact_block': self.contact_block,
|
|
|
|
|
|
'is_default': self.is_default,
|
2018-04-25 10:42:00 +01:00
|
|
|
|
'archived': self.archived,
|
2017-09-21 16:08:49 +01:00
|
|
|
|
'created_at': self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'updated_at': get_dt_string_or_none(self.updated_at),
|
2017-09-21 16:08:49 +01:00
|
|
|
|
}
|
2017-09-27 10:36:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-10-27 17:59:51 +01:00
|
|
|
|
class AuthType(db.Model):
|
|
|
|
|
|
__tablename__ = 'auth_type'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String, primary_key=True)
|
2017-11-07 14:50:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-02-15 14:27:38 +00:00
|
|
|
|
class DailySortedLetter(db.Model):
|
|
|
|
|
|
__tablename__ = "daily_sorted_letter"
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2018-03-14 17:04:58 +00:00
|
|
|
|
billing_day = db.Column(db.Date, nullable=False, index=True)
|
|
|
|
|
|
file_name = db.Column(db.String, nullable=True, index=True)
|
2018-02-15 14:27:38 +00:00
|
|
|
|
unsorted_count = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
|
|
sorted_count = db.Column(db.Integer, nullable=False, default=0)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2018-03-12 18:19:26 +00:00
|
|
|
|
|
2018-03-14 17:04:58 +00:00
|
|
|
|
__table_args__ = (UniqueConstraint('file_name', 'billing_day', name='uix_file_name_billing_day'),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2018-03-12 18:19:26 +00:00
|
|
|
|
|
|
|
|
|
|
class FactBilling(db.Model):
|
|
|
|
|
|
__tablename__ = "ft_billing"
|
|
|
|
|
|
|
|
|
|
|
|
bst_date = db.Column(db.Date, nullable=False, primary_key=True, index=True)
|
2018-03-14 14:47:30 +00:00
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), nullable=False, primary_key=True, index=True)
|
2018-09-24 14:20:39 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), nullable=False, primary_key=True, index=True)
|
2018-03-21 14:14:16 +00:00
|
|
|
|
notification_type = db.Column(db.Text, nullable=False, primary_key=True)
|
2018-09-24 14:20:39 +01:00
|
|
|
|
provider = db.Column(db.Text, nullable=False, primary_key=True)
|
|
|
|
|
|
rate_multiplier = db.Column(db.Integer(), nullable=False, primary_key=True)
|
|
|
|
|
|
international = db.Column(db.Boolean, nullable=False, primary_key=True)
|
|
|
|
|
|
rate = db.Column(db.Numeric(), nullable=False, primary_key=True)
|
2018-09-26 15:09:13 +01:00
|
|
|
|
postage = db.Column(db.String, nullable=False, primary_key=True)
|
2018-05-10 15:35:58 +01:00
|
|
|
|
billable_units = db.Column(db.Integer(), nullable=True)
|
2018-03-12 18:19:26 +00:00
|
|
|
|
notifications_sent = db.Column(db.Integer(), nullable=True)
|
2018-05-22 14:49:48 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2018-05-22 14:19:51 +01:00
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2018-03-12 18:19:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-05-09 14:06:46 +01:00
|
|
|
|
class FactNotificationStatus(db.Model):
|
|
|
|
|
|
__tablename__ = "ft_notification_status"
|
|
|
|
|
|
|
|
|
|
|
|
bst_date = db.Column(db.Date, index=True, primary_key=True, nullable=False)
|
|
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), primary_key=True, index=True, nullable=False)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), primary_key=True, index=True, nullable=False, )
|
|
|
|
|
|
job_id = db.Column(UUID(as_uuid=True), primary_key=True, index=True, nullable=False)
|
|
|
|
|
|
notification_type = db.Column(db.Text, primary_key=True, nullable=False)
|
|
|
|
|
|
key_type = db.Column(db.Text, primary_key=True, nullable=False)
|
|
|
|
|
|
notification_status = db.Column(db.Text, primary_key=True, nullable=False)
|
|
|
|
|
|
notification_count = db.Column(db.Integer(), nullable=False)
|
2018-05-22 16:25:07 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2018-05-31 14:43:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
2021-02-22 15:42:29 +00:00
|
|
|
|
class FactProcessingTime(db.Model):
|
|
|
|
|
|
__tablename__ = "ft_processing_time"
|
|
|
|
|
|
|
|
|
|
|
|
bst_date = db.Column(db.Date, index=True, primary_key=True, nullable=False)
|
|
|
|
|
|
messages_total = db.Column(db.Integer(), nullable=False)
|
|
|
|
|
|
messages_within_10_secs = db.Column(db.Integer(), nullable=False)
|
2021-02-23 14:24:46 +00:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2021-02-22 15:42:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-05-31 14:43:49 +01:00
|
|
|
|
class Complaint(db.Model):
|
|
|
|
|
|
__tablename__ = 'complaints'
|
2018-06-05 14:25:24 +01:00
|
|
|
|
|
2018-05-31 14:43:49 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2019-12-09 12:19:18 +00:00
|
|
|
|
notification_id = db.Column(UUID(as_uuid=True), index=True, nullable=False)
|
2018-05-31 14:43:49 +01:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
|
|
|
|
|
service = db.relationship(Service, backref=db.backref('complaints'))
|
|
|
|
|
|
ses_feedback_id = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
complaint_type = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
complaint_date = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2018-06-05 14:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': str(self.id),
|
|
|
|
|
|
'notification_id': str(self.notification_id),
|
|
|
|
|
|
'service_id': str(self.service_id),
|
|
|
|
|
|
'service_name': self.service.name,
|
|
|
|
|
|
'ses_feedback_id': str(self.ses_feedback_id),
|
|
|
|
|
|
'complaint_type': self.complaint_type,
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'complaint_date': get_dt_string_or_none(self.complaint_date),
|
2018-06-05 14:25:24 +01:00
|
|
|
|
'created_at': self.created_at.strftime(DATETIME_FORMAT),
|
|
|
|
|
|
}
|
2018-07-10 11:35:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceDataRetention(db.Model):
|
|
|
|
|
|
__tablename__ = 'service_data_retention'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
2019-08-15 15:03:49 +01:00
|
|
|
|
service = db.relationship(
|
|
|
|
|
|
Service,
|
|
|
|
|
|
backref=db.backref(
|
|
|
|
|
|
'data_retention',
|
|
|
|
|
|
collection_class=attribute_mapped_collection('notification_type')
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2018-07-10 11:35:20 +01:00
|
|
|
|
notification_type = db.Column(notification_types, nullable=False)
|
|
|
|
|
|
days_of_retention = db.Column(db.Integer, nullable=False)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2018-07-10 13:54:44 +01:00
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
UniqueConstraint('service_id', 'notification_type', name='uix_service_data_retention'),
|
|
|
|
|
|
)
|
2018-07-11 17:02:49 +01:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"service_id": str(self.service_id),
|
|
|
|
|
|
"service_name": self.service.name,
|
|
|
|
|
|
"notification_type": self.notification_type,
|
|
|
|
|
|
"days_of_retention": self.days_of_retention,
|
|
|
|
|
|
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
2020-07-27 15:17:19 +01:00
|
|
|
|
"updated_at": get_dt_string_or_none(self.updated_at),
|
2018-07-11 17:02:49 +01:00
|
|
|
|
}
|
2019-12-09 12:19:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReturnedLetter(db.Model):
|
|
|
|
|
|
__tablename__ = 'returned_letters'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2019-12-12 14:11:54 +00:00
|
|
|
|
reported_at = db.Column(db.Date, nullable=False)
|
2019-12-09 12:19:18 +00:00
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
|
|
|
|
|
service = db.relationship(Service, backref=db.backref('returned_letters'))
|
|
|
|
|
|
notification_id = db.Column(UUID(as_uuid=True), unique=True, nullable=False)
|
2019-12-12 14:11:54 +00:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2020-03-12 13:53:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceContactList(db.Model):
|
|
|
|
|
|
__tablename__ = 'service_contact_list'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
original_file_name = db.Column(db.String, nullable=False)
|
|
|
|
|
|
row_count = db.Column(db.Integer, nullable=False)
|
|
|
|
|
|
template_type = db.Column(template_types, nullable=False)
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), unique=False, index=True, nullable=False)
|
|
|
|
|
|
service = db.relationship(Service, backref=db.backref('contact_list'))
|
|
|
|
|
|
created_by = db.relationship('User')
|
|
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True)
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2020-03-26 11:38:04 +00:00
|
|
|
|
archived = db.Column(db.Boolean, nullable=False, default=False)
|
2020-03-12 13:53:57 +00:00
|
|
|
|
|
2020-07-21 15:12:44 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def job_count(self):
|
2020-05-12 11:53:36 +01:00
|
|
|
|
today = datetime.datetime.utcnow().date()
|
|
|
|
|
|
return Job.query.filter(
|
|
|
|
|
|
Job.contact_list_id == self.id,
|
|
|
|
|
|
func.coalesce(
|
|
|
|
|
|
Job.processing_started, Job.created_at
|
|
|
|
|
|
) >= today - func.coalesce(ServiceDataRetention.days_of_retention, 7)
|
|
|
|
|
|
).outerjoin(
|
|
|
|
|
|
ServiceDataRetention, and_(
|
|
|
|
|
|
self.service_id == ServiceDataRetention.service_id,
|
|
|
|
|
|
func.cast(self.template_type, String) == func.cast(ServiceDataRetention.notification_type, String)
|
|
|
|
|
|
)
|
|
|
|
|
|
).count()
|
|
|
|
|
|
|
2020-07-21 15:12:44 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def has_jobs(self):
|
2020-05-28 13:22:47 +01:00
|
|
|
|
return bool(Job.query.filter(
|
|
|
|
|
|
Job.contact_list_id == self.id,
|
|
|
|
|
|
).first())
|
|
|
|
|
|
|
2020-03-12 13:53:57 +00:00
|
|
|
|
def serialize(self):
|
|
|
|
|
|
created_at_in_bst = convert_utc_to_bst(self.created_at)
|
|
|
|
|
|
contact_list = {
|
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
|
"original_file_name": self.original_file_name,
|
|
|
|
|
|
"row_count": self.row_count,
|
2020-07-21 15:12:44 +01:00
|
|
|
|
"recent_job_count": self.job_count,
|
|
|
|
|
|
"has_jobs": self.has_jobs,
|
2020-03-12 13:53:57 +00:00
|
|
|
|
"template_type": self.template_type,
|
|
|
|
|
|
"service_id": str(self.service_id),
|
|
|
|
|
|
"created_by": self.created_by.name,
|
|
|
|
|
|
"created_at": created_at_in_bst.strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
|
}
|
|
|
|
|
|
return contact_list
|
2020-07-02 12:22:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastStatusType(db.Model):
|
|
|
|
|
|
__tablename__ = 'broadcast_status_type'
|
|
|
|
|
|
DRAFT = 'draft'
|
|
|
|
|
|
PENDING_APPROVAL = 'pending-approval'
|
|
|
|
|
|
REJECTED = 'rejected'
|
|
|
|
|
|
BROADCASTING = 'broadcasting'
|
|
|
|
|
|
COMPLETED = 'completed'
|
|
|
|
|
|
CANCELLED = 'cancelled'
|
|
|
|
|
|
TECHNICAL_FAILURE = 'technical-failure'
|
|
|
|
|
|
|
|
|
|
|
|
STATUSES = [DRAFT, PENDING_APPROVAL, REJECTED, BROADCASTING, COMPLETED, CANCELLED, TECHNICAL_FAILURE]
|
|
|
|
|
|
|
2020-07-10 15:30:25 +01:00
|
|
|
|
# a broadcast message can be edited while in one of these states
|
|
|
|
|
|
PRE_BROADCAST_STATUSES = [DRAFT, PENDING_APPROVAL, REJECTED]
|
|
|
|
|
|
LIVE_STATUSES = [BROADCASTING, COMPLETED, CANCELLED]
|
|
|
|
|
|
|
2020-07-16 16:02:04 +01:00
|
|
|
|
# these are only the transitions we expect to administer via the API code.
|
|
|
|
|
|
ALLOWED_STATUS_TRANSITIONS = {
|
2021-06-15 17:18:54 +01:00
|
|
|
|
DRAFT: {PENDING_APPROVAL},
|
2020-07-16 16:02:04 +01:00
|
|
|
|
PENDING_APPROVAL: {REJECTED, DRAFT, BROADCASTING},
|
|
|
|
|
|
REJECTED: {DRAFT, PENDING_APPROVAL},
|
|
|
|
|
|
BROADCASTING: {COMPLETED, CANCELLED},
|
|
|
|
|
|
COMPLETED: {},
|
|
|
|
|
|
CANCELLED: {},
|
|
|
|
|
|
TECHNICAL_FAILURE: {},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-02 12:22:15 +01:00
|
|
|
|
name = db.Column(db.String, primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastMessage(db.Model):
|
2020-10-26 16:04:39 +00:00
|
|
|
|
"""
|
|
|
|
|
|
This is for creating a message, viewing it in notify, adding areas, approvals, drafts, etc. Notify logic before
|
|
|
|
|
|
hitting send.
|
|
|
|
|
|
"""
|
2020-07-02 12:22:15 +01:00
|
|
|
|
__tablename__ = 'broadcast_message'
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
db.ForeignKeyConstraint(
|
|
|
|
|
|
['template_id', 'template_version'],
|
|
|
|
|
|
['templates_history.id', 'templates_history.version'],
|
|
|
|
|
|
),
|
|
|
|
|
|
{}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-07-07 11:42:38 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
2020-07-02 12:22:15 +01:00
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'))
|
|
|
|
|
|
service = db.relationship('Service', backref='broadcast_messages')
|
|
|
|
|
|
|
2021-01-08 16:38:51 +00:00
|
|
|
|
template_id = db.Column(UUID(as_uuid=True), nullable=True)
|
|
|
|
|
|
template_version = db.Column(db.Integer, nullable=True)
|
2020-07-02 12:22:15 +01:00
|
|
|
|
template = db.relationship('TemplateHistory', backref='broadcast_messages')
|
|
|
|
|
|
|
|
|
|
|
|
_personalisation = db.Column(db.String, nullable=True)
|
2021-01-08 16:38:51 +00:00
|
|
|
|
content = db.Column(db.String, nullable=False)
|
2020-07-07 11:42:38 +01:00
|
|
|
|
# defaults to empty list
|
|
|
|
|
|
areas = db.Column(JSONB(none_as_null=True), nullable=False, default=list)
|
2020-07-02 12:22:15 +01:00
|
|
|
|
|
|
|
|
|
|
status = db.Column(
|
|
|
|
|
|
db.String,
|
|
|
|
|
|
db.ForeignKey('broadcast_status_type.name'),
|
|
|
|
|
|
nullable=False,
|
|
|
|
|
|
default=BroadcastStatusType.DRAFT
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# these times are related to the actual broadcast, rather than auditing purposes
|
|
|
|
|
|
starts_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
finishes_at = db.Column(db.DateTime, nullable=True) # isn't updated if user cancels
|
|
|
|
|
|
|
|
|
|
|
|
# these times correspond to when
|
2020-07-07 11:42:38 +01:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2020-07-02 12:22:15 +01:00
|
|
|
|
approved_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
cancelled_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
|
2021-01-13 11:21:42 +00:00
|
|
|
|
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
|
2020-07-02 12:22:15 +01:00
|
|
|
|
approved_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
|
|
|
|
|
|
cancelled_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=True)
|
|
|
|
|
|
|
|
|
|
|
|
created_by = db.relationship('User', foreign_keys=[created_by_id])
|
|
|
|
|
|
approved_by = db.relationship('User', foreign_keys=[approved_by_id])
|
|
|
|
|
|
cancelled_by = db.relationship('User', foreign_keys=[cancelled_by_id])
|
2020-07-07 11:42:38 +01:00
|
|
|
|
|
2021-01-13 11:21:42 +00:00
|
|
|
|
api_key_id = db.Column(UUID(as_uuid=True), db.ForeignKey('api_keys.id'), nullable=True)
|
|
|
|
|
|
api_key = db.relationship('ApiKey')
|
|
|
|
|
|
|
|
|
|
|
|
reference = db.Column(db.String(255), nullable=True)
|
|
|
|
|
|
|
2021-02-08 18:22:25 +00:00
|
|
|
|
stubbed = db.Column(db.Boolean, nullable=False)
|
2021-01-26 16:50:34 +00:00
|
|
|
|
|
2021-01-13 11:21:42 +00:00
|
|
|
|
CheckConstraint("created_by_id is not null or api_key_id is not null")
|
|
|
|
|
|
|
2020-07-07 11:42:38 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def personalisation(self):
|
|
|
|
|
|
if self._personalisation:
|
|
|
|
|
|
return encryption.decrypt(self._personalisation)
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
@personalisation.setter
|
|
|
|
|
|
def personalisation(self, personalisation):
|
|
|
|
|
|
self._personalisation = encryption.encrypt(personalisation or {})
|
|
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
2021-08-27 13:22:54 +01:00
|
|
|
|
areas = dict(self.areas)
|
|
|
|
|
|
areas["simple_polygons"] = areas.get("simple_polygons", [])
|
2021-08-25 13:49:18 +01:00
|
|
|
|
|
2020-07-07 11:42:38 +01:00
|
|
|
|
return {
|
2020-07-09 18:22:29 +01:00
|
|
|
|
'id': str(self.id),
|
2021-01-13 11:21:42 +00:00
|
|
|
|
'reference': self.reference,
|
2020-07-07 11:42:38 +01:00
|
|
|
|
|
2020-07-09 18:22:29 +01:00
|
|
|
|
'service_id': str(self.service_id),
|
2020-07-07 11:42:38 +01:00
|
|
|
|
|
2021-01-08 16:38:51 +00:00
|
|
|
|
'template_id': str(self.template_id) if self.template else None,
|
2020-07-07 11:42:38 +01:00
|
|
|
|
'template_version': self.template_version,
|
2021-01-08 16:38:51 +00:00
|
|
|
|
'template_name': self.template.name if self.template else None,
|
|
|
|
|
|
'personalisation': self.personalisation if self.template else None,
|
2020-10-08 11:39:17 +01:00
|
|
|
|
'content': self.content,
|
2020-07-07 11:42:38 +01:00
|
|
|
|
|
2021-08-27 13:22:54 +01:00
|
|
|
|
'areas': areas,
|
2020-07-07 11:42:38 +01:00
|
|
|
|
'status': self.status,
|
|
|
|
|
|
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'starts_at': get_dt_string_or_none(self.starts_at),
|
|
|
|
|
|
'finishes_at': get_dt_string_or_none(self.finishes_at),
|
2020-07-07 11:42:38 +01:00
|
|
|
|
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'created_at': get_dt_string_or_none(self.created_at),
|
|
|
|
|
|
'approved_at': get_dt_string_or_none(self.approved_at),
|
|
|
|
|
|
'cancelled_at': get_dt_string_or_none(self.cancelled_at),
|
|
|
|
|
|
'updated_at': get_dt_string_or_none(self.updated_at),
|
2020-07-07 11:42:38 +01:00
|
|
|
|
|
2021-01-15 13:15:00 +00:00
|
|
|
|
'created_by_id': get_uuid_string_or_none(self.created_by_id),
|
|
|
|
|
|
'approved_by_id': get_uuid_string_or_none(self.approved_by_id),
|
|
|
|
|
|
'cancelled_by_id': get_uuid_string_or_none(self.cancelled_by_id),
|
2020-07-07 11:42:38 +01:00
|
|
|
|
}
|
2020-07-24 12:46:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastEventMessageType:
|
|
|
|
|
|
ALERT = 'alert'
|
|
|
|
|
|
UPDATE = 'update'
|
|
|
|
|
|
CANCEL = 'cancel'
|
|
|
|
|
|
|
|
|
|
|
|
MESSAGE_TYPES = [ALERT, UPDATE, CANCEL]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastEvent(db.Model):
|
|
|
|
|
|
"""
|
2020-10-26 16:04:39 +00:00
|
|
|
|
This table represents an instruction that we will send to the broadcast providers. It directly correlates with an
|
|
|
|
|
|
instruction from the admin - to broadcast a message, to cancel an existing message, or to update an existing one.
|
2020-07-24 12:46:28 +01:00
|
|
|
|
|
|
|
|
|
|
We should be able to create the complete CAP message without joining from this to any other tables, eg
|
|
|
|
|
|
template, service, or broadcast_message.
|
|
|
|
|
|
|
|
|
|
|
|
The only exception to this is that we will have to join to itself to find other broadcast_events with the
|
|
|
|
|
|
same broadcast_message_id when building up the `<references>` xml field for updating/cancelling an existing message.
|
|
|
|
|
|
|
|
|
|
|
|
As such, this shouldn't have foreign keys to things that can change or be deleted.
|
|
|
|
|
|
"""
|
|
|
|
|
|
__tablename__ = 'broadcast_event'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'))
|
|
|
|
|
|
service = db.relationship('Service')
|
|
|
|
|
|
|
|
|
|
|
|
broadcast_message_id = db.Column(UUID(as_uuid=True), db.ForeignKey('broadcast_message.id'), nullable=False)
|
|
|
|
|
|
broadcast_message = db.relationship('BroadcastMessage', backref='events')
|
|
|
|
|
|
|
|
|
|
|
|
# this is used for <sent> in the cap xml
|
|
|
|
|
|
sent_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
# msgType. alert, cancel, or update. (other options in the spec are "ack" and "error")
|
|
|
|
|
|
message_type = db.Column(db.String, nullable=False)
|
|
|
|
|
|
|
2020-07-27 15:43:27 +01:00
|
|
|
|
# this will be json containing anything that isnt hardcoded in utils/cbc proxy. for now just body but may grow to
|
|
|
|
|
|
# include, eg, title, headline, instructions.
|
2020-07-24 12:46:28 +01:00
|
|
|
|
transmitted_content = db.Column(
|
|
|
|
|
|
JSONB(none_as_null=True),
|
2020-07-27 15:43:27 +01:00
|
|
|
|
nullable=True
|
2020-07-24 12:46:28 +01:00
|
|
|
|
)
|
|
|
|
|
|
# unsubstantiated reckon: even if we're sending a cancel, we'll still need to provide areas
|
|
|
|
|
|
transmitted_areas = db.Column(JSONB(none_as_null=True), nullable=False, default=list)
|
|
|
|
|
|
transmitted_sender = db.Column(db.String(), nullable=False)
|
|
|
|
|
|
|
2020-07-27 15:43:27 +01:00
|
|
|
|
# we may only need this starts_at if this is scheduled for the future. Interested to see how this affects
|
|
|
|
|
|
# updates/cancels (ie: can you schedule an update for the future?)
|
2020-07-24 12:46:28 +01:00
|
|
|
|
transmitted_starts_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
transmitted_finishes_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
|
|
2020-08-04 19:00:10 +01:00
|
|
|
|
@property
|
|
|
|
|
|
def reference(self):
|
2020-10-26 17:39:11 +00:00
|
|
|
|
notify_email_domain = current_app.config['NOTIFY_EMAIL_DOMAIN']
|
|
|
|
|
|
return (
|
|
|
|
|
|
f'https://www.{notify_email_domain}/,'
|
|
|
|
|
|
f'{self.id},'
|
|
|
|
|
|
f'{self.sent_at_as_cap_datetime_string}'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def sent_at_as_cap_datetime_string(self):
|
|
|
|
|
|
return self.formatted_datetime_for('sent_at')
|
|
|
|
|
|
|
2020-10-28 11:26:38 +00:00
|
|
|
|
@property
|
|
|
|
|
|
def transmitted_finishes_at_as_cap_datetime_string(self):
|
|
|
|
|
|
return self.formatted_datetime_for('transmitted_finishes_at')
|
|
|
|
|
|
|
2020-10-26 17:39:11 +00:00
|
|
|
|
def formatted_datetime_for(self, property_name):
|
|
|
|
|
|
return self.convert_naive_utc_datetime_to_cap_standard_string(
|
|
|
|
|
|
getattr(self, property_name)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def convert_naive_utc_datetime_to_cap_standard_string(dt):
|
|
|
|
|
|
"""
|
|
|
|
|
|
As defined in section 3.3.2 of
|
|
|
|
|
|
http://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2-os.html
|
|
|
|
|
|
They define the standard "YYYY-MM-DDThh:mm:ssXzh:zm", where X is
|
|
|
|
|
|
`+` if the timezone is > UTC, otherwise `-`
|
|
|
|
|
|
"""
|
|
|
|
|
|
return f"{dt.strftime('%Y-%m-%dT%H:%M:%S')}-00:00"
|
2020-08-04 19:00:10 +01:00
|
|
|
|
|
2020-11-16 18:48:00 +00:00
|
|
|
|
def get_provider_message(self, provider):
|
|
|
|
|
|
return next(
|
|
|
|
|
|
(
|
|
|
|
|
|
provider_message
|
|
|
|
|
|
for provider_message in self.provider_messages
|
|
|
|
|
|
if provider_message.provider == provider
|
|
|
|
|
|
),
|
|
|
|
|
|
None
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-11-17 12:35:10 +00:00
|
|
|
|
def get_earlier_provider_messages(self, provider):
|
|
|
|
|
|
"""
|
2021-02-09 17:01:04 +00:00
|
|
|
|
Get the previous message for a provider. These are different per provider, as the identifiers are different.
|
2020-11-17 12:35:10 +00:00
|
|
|
|
Return the full provider_message object rather than just an identifier, since the different providers expect
|
|
|
|
|
|
reference to contain different things - let the cbc_proxy work out what information is relevant.
|
|
|
|
|
|
"""
|
2021-03-10 13:55:06 +00:00
|
|
|
|
from app.dao.broadcast_message_dao import (
|
|
|
|
|
|
get_earlier_events_for_broadcast_event,
|
|
|
|
|
|
)
|
2020-11-17 12:35:10 +00:00
|
|
|
|
earlier_events = [
|
|
|
|
|
|
event for event in get_earlier_events_for_broadcast_event(self.id)
|
|
|
|
|
|
]
|
|
|
|
|
|
ret = []
|
|
|
|
|
|
for event in earlier_events:
|
|
|
|
|
|
provider_message = event.get_provider_message(provider)
|
|
|
|
|
|
if provider_message is None:
|
|
|
|
|
|
# TODO: We should figure out what to do if a previous message hasn't been sent out yet.
|
|
|
|
|
|
# We don't want to not cancel a message just because it's stuck in a queue somewhere.
|
|
|
|
|
|
# This exception should probably be named, and then should be caught further up and handled
|
|
|
|
|
|
# appropriately.
|
|
|
|
|
|
raise Exception(
|
|
|
|
|
|
f'Cannot get earlier message references for event {self.id}, previous event {event.id} has not ' +
|
|
|
|
|
|
f' been sent to provider "{provider}" yet'
|
|
|
|
|
|
)
|
|
|
|
|
|
ret.append(provider_message)
|
|
|
|
|
|
return ret
|
2020-07-24 12:46:28 +01:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
2020-08-04 19:21:22 +01:00
|
|
|
|
'id': str(self.id),
|
2020-07-24 12:46:28 +01:00
|
|
|
|
|
2020-08-04 19:21:22 +01:00
|
|
|
|
'service_id': str(self.service_id),
|
2020-08-04 19:00:10 +01:00
|
|
|
|
|
2020-08-04 19:21:22 +01:00
|
|
|
|
'broadcast_message_id': str(self.broadcast_message_id),
|
|
|
|
|
|
# sent_at is required by BroadcastMessageTemplate.from_broadcast_event
|
|
|
|
|
|
'sent_at': self.sent_at.strftime(DATETIME_FORMAT),
|
2020-07-24 12:46:28 +01:00
|
|
|
|
'message_type': self.message_type,
|
|
|
|
|
|
|
|
|
|
|
|
'transmitted_content': self.transmitted_content,
|
|
|
|
|
|
'transmitted_areas': self.transmitted_areas,
|
|
|
|
|
|
'transmitted_sender': self.transmitted_sender,
|
|
|
|
|
|
|
2020-07-27 15:17:19 +01:00
|
|
|
|
'transmitted_starts_at': get_dt_string_or_none(self.transmitted_starts_at),
|
2020-08-04 19:21:22 +01:00
|
|
|
|
# transmitted_finishes_at is required by BroadcastMessageTemplate.from_broadcast_event
|
|
|
|
|
|
'transmitted_finishes_at': self.transmitted_finishes_at.strftime(DATETIME_FORMAT),
|
2020-08-04 19:00:10 +01:00
|
|
|
|
|
2020-07-24 12:46:28 +01:00
|
|
|
|
}
|
2020-10-26 16:04:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastProvider:
|
|
|
|
|
|
EE = 'ee'
|
|
|
|
|
|
VODAFONE = 'vodafone'
|
|
|
|
|
|
THREE = 'three'
|
|
|
|
|
|
O2 = 'o2'
|
|
|
|
|
|
|
|
|
|
|
|
PROVIDERS = [EE, VODAFONE, THREE, O2]
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-06 15:20:59 +01:00
|
|
|
|
ALL_BROADCAST_PROVIDERS = 'all'
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-10-26 16:04:39 +00:00
|
|
|
|
class BroadcastProviderMessageStatus:
|
|
|
|
|
|
TECHNICAL_FAILURE = 'technical-failure' # Couldn’t send (cbc proxy 5xx/4xx)
|
|
|
|
|
|
SENDING = 'sending' # Sent to cbc, awaiting response
|
|
|
|
|
|
ACK = 'returned-ack' # Received ack response
|
|
|
|
|
|
ERR = 'returned-error' # Received error response
|
|
|
|
|
|
|
|
|
|
|
|
STATES = [TECHNICAL_FAILURE, SENDING, ACK, ERR]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastProviderMessage(db.Model):
|
|
|
|
|
|
"""
|
|
|
|
|
|
A row in this table represents the XML blob sent to a single provider.
|
|
|
|
|
|
"""
|
|
|
|
|
|
__tablename__ = 'broadcast_provider_message'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
|
|
|
|
|
|
|
|
|
|
broadcast_event_id = db.Column(UUID(as_uuid=True), db.ForeignKey('broadcast_event.id'))
|
2020-11-16 18:48:00 +00:00
|
|
|
|
broadcast_event = db.relationship('BroadcastEvent', backref='provider_messages')
|
2020-10-26 16:04:39 +00:00
|
|
|
|
|
|
|
|
|
|
# 'ee', 'three', 'vodafone', etc
|
|
|
|
|
|
provider = db.Column(db.String)
|
|
|
|
|
|
|
|
|
|
|
|
status = db.Column(db.String)
|
|
|
|
|
|
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
UniqueConstraint(broadcast_event_id, provider)
|
2020-12-01 17:24:08 +00:00
|
|
|
|
|
2020-12-09 11:41:22 +00:00
|
|
|
|
message_number = association_proxy('broadcast_provider_message_number', 'broadcast_provider_message_number')
|
|
|
|
|
|
|
2020-12-01 17:24:08 +00:00
|
|
|
|
|
2020-12-02 15:59:41 +00:00
|
|
|
|
class BroadcastProviderMessageNumber(db.Model):
|
|
|
|
|
|
"""
|
|
|
|
|
|
To send IBAG messages via the CBC proxy to Nokia CBC appliances, Notify must generate and store a numeric
|
|
|
|
|
|
message_number alongside the message ID (GUID).
|
|
|
|
|
|
Subsequent messages (Update, Cancel) in IBAG format must reference the original message_number & message_id.
|
|
|
|
|
|
This model relates broadcast_provider_message_id to that numeric message_number.
|
|
|
|
|
|
"""
|
|
|
|
|
|
__tablename__ = 'broadcast_provider_message_number'
|
|
|
|
|
|
|
|
|
|
|
|
sequence = Sequence('broadcast_provider_message_number_seq')
|
|
|
|
|
|
broadcast_provider_message_number = db.Column(
|
|
|
|
|
|
db.Integer, sequence, server_default=sequence.next_value(), primary_key=True
|
|
|
|
|
|
)
|
|
|
|
|
|
broadcast_provider_message_id = db.Column(
|
|
|
|
|
|
UUID(as_uuid=True), db.ForeignKey('broadcast_provider_message.id'), nullable=False
|
|
|
|
|
|
)
|
2020-12-09 11:41:22 +00:00
|
|
|
|
broadcast_provider_message = db.relationship(
|
|
|
|
|
|
'BroadcastProviderMessage', backref=db.backref("broadcast_provider_message_number", uselist=False)
|
|
|
|
|
|
)
|
2020-12-02 15:59:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
2021-01-28 13:57:33 +00:00
|
|
|
|
class ServiceBroadcastSettings(db.Model):
|
|
|
|
|
|
"""
|
2021-02-19 11:33:05 +00:00
|
|
|
|
Every broadcast service should have one and only one row in this table.
|
2021-01-28 13:57:33 +00:00
|
|
|
|
"""
|
|
|
|
|
|
__tablename__ = "service_broadcast_settings"
|
|
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True, nullable=False)
|
|
|
|
|
|
service = db.relationship(Service, backref=db.backref("service_broadcast_settings", uselist=False))
|
|
|
|
|
|
channel = db.Column(
|
|
|
|
|
|
db.String(255), db.ForeignKey('broadcast_channel_types.name'), nullable=False
|
|
|
|
|
|
)
|
2021-05-10 15:41:34 +01:00
|
|
|
|
provider = db.Column(db.String, db.ForeignKey('broadcast_provider_types.name'), nullable=False)
|
2021-01-28 13:57:33 +00:00
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
|
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BroadcastChannelTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'broadcast_channel_types'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-05 15:24:49 +01:00
|
|
|
|
class BroadcastProviderTypes(db.Model):
|
|
|
|
|
|
__tablename__ = 'broadcast_provider_types'
|
|
|
|
|
|
|
|
|
|
|
|
name = db.Column(db.String(255), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-12-01 17:24:08 +00:00
|
|
|
|
class ServiceBroadcastProviderRestriction(db.Model):
|
|
|
|
|
|
"""
|
2021-02-09 09:38:43 +00:00
|
|
|
|
TODO: Drop this table as no longer used
|
|
|
|
|
|
|
2020-12-01 17:24:08 +00:00
|
|
|
|
Most services don't send broadcasts. Of those that do, most send to all broadcast providers.
|
|
|
|
|
|
However, some services don't send to all providers. These services are test services that we or the providers
|
|
|
|
|
|
themselves use.
|
|
|
|
|
|
|
|
|
|
|
|
This table links those services. There should only be one row per service in this table, and this is enforced by
|
|
|
|
|
|
the service_id being a primary key.
|
|
|
|
|
|
"""
|
|
|
|
|
|
__tablename__ = "service_broadcast_provider_restriction"
|
|
|
|
|
|
|
|
|
|
|
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), primary_key=True, nullable=False)
|
2020-12-01 17:41:39 +00:00
|
|
|
|
service = db.relationship(Service, backref=db.backref("service_broadcast_provider_restriction", uselist=False))
|
2020-12-01 17:24:08 +00:00
|
|
|
|
|
|
|
|
|
|
provider = db.Column(db.String, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2021-05-07 16:08:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WebauthnCredential(db.Model):
|
|
|
|
|
|
"""
|
|
|
|
|
|
A table that stores data for registered webauthn credentials.
|
|
|
|
|
|
"""
|
|
|
|
|
|
__tablename__ = "webauthn_credential"
|
|
|
|
|
|
|
2021-05-10 16:36:00 +01:00
|
|
|
|
id = db.Column(UUID(as_uuid=True), primary_key=True, nullable=False, default=uuid.uuid4)
|
2021-05-07 16:08:34 +01:00
|
|
|
|
|
2021-05-10 16:36:00 +01:00
|
|
|
|
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=False)
|
|
|
|
|
|
user = db.relationship(User, backref=db.backref("webauthn_credentials"))
|
2021-05-07 16:08:34 +01:00
|
|
|
|
|
2021-05-10 16:36:00 +01:00
|
|
|
|
name = db.Column(db.String, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
# base64 encoded CBOR. used for logging in. https://w3c.github.io/webauthn/#sctn-attested-credential-data
|
|
|
|
|
|
credential_data = db.Column(db.String, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
# base64 encoded CBOR. used for auditing. https://www.w3.org/TR/webauthn-2/#authenticatorattestationresponse
|
|
|
|
|
|
registration_response = db.Column(db.String, nullable=False)
|
2021-05-07 16:08:34 +01:00
|
|
|
|
|
|
|
|
|
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
2021-05-10 16:36:00 +01:00
|
|
|
|
updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow)
|
2021-05-10 22:09:07 +01:00
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': str(self.id),
|
|
|
|
|
|
'user_id': str(self.user_id),
|
|
|
|
|
|
'name': self.name,
|
|
|
|
|
|
'credential_data': self.credential_data,
|
|
|
|
|
|
'created_at': self.created_at.strftime(DATETIME_FORMAT),
|
|
|
|
|
|
'updated_at': get_dt_string_or_none(self.updated_at),
|
|
|
|
|
|
}
|