From e29b0465f925f0e139116f954556f089a9f07958 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Fri, 24 Apr 2020 17:48:31 +0200
Subject: [PATCH 1/3] sms: add SMS class to store sms parameters (#21465)

---
 .../choosit/migrations/0010_choositsms.py     | 33 +++++++++++++++++++
 passerelle/apps/choosit/models.py             | 12 +++++--
 .../apps/mobyt/migrations/0009_mobytsms.py    | 33 +++++++++++++++++++
 passerelle/apps/mobyt/models.py               | 14 +++++---
 .../apps/orange/migrations/0009_orangesms.py  | 33 +++++++++++++++++++
 passerelle/apps/orange/models.py              | 10 +++++-
 passerelle/apps/ovh/migrations/0009_ovhsms.py | 33 +++++++++++++++++++
 passerelle/apps/ovh/models.py                 | 12 +++++--
 .../apps/oxyd/migrations/0009_oxydsms.py      | 33 +++++++++++++++++++
 passerelle/apps/oxyd/models.py                | 12 +++++--
 .../apps/twilio/migrations/0002_twiliosms.py  | 33 +++++++++++++++++++
 passerelle/apps/twilio/models.py              | 12 +++++--
 passerelle/base/models.py                     | 22 +++++++++++++
 13 files changed, 279 insertions(+), 13 deletions(-)
 create mode 100644 passerelle/apps/choosit/migrations/0010_choositsms.py
 create mode 100644 passerelle/apps/mobyt/migrations/0009_mobytsms.py
 create mode 100644 passerelle/apps/orange/migrations/0009_orangesms.py
 create mode 100644 passerelle/apps/ovh/migrations/0009_ovhsms.py
 create mode 100644 passerelle/apps/oxyd/migrations/0009_oxydsms.py
 create mode 100644 passerelle/apps/twilio/migrations/0002_twiliosms.py

diff --git a/passerelle/apps/choosit/migrations/0010_choositsms.py b/passerelle/apps/choosit/migrations/0010_choositsms.py
new file mode 100644
index 00000000..1e004eff
--- /dev/null
+++ b/passerelle/apps/choosit/migrations/0010_choositsms.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-04-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('choosit', '0009_choositsmsgateway_max_message_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ChoositSMS',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=256)),
+                ('sender', models.CharField(blank=True, max_length=128)),
+                ('destination', models.CharField(blank=True, max_length=256)),
+                ('stop', models.BooleanField(default=False)),
+                ('status', models.CharField(choices=[('pending', 'pending'), ('pushed', 'pushed'), ('error', 'error')], default='pending', max_length=32, null=True)),
+                ('status_details', models.CharField(blank=True, max_length=256)),
+                ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sms_set', to='choosit.ChoositSMSGateway')),
+            ],
+            options={
+                'verbose_name': 'Choosit SMS',
+                'db_table': 'sms_choosit_sms',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/choosit/models.py b/passerelle/apps/choosit/models.py
index a4678dcd..6b2efe5a 100644
--- a/passerelle/apps/choosit/models.py
+++ b/passerelle/apps/choosit/models.py
@@ -2,17 +2,17 @@
 import json
 import requests
 
 from django.utils.six import string_types
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
 
 from passerelle.utils.jsonresponse import APIError
-from passerelle.base.models import SMSResource
+from passerelle.base.models import SMSResource, SMS
 
 
 class ChoositSMSGateway(SMSResource):
     key = models.CharField(verbose_name=_('Key'), max_length=64)
     default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3,
                                             default=u'33')
     default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2,
                                             default=u'0')
@@ -61,17 +61,17 @@ class ChoositSMSGateway(SMSResource):
                 }
             }
 
         ],
     }
     URL = 'http://sms.choosit.com/webservice'
 
     class Meta:
-        verbose_name = 'Choosit'
+        verbose_name = _('Choosit')
         db_table = 'sms_choosit'
 
     @classmethod
     def get_verbose_name(cls):
         return cls._meta.verbose_name
 
     def send_msg(self, text, sender, destinations, **kwargs):
         """Send a SMS using the Choosit provider"""
