Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ce/connect messaging #35364

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions corehq/apps/commtrack/sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from corehq.apps.domain.models import Domain
from corehq.apps.products.models import SQLProduct
from corehq.apps.receiverwrapper.util import submit_form_locally
from corehq.apps.sms.api import MessageMetadata, send_sms_to_verified_number
from corehq.apps.sms.api import MessageMetadata, send_message_to_verified_number
from corehq.apps.users.models import CouchUser
from corehq.form_processor.interfaces.supply import SupplyInterface
from corehq.form_processor.parsers.ledgers.helpers import (
Expand Down Expand Up @@ -50,7 +50,7 @@
except Exception as e:
if settings.UNIT_TESTING or settings.DEBUG:
raise
send_sms_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e))
send_message_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e))
return True

process(domain_obj.name, data)
Expand Down Expand Up @@ -163,7 +163,7 @@

try:
value = int(arg)
except:

Check failure on line 166 in corehq/apps/commtrack/sms.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/commtrack/sms.py#L166

Do not use bare 'except' (E722)
raise SMSError('could not understand product quantity "%s"' % arg)

for product in products:
Expand Down Expand Up @@ -270,7 +270,7 @@
]:
here, there = ('dest', 'src')
else:
here, there = ('src', 'dest')

Check failure on line 273 in corehq/apps/commtrack/sms.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/commtrack/sms.py#L273

Local variable 'there' is assigned to but never used (F841)

attr[here] = transfers[0].case_id

Expand Down Expand Up @@ -350,7 +350,7 @@

def truncate(text, maxlen, ellipsis='...'):
if len(text) > maxlen:
return text[:maxlen-len(ellipsis)] + ellipsis

Check failure on line 353 in corehq/apps/commtrack/sms.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/commtrack/sms.py#L353

Missing whitespace around arithmetic operator (E226)
else:
return text

Expand All @@ -361,7 +361,7 @@
static_loc = data['location']
location_name = static_loc.name
metadata = MessageMetadata(location_id=static_loc.get_id)
tx_by_action = map_reduce(lambda tx: [(tx.action_config(C).name,)], data=data['transactions'], include_docs=True)

Check failure on line 364 in corehq/apps/commtrack/sms.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/commtrack/sms.py#L364

Line too long (117 > 115 characters) (E501)

def summarize_action(action, txs):
return '%s %s' % (txs[0].action_config(C).keyword.upper(), ' '.join(sorted(tx.fragment() for tx in txs)))
Expand All @@ -372,4 +372,4 @@
' '.join(sorted(summarize_action(a, txs) for a, txs in tx_by_action.items()))
)

send_sms_to_verified_number(v, msg, metadata=metadata)
send_message_to_verified_number(v, msg, metadata=metadata)
20 changes: 19 additions & 1 deletion corehq/apps/domain/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import User
from django.db.models import Q
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.views.decorators.debug import sensitive_variables

from no_exceptions.exceptions import Http400
Expand Down Expand Up @@ -169,7 +169,9 @@
return real_decorator




def formplayer_auth(view):

Check failure on line 174 in corehq/apps/domain/auth.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/domain/auth.py#L174

Too many blank lines (4) (E303)
return validate_request_hmac('FORMPLAYER_INTERNAL_AUTH_KEY')(view)


Expand Down Expand Up @@ -405,3 +407,19 @@
return False

return couch_user.is_member_of(project) or (couch_user.is_superuser and not project.restrict_superusers)


def connectid_token_auth(view_func):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird that connectid is one word. I do see that it's consistent.

@wraps(view_func)
def _inner(request, *args, **kwargs):
auth_header = request.META.get("HTTP_AUTHORIZATION")
if not auth_header:
return HttpResponseForbidden()
_, token = auth_header.split(" ")
if not token:
return HttpResponseBadRequest("ConnectID Token Required")
username = get_connectid_userinfo(token)
if username is None:
return HttpResponseForbidden()
return view_func(request, *args, **kwargs)

Check failure on line 424 in corehq/apps/domain/auth.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/domain/auth.py#L424