@@ -103,8 +103,16 @@ class ChoositSMSGateway(SMSResource):
                     elif 'error' in output:
                         results.append(u'Choosit error: %s' % output['error'])
                     else:
                         results.append(output)
         if any(isinstance(result, string_types) for result in results):
             raise APIError('Choosit error: some destinations failed',
                            data=list(zip(destinations, results)))
         return list(zip(destinations, results))
+
+
+class ChoositSMS(SMS):
+    gateway = models.ForeignKey(ChoositSMSGateway, related_name='sms_set')
+
+    class Meta:
+        verbose_name = _('Choosit SMS')
+        db_table = 'sms_choosit_sms'
diff --git a/passerelle/apps/mobyt/migrations/0009_mobytsms.py b/passerelle/apps/mobyt/migrations/0009_mobytsms.py
new file mode 100644
index 00000000..095d1ea5
--- /dev/null
+++ b/passerelle/apps/mobyt/migrations/0009_mobytsms.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-04-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('mobyt', '0008_auto_20200310_1539'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MobytSMS',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=256)),
+                ('sender', models.CharField(blank=True, max_length=128)),
+                ('destination', models.CharField(blank=True, max_length=256)),
+                ('stop', models.BooleanField(default=False)),
+                ('status', models.CharField(choices=[('pending', 'pending'), ('pushed', 'pushed'), ('error', 'error')], default='pending', max_length=32, null=True)),
+                ('status_details', models.CharField(blank=True, max_length=256)),
+                ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sms_set', to='mobyt.MobytSMSGateway')),
+            ],
+            options={
+                'verbose_name': 'Mobyt SMS',
+                'db_table': 'sms_mobyt_sms',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/mobyt/models.py b/passerelle/apps/mobyt/models.py
index 4983b9d7..b9e36670 100644
--- a/passerelle/apps/mobyt/models.py
+++ b/passerelle/apps/mobyt/models.py
@@ -1,15 +1,15 @@
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
 
 import requests
 
 from passerelle.utils.jsonresponse import APIError
-from passerelle.base.models import SMSResource
+from passerelle.base.models import SMSResource, SMS
 
 
 class MobytSMSGateway(SMSResource):
     URL = 'http://multilevel.mobyt.fr/sms/batch.php'
     MESSAGES_QUALITIES = (
         ('l', _('sms direct')),
         ('ll', _('sms low-cost')),
         ('n', _('sms top')),
@@ -34,23 +34,21 @@ class MobytSMSGateway(SMSResource):
         'test_vectors': [
             {
                 'response': '',
                 'result': {
                     'err': 1,
                     'err_desc': 'MobyT error: response is not "OK"',
                 }
             }
-
         ],
-
     }
 
     class Meta:
-        verbose_name = 'Mobyt'
+        verbose_name = _('Mobyt')
         db_table = 'sms_mobyt'
 
     def send_msg(self, text, sender, destinations, **kwargs):
         """Send a SMS using the Mobyt provider"""
         # unfortunately it lacks a batch API...
         destinations = self.clean_numbers(destinations,
                                           self.default_country_code,
                                           self.default_trunk_prefix)
@@ -65,8 +63,16 @@ class MobytSMSGateway(SMSResource):
         }
         try:
             r = self.requests.post(self.URL, data=params)
         except requests.RequestException as e:
             raise APIError('MobyT error: POST failed, %s' % e)
         if r.content[:2] != "OK":
             raise APIError('MobyT error: response is not "OK"')
         return None
+
+
+class MobytSMS(SMS):
+    gateway = models.ForeignKey(MobytSMSGateway, related_name='sms_set')
+
+    class Meta:
+        verbose_name = _('Mobyt SMS')
+        db_table = 'sms_mobyt_sms'
diff --git a/passerelle/apps/orange/migrations/0009_orangesms.py b/passerelle/apps/orange/migrations/0009_orangesms.py
new file mode 100644
index 00000000..7367e06a
--- /dev/null
+++ b/passerelle/apps/orange/migrations/0009_orangesms.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-04-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('orange', '0008_auto_20200412_1240'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OrangeSMS',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=256)),
+                ('sender', models.CharField(blank=True, max_length=128)),
+                ('destination', models.CharField(blank=True, max_length=256)),
+                ('stop', models.BooleanField(default=False)),
+                ('status', models.CharField(choices=[('pending', 'pending'), ('pushed', 'pushed'), ('error', 'error')], default='pending', max_length=32, null=True)),
+                ('status_details', models.CharField(blank=True, max_length=256)),
+                ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sms_set', to='orange.OrangeSMSGateway')),
+            ],
+            options={
+                'verbose_name': 'Orange SMS',
+                'db_table': 'sms_orange_sms',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/orange/models.py b/passerelle/apps/orange/models.py
index e1b17613..d6a4d4c3 100644
--- a/passerelle/apps/orange/models.py
+++ b/passerelle/apps/orange/models.py
@@ -17,17 +17,17 @@
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
 #
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
-from passerelle.base.models import SMSResource
+from passerelle.base.models import SMSResource, SMS
 from passerelle.utils.jsonresponse import APIError
 
 BASE_API = 'https://contact-everyone.orange-business.com/api/v1.2/'
 URL_TOKEN = BASE_API + 'oauth/token'
 URL_GROUPS = BASE_API + 'groups'
 URL_DIFFUSION = BASE_API + 'groups/%s/diffusion-requests'
 
 
@@ -109,8 +109,16 @@ class OrangeSMSGateway(SMSResource):
     def send_msg(self, text, sender, destinations, **kwargs):
         '''Send a SMS using the Orange provider'''
         destinations = self.clean_numbers(
             destinations, self.default_country_code, self.default_trunk_prefix)
         access_token = self.get_access_token()
         group_id = self.group_id_from_name(access_token)
         response = self.diffusion(access_token, group_id, destinations, text)
         return response
+
+
+class OrangeSMS(SMS):
+    gateway = models.ForeignKey(OrangeSMSGateway, related_name='sms_set')
+
+    class Meta:
+        verbose_name = _('Orange SMS')
+        db_table = 'sms_orange_sms'
diff --git a/passerelle/apps/ovh/migrations/0009_ovhsms.py b/passerelle/apps/ovh/migrations/0009_ovhsms.py
new file mode 100644
index 00000000..0f8d595d
--- /dev/null
+++ b/passerelle/apps/ovh/migrations/0009_ovhsms.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-04-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ovh', '0008_ovhsmsgateway_max_message_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OVHSMS',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=256)),
+                ('sender', models.CharField(blank=True, max_length=128)),
+                ('destination', models.CharField(blank=True, max_length=256)),
+                ('stop', models.BooleanField(default=False)),
+                ('status', models.CharField(choices=[('pending', 'pending'), ('pushed', 'pushed'), ('error', 'error')], default='pending', max_length=32, null=True)),
+                ('status_details', models.CharField(blank=True, max_length=256)),
+                ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sms_set', to='ovh.OVHSMSGateway')),
+            ],
+            options={
+                'verbose_name': 'OVH SMS',
+                'db_table': 'sms_ovh_sms',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/ovh/models.py b/passerelle/apps/ovh/models.py
index d9d96135..ce85fccf 100644
--- a/passerelle/apps/ovh/models.py
+++ b/passerelle/apps/ovh/models.py
@@ -1,16 +1,16 @@
 import requests
 
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
 from django.utils.encoding import force_text
 
 from passerelle.utils.jsonresponse import APIError
-from passerelle.base.models import SMSResource
+from passerelle.base.models import SMSResource, SMS
 
 
 class OVHSMSGateway(SMSResource):
     URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi'
     MESSAGES_CLASSES = (
         (0, _('Message are directly shown to users on phone screen '
               'at reception. The message is never stored, neither in the '
               'phone memory nor in the SIM card. It is deleted as '
@@ -72,17 +72,17 @@ class OVHSMSGateway(SMSResource):
                 }
             }
 
         ],
 
     }
 
     class Meta:
-        verbose_name = 'OVH'
+        verbose_name = _('OVH')
         db_table = 'sms_ovh'
 
     def send_msg(self, text, sender, destinations, **kwargs):
         """Send a SMS using the OVH provider"""
         destinations = self.clean_numbers(destinations,
                                           self.default_country_code,
                                           self.default_trunk_prefix)
 
@@ -122,8 +122,16 @@ class OVHSMSGateway(SMSResource):
                         ret['warning'] = ('credit level too low for %s: %s (threshold %s)' %
                                           (self.slug, credit_left, self.credit_threshold_alert))
                     ret['credit_left'] = credit_left
                     ret['ovh_result'] = result
                     ret['sms_ids'] = result.get('SmsIds', [])
                     return ret
                 else:
                     raise APIError('OVH error: %r' % result)
+
+
+class OVHSMS(SMS):
+    gateway = models.ForeignKey(OVHSMSGateway, related_name='sms_set')
+
+    class Meta:
+        verbose_name = _('OVH SMS')
+        db_table = 'sms_ovh_sms'
diff --git a/passerelle/apps/oxyd/migrations/0009_oxydsms.py b/passerelle/apps/oxyd/migrations/0009_oxydsms.py
new file mode 100644
index 00000000..aa5f62ec
--- /dev/null
+++ b/passerelle/apps/oxyd/migrations/0009_oxydsms.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-04-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('oxyd', '0008_oxydsmsgateway_max_message_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OxydSMS',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=256)),
+                ('sender', models.CharField(blank=True, max_length=128)),
+                ('destination', models.CharField(blank=True, max_length=256)),
+                ('stop', models.BooleanField(default=False)),
+                ('status', models.CharField(choices=[('pending', 'pending'), ('pushed', 'pushed'), ('error', 'error')], default='pending', max_length=32, null=True)),
+                ('status_details', models.CharField(blank=True, max_length=256)),
+                ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sms_set', to='oxyd.OxydSMSGateway')),
+            ],
+            options={
+                'verbose_name': 'Oxyd SMS',
+                'db_table': 'sms_oxyd_sms',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/oxyd/models.py b/passerelle/apps/oxyd/models.py
index 5e945b16..e7bf0490 100644
--- a/passerelle/apps/oxyd/models.py
+++ b/passerelle/apps/oxyd/models.py
@@ -1,15 +1,15 @@
 import requests
 
 from django.db import models
 from django.utils.encoding import force_text
 
 from passerelle.utils.jsonresponse import APIError
-from passerelle.base.models import SMSResource
+from passerelle.base.models import SMSResource, SMS
 from django.utils.translation import ugettext_lazy as _
 
 
 class OxydSMSGateway(SMSResource):
     username = models.CharField(verbose_name=_('Username'), max_length=64)
     password = models.CharField(verbose_name=_('Password'), max_length=64)
     default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3,
                                             default=u'33')
@@ -43,17 +43,17 @@ class OxydSMSGateway(SMSResource):
                     'data': None,
                 }
             }
         ],
     }
     URL = 'http://sms.oxyd.fr/send.php'
 
     class Meta:
-        verbose_name = 'Oxyd'
+        verbose_name = _('Oxyd')
         db_table = 'sms_oxyd'
 
     def send_msg(self, text, sender, destinations, **kwargs):
         """Send a SMS using the Oxyd provider"""
         # unfortunately it lacks a batch API...
         destinations = self.clean_numbers(destinations,
                                           self.default_country_code,
                                           self.default_trunk_prefix)
@@ -81,8 +81,16 @@ class OxydSMSGateway(SMSResource):
                 'OXYD error: some destinations failed', data=list(zip(destinations, results)))
         return None
 
     def get_sms_left(self, type='standard'):
         raise NotImplementedError
 
     def get_money_left(self):
         raise NotImplementedError
+
+
+class OxydSMS(SMS):
+    gateway = models.ForeignKey(OxydSMSGateway, related_name='sms_set')
+
+    class Meta:
+        verbose_name = _('Oxyd SMS')
+        db_table = 'sms_oxyd_sms'
diff --git a/passerelle/apps/twilio/migrations/0002_twiliosms.py b/passerelle/apps/twilio/migrations/0002_twiliosms.py
new file mode 100644
index 00000000..2cc0c767
--- /dev/null
+++ b/passerelle/apps/twilio/migrations/0002_twiliosms.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-04-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('twilio', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='TwilioSMS',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('text', models.CharField(blank=True, max_length=256)),
+                ('sender', models.CharField(blank=True, max_length=128)),
+                ('destination', models.CharField(blank=True, max_length=256)),
+                ('stop', models.BooleanField(default=False)),
+                ('status', models.CharField(choices=[('pending', 'pending'), ('pushed', 'pushed'), ('error', 'error')], default='pending', max_length=32, null=True)),
+                ('status_details', models.CharField(blank=True, max_length=256)),
+                ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sms_set', to='twilio.TwilioSMSGateway')),
+            ],
+            options={
+                'verbose_name': 'Twilio SMS',
+                'db_table': 'sms_twilio_sms',
+            },
+        ),
+    ]
diff --git a/passerelle/apps/twilio/models.py b/passerelle/apps/twilio/models.py
index 807c59b9..c2f7ea67 100644
--- a/passerelle/apps/twilio/models.py
+++ b/passerelle/apps/twilio/models.py
@@ -15,27 +15,27 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import requests
 from requests.auth import HTTPBasicAuth
 
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
 from passerelle.utils.jsonresponse import APIError
-from passerelle.base.models import SMSResource
+from passerelle.base.models import SMSResource, SMS
 
 
 class TwilioSMSGateway(SMSResource):
     account_sid = models.CharField(verbose_name=_('Account Sid'), max_length=64)
     auth_token = models.CharField(verbose_name=_('Auth Token'), max_length=64)
 
     manager_view_template_name = 'passerelle/manage/messages_service_view.html'
 
     class Meta:
-        verbose_name = 'Twilio'
+        verbose_name = _('Twilio')
         db_table = 'sms_twilio'
 
     TEST_DEFAULTS = {
         'create_kwargs': {
             'account_sid': 'ACxxx',
             'auth_token': 'yyy',
         },
         'test_vectors': [
@@ -83,8 +83,16 @@ class TwilioSMSGateway(SMSResource):
                     results.append('Twilio error: %s' % resp.text)
                 else:
                     results.append(0)
         if any(results):
             raise APIError(
                 'Twilio error: some destinations failed',
                 data=list(zip(destinations, results)))
         return None
+
+
+class TwilioSMS(SMS):
+    gateway = models.ForeignKey(TwilioSMSGateway, related_name='sms_set')
+
+    class Meta:
+        verbose_name = _('Twilio SMS')
+        db_table = 'sms_twilio_sms'
diff --git a/passerelle/base/models.py b/passerelle/base/models.py
index 9d9365db..4ab5efcb 100644
--- a/passerelle/base/models.py
+++ b/passerelle/base/models.py
@@ -965,16 +965,38 @@ class SMSResource(BaseResource):
         result = {'data': self.send_msg(data['message'], data['from'], data['to'], stop=stop)}
         SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug)
         return result
 
     class Meta:
         abstract = True
 
 
+class SMS(models.Model):
+    STATUS_PENDING = 'pending'
+    STATUS_PUSHED = 'pushed'
+    STATUS_ERROR = 'error'
+
+    STATUSES = [
+        (STATUS_PENDING, _('pending')),
+        (STATUS_PUSHED, _('pushed')),
+        (STATUS_ERROR, _('error')),
+    ]
+
+    text = models.CharField(max_length=256, blank=True)
+    sender = models.CharField(max_length=128, blank=True)
+    destination = models.CharField(max_length=256, blank=True)
+    stop = models.BooleanField(default=False)
+    status = models.CharField(max_length=32, null=True, choices=STATUSES, default=STATUS_PENDING)
+    status_details = models.CharField(max_length=256, blank=True)
+
+    class Meta:
+        abstract = True
+
+
 @six.python_2_unicode_compatible
 class SMSLog(models.Model):
     timestamp = models.DateTimeField(auto_now_add=True)
     appname = models.CharField(max_length=128, verbose_name='appname', null=True)
     slug = models.CharField(max_length=128, verbose_name='slug', null=True)
 
     def __str__(self):
         return '%s %s %s' % (self.timestamp, self.appname, self.slug)
-- 
2.26.2