Multiple spaces after ',' (E241)
return _inner
1 change: 1 addition & 0 deletions corehq/apps/domain/deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ def _delete_demo_user_restores(domain_name):
ModelDeletion('userreports', 'InvalidUCRData', 'domain'),
ModelDeletion('userreports', 'UCRExpression', 'domain'),
ModelDeletion('users', 'ConnectIDUserLink', 'domain'),
ModelDeletion('users', 'ConnectIDMessagingKey', 'domain'),
ModelDeletion('users', 'DomainRequest', 'domain'),
ModelDeletion('users', 'DeactivateMobileWorkerTrigger', 'domain'),
ModelDeletion('users', 'Invitation', 'domain'),
Expand Down
1 change: 1 addition & 0 deletions corehq/apps/dump_reload/sql/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
FilteredModelIteratorBuilder('linked_domain.DomainLinkHistory', SimpleFilter('link__linked_domain')),
FilteredModelIteratorBuilder('user_importer.UserUploadRecord', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.ConnectIDUserLink', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.ConnectIDMessagingKey', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.DeactivateMobileWorkerTrigger', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.DomainRequest', SimpleFilter('domain')),
FilteredModelIteratorBuilder('users.Invitation', SimpleFilter('domain')),
Expand Down
42 changes: 33 additions & 9 deletions corehq/apps/sms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
from corehq.apps.sms.mixin import BadSMSConfigException
from corehq.apps.sms.models import (
ConnectMessage,
INCOMING,
OUTGOING,
SMS,
Expand Down Expand Up @@ -131,6 +132,13 @@
return QueuedSMS if settings.SMS_QUEUE_ENABLED else SMS


def get_message_class(phone_number):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you think of inlining get_sms_class into this function? Seems a little nicer to have calling code always use this function instead of needing to know when to call this vs get_sms_class.

if phone_number.is_sms:
return get_sms_class()
else:
return ConnectMessage


def send_sms(domain, contact, phone_number, text, metadata=None, logged_subevent=None):
"""
Sends an outbound SMS. Returns false if it fails.
Expand Down Expand Up @@ -174,7 +182,7 @@
return queue_outgoing_sms(msg)


def send_sms_to_verified_number(verified_number, text, metadata=None, logged_subevent=None, events=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is referenced in https://github.com/dimagi/commcare-hq/blob/master/docs/messaging/outbound_sms.rst, can you update that? It'd probably be helpful to update those docs a bit more holitically, at least to mention that connect messages exist.

def send_message_to_verified_number(verified_number, text, metadata=None, logged_subevent=None, events=None):
"""
Sends an sms using the given verified phone number entry.

Expand All @@ -192,7 +200,7 @@
return False
raise

msg = get_sms_class()(
msg = get_message_class(verified_number)(
couch_recipient_doc_type=verified_number.owner_doc_type,
couch_recipient=verified_number.owner_id,
phone_number="+" + str(verified_number.phone_number),
Expand All @@ -215,7 +223,10 @@
msg.custom_metadata[field] = value
msg.save()

return queue_outgoing_sms(msg)
if verified_number.is_sms:
return queue_outgoing_sms(msg)
else:
return send_connect_message(msg, backend)


def send_sms_with_backend(domain, phone_number, text, backend_id, metadata=None):
Expand Down Expand Up @@ -253,7 +264,7 @@
try:
from corehq.apps.sms.management.commands.run_sms_queue import SMSEnqueuingOperation
SMSEnqueuingOperation().enqueue(msg)
except:

Check failure on line 267 in corehq/apps/sms/api.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/api.py#L267

Do not use bare 'except' (E722)
# If this direct enqueue fails, no problem, it will get picked up
# shortly.
pass
Expand All @@ -266,7 +277,7 @@
msg.datetime_to_process = msg.date
msg.queued_timestamp = get_utcnow()
msg.save()
except:

Check failure on line 280 in corehq/apps/sms/api.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/api.py#L280

Do not use bare 'except' (E722)
log_sms_exception(msg)
return False

Expand Down Expand Up @@ -386,7 +397,17 @@
return True


def send_connect_message(message, backend):
try:
backend.send(message)
return True
except Exception:
log_sms_exception(message)
return False



def register_sms_user(

Check failure on line 410 in corehq/apps/sms/api.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/api.py#L410

Too many blank lines (3) (E303)
username, cleaned_phone_number, domain, send_welcome_sms=False, admin_alert_emails=None
):
try:
Expand Down Expand Up @@ -496,7 +517,7 @@
"""
registration_processed = False
text_words = msg.text.upper().split()
keyword1 = text_words[0] if len(text_words) > 0 else ""

Check failure on line 520 in corehq/apps/sms/api.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/api.py#L520

Local variable 'keyword1' is assigned to but never used (F841)
keyword2 = text_words[1].lower() if len(text_words) > 1 else ""
keyword3 = text_words[2] if len(text_words) > 2 else ""
keyword4 = text_words[3] if len(text_words) > 3 else ""
Expand Down Expand Up @@ -621,7 +642,7 @@
for sms_handler_name in sms_handler_names:
try:
handler = to_function(sms_handler_name)
except:

Check failure on line 645 in corehq/apps/sms/api.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/api.py#L645

Do not use bare 'except' (E722)
notify_exception(None, message=('error loading sms handler: %s' % sms_handler_name))
continue

Expand Down Expand Up @@ -685,9 +706,9 @@
)


def process_incoming(msg):
def process_incoming(msg, phone=None):
try:
_process_incoming(msg)
_process_incoming(msg, phone)
status = 'ok'
except Exception:
status = 'error'
Expand Down Expand Up @@ -715,9 +736,12 @@
return msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS)


def _process_incoming(msg):
def _process_incoming(msg, phone=None):
sms_load_counter("inbound", msg.domain)()
verified_number, has_domain_two_way_scope = get_inbound_phone_entry_from_sms(msg)
if phone is None:
verified_number, has_domain_two_way_scope = get_inbound_phone_entry_from_sms(msg)
else:
verified_number = phone
is_two_way = verified_number is not None and verified_number.is_two_way

if verified_number:
Expand Down Expand Up @@ -746,7 +770,7 @@
metadata = MessageMetadata(ignore_opt_out=True)
text = get_message(MSG_OPTED_OUT, verified_number, context=(opt_in_keywords[0],))
if verified_number:
send_sms_to_verified_number(verified_number, text, metadata=metadata)
send_message_to_verified_number(verified_number, text, metadata=metadata)
elif msg.backend_id:
send_sms_with_backend(msg.domain, msg.phone_number, text, msg.backend_id, metadata=metadata)
else:
Expand All @@ -756,7 +780,7 @@
if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain):
text = get_message(MSG_OPTED_IN, verified_number, context=(opt_out_keywords[0],))
if verified_number:
send_sms_to_verified_number(verified_number, text)
send_message_to_verified_number(verified_number, text)
elif msg.backend_id:
send_sms_with_backend(msg.domain, msg.phone_number, text, msg.backend_id)
else:
Expand Down
1 change: 1 addition & 0 deletions corehq/apps/sms/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def options(self):
(MessagingEvent.CONTENT_SMS_CALLBACK, gettext_noop('SMS Callback')),
(MessagingEvent.CONTENT_SMS, gettext_noop('Other SMS')),
(MessagingEvent.CONTENT_EMAIL, gettext_noop('Email')),
(MessagingEvent.CONTENT_CONNECT, gettext_noop('Connect Message')),
]


Expand Down
4 changes: 2 additions & 2 deletions corehq/apps/sms/handlers/fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from corehq.apps.sms.api import (
MessageMetadata,
add_msg_tags,
send_sms_to_verified_number,
send_message_to_verified_number,
)
from corehq.apps.sms.models import WORKFLOW_DEFAULT, MessagingEvent

Expand All @@ -26,7 +26,7 @@ def fallback_handler(verified_number, text, msg):
outbound_meta = MessageMetadata(workflow=WORKFLOW_DEFAULT,
location_id=msg.location_id,
messaging_subevent_id=outbound_subevent.pk)
send_sms_to_verified_number(verified_number, domain_obj.default_sms_response,
send_message_to_verified_number(verified_number, domain_obj.default_sms_response,
metadata=outbound_meta)
outbound_subevent.completed()

Expand Down
12 changes: 6 additions & 6 deletions corehq/apps/sms/handlers/form_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
MessageMetadata,
add_msg_tags,
log_sms_exception,
send_sms_to_verified_number,
send_message_to_verified_number,
)
from corehq.apps.sms.messages import (
MSG_CHOICE_OUT_OF_RANGE,
Expand Down Expand Up @@ -59,7 +59,7 @@
"message_id": msg.couch_id
})
session.mark_completed(False) # this will also release the channel
send_sms_to_verified_number(
send_message_to_verified_number(
verified_number, get_message(MSG_GENERIC_ERROR, verified_number)
)
return True
Expand All @@ -77,7 +77,7 @@
verified_number.domain, verified_number.owner_id
)
if multiple:
send_sms_to_verified_number(verified_number, get_message(MSG_GENERIC_ERROR, verified_number))
send_message_to_verified_number(verified_number, get_message(MSG_GENERIC_ERROR, verified_number))
return True

if session:
Expand All @@ -102,7 +102,7 @@
except Exception:
# Catch any touchforms errors
log_sms_exception(msg)
send_sms_to_verified_number(verified_number, get_message(MSG_TOUCHFORMS_DOWN, verified_number))
send_message_to_verified_number(verified_number, get_message(MSG_TOUCHFORMS_DOWN, verified_number))
return True
else:
return False
Expand Down Expand Up @@ -152,12 +152,12 @@
events = get_events_from_responses(responses)
if len(text_responses) > 0:
response_text = format_message_list(text_responses)
send_sms_to_verified_number(verified_number, response_text,
send_message_to_verified_number(verified_number, response_text,
metadata=outbound_metadata, events=events)
else:
mark_as_invalid_response(msg)
response_text = "%s %s" % (error_msg, event.text_prompt)
send_sms_to_verified_number(verified_number, response_text,
send_message_to_verified_number(verified_number, response_text,
metadata=outbound_metadata, events=[event])


Expand Down Expand Up @@ -217,7 +217,7 @@
error_msg = get_message(MSG_INVALID_INT_RANGE, verified_number)
except ValueError:
error_msg = get_message(MSG_INVALID_INT, verified_number)

Check failure on line 220 in corehq/apps/sms/handlers/form_session.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/handlers/form_session.py#L220

Blank line contains whitespace (W293)
# Validate float
elif event.datatype == "float":
try:
Expand All @@ -225,7 +225,7 @@
valid = True
except ValueError:
error_msg = get_message(MSG_INVALID_FLOAT, verified_number)

Check failure on line 228 in corehq/apps/sms/handlers/form_session.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/handlers/form_session.py#L228

Blank line contains whitespace (W293)
# Validate longint
elif event.datatype == "longint":
try:
Expand All @@ -233,7 +233,7 @@
valid = True
except ValueError:
error_msg = get_message(MSG_INVALID_LONG, verified_number)

Check failure on line 236 in corehq/apps/sms/handlers/form_session.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/handlers/form_session.py#L236

Blank line contains whitespace (W293)
# Validate date (Format: specified by Domain.sms_survey_date_format, default: YYYYMMDD)
elif event.datatype == "date":
domain_obj = Domain.get_by_name(verified_number.domain)
Expand Down
14 changes: 7 additions & 7 deletions corehq/apps/sms/handlers/keyword.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from corehq.apps.sms.api import (
MessageMetadata,
add_msg_tags,
send_sms_to_verified_number,
send_message_to_verified_number,
)
from corehq.apps.sms.handlers.form_session import validate_answer
from corehq.apps.sms.messages import (
Expand Down Expand Up @@ -107,10 +107,10 @@
process_survey_keyword_actions(verified_number, k, text[6:].strip(), msg)
else:
message = get_message(MSG_KEYWORD_NOT_FOUND, verified_number, (keyword,))
send_sms_to_verified_number(verified_number, message, metadata=outbound_metadata)
send_message_to_verified_number(verified_number, message, metadata=outbound_metadata)
else:
message = get_message(MSG_START_KEYWORD_USAGE, verified_number, (text_words[0],))
send_sms_to_verified_number(verified_number, message, metadata=outbound_metadata)
send_message_to_verified_number(verified_number, message, metadata=outbound_metadata)
return True


Expand All @@ -130,14 +130,14 @@

resp = FormplayerInterface(session.session_id, verified_number.domain).current_question()

send_sms_to_verified_number(verified_number, resp.event.text_prompt,
send_message_to_verified_number(verified_number, resp.event.text_prompt,
metadata=outbound_metadata, events=[resp.event])
return True


def global_keyword_unknown(verified_number, text, msg, text_words, open_sessions):
message = get_message(MSG_UNKNOWN_GLOBAL_KEYWORD, verified_number, (text_words[0],))
send_sms_to_verified_number(verified_number, message)
send_message_to_verified_number(verified_number, message)
return True


Expand Down Expand Up @@ -308,7 +308,7 @@
form = app.get_form(form_unique_id)
module = form.get_module()
return app, module, form, False, None
except:

Check failure on line 311 in corehq/apps/sms/handlers/keyword.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/handlers/keyword.py#L311

Do not use bare 'except' (E722)
log_error(MessagingEvent.ERROR_CANNOT_FIND_FORM, logged_subevent)
return None, None, None, True, MSG_FORM_NOT_FOUND

Expand Down Expand Up @@ -469,7 +469,7 @@
contact.doc_type, contact.get_id)
metadata.messaging_subevent_id = response_subevent.pk

send_sms_to_verified_number(verified_number, error_msg, metadata=metadata)
send_message_to_verified_number(verified_number, error_msg, metadata=metadata)
if response_subevent:
response_subevent.completed()

Expand Down Expand Up @@ -544,8 +544,8 @@

def user_can_access_case(user, case):
return (
user_is_owner(user, case) or

Check failure on line 547 in corehq/apps/sms/handlers/keyword.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/handlers/keyword.py#L547

Line break after binary operator (W504)
case_is_shared(user, case) or

Check failure on line 548 in corehq/apps/sms/handlers/keyword.py

View workflow job for this annotation

GitHub Actions / Flake8

corehq/apps/sms/handlers/keyword.py#L548

Line break after binary operator (W504)
access_through_subcases(user, case)
)

Expand All @@ -560,7 +560,7 @@
messaging_subevent_id=subevent.pk,
)
message = get_message(message_id, vn)
send_sms_to_verified_number(vn, message, metadata=metadata)
send_message_to_verified_number(vn, message, metadata=metadata)
subevent.completed()


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by Django 4.2.15 on 2024-10-11 13:12

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('sms', '0059_remove_unicel_backend'),
]

operations = [
migrations.AlterField(
model_name='messagingevent',
name='content_type',
field=models.CharField(choices=[('NOP', 'None'), ('SMS', 'SMS Message'), ('CBK', 'SMS Expecting Callback'), ('SVY', 'SMS Survey'), ('IVR', 'IVR Survey'), ('VER', 'Phone Verification'), ('ADH', 'Manually Sent Message'), ('API', 'Message Sent Via API'), ('CHT', 'Message Sent Via Chat'), ('EML', 'Email'), ('FCM', 'FCM Push Notification'), ('CON', 'Connect Message')], max_length=3),
),
migrations.AlterField(
model_name='messagingsubevent',
name='content_type',
field=models.CharField(choices=[('NOP', 'None'), ('SMS', 'SMS Message'), ('CBK', 'SMS Expecting Callback'), ('SVY', 'SMS Survey'), ('IVR', 'IVR Survey'), ('VER', 'Phone Verification'), ('ADH', 'Manually Sent Message'), ('API', 'Message Sent Via API'), ('CHT', 'Message Sent Via Chat'), ('EML', 'Email'), ('FCM', 'FCM Push Notification'), ('CON', 'Connect Message')], max_length=3),
),
migrations.CreateModel(
name='ConnectMessage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('domain', models.CharField(db_index=True, max_length=126, null=True)),
('date', models.DateTimeField(db_index=True, null=True)),
('couch_recipient_doc_type', models.CharField(db_index=True, max_length=126, null=True)),
('couch_recipient', models.CharField(db_index=True, max_length=126, null=True)),
('phone_number', models.CharField(db_index=True, max_length=126, null=True)),
('direction', models.CharField(max_length=1, null=True)),
('error', models.BooleanField(default=False, null=True)),
('system_error_message', models.TextField(null=True)),
('system_phone_number', models.CharField(max_length=126, null=True)),
('backend_api', models.CharField(max_length=126, null=True)),
('backend_id', models.CharField(max_length=126, null=True)),
('billed', models.BooleanField(default=False, null=True)),
('workflow', models.CharField(max_length=126, null=True)),
('xforms_session_couch_id', models.CharField(db_index=True, max_length=126, null=True)),
('reminder_id', models.CharField(max_length=126, null=True)),
('location_id', models.CharField(max_length=126, null=True)),
('date_modified', models.DateTimeField(auto_now=True, db_index=True, null=True)),
('text', models.CharField(max_length=300)),
('messaging_subevent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='sms.messagingsubevent')),
],
options={
'abstract': False,
},
),
]
Loading
Loading