Matthew Schinckel avatar Matthew Schinckel committed dbb4069

Add support for MessageSet: which allows for aggregating messages for status updates.

Comments (0)

Files changed (5)

sms/migrations/0004_auto__add_messageset.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'MessageSet'
+        db.create_table('sms_messageset', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('uuid', self.gf('uuidfield.fields.UUIDField')(max_length=36, auto=True, null=True, blank=True)),
+            ('data', self.gf('jsonfield.fields.JSONField')(default='{}')),
+        ))
+        db.send_create_signal('sms', ['MessageSet'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'MessageSet'
+        db.delete_table('sms_messageset')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'rank': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '5'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'sms.gateway': {
+            'Meta': {'object_name': 'Gateway'},
+            'base_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            'charge_keyword': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'content_keyword': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'error_format': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+            'query_balance_params': ('jsonfield.fields.JSONField', [], {'default': '[]'}),
+            'query_balance_response_format': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'query_balance_url': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'recipient_keyword': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'reply_content': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'reply_date': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'reply_date_format': ('django.db.models.fields.CharField', [], {'default': "'%Y-%m-%d %H:%M:%S'", 'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'reply_sender': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'settings': ('jsonfield.fields.JSONField', [], {'default': '{}'}),
+            'status_date': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'status_date_format': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'status_error_code': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'status_mapping': ('jsonfield.fields.JSONField', [], {'default': '{}'}),
+            'status_msg_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'status_status': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'success_format': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'uuid_keyword': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
+        },
+        'sms.message': {
+            'Meta': {'ordering': "('send_date',)", 'object_name': 'Message'},
+            'billed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'delivery_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'gateway': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sms.Gateway']", 'null': 'True', 'blank': 'True'}),
+            'gateway_charge': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '10', 'decimal_places': '5', 'blank': 'True'}),
+            'gateway_message_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'recipient_number': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'reply_callback': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}),
+            'send_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sent_sms_messages'", 'to': "orm['auth.User']"}),
+            'status': ('django.db.models.fields.CharField', [], {'default': "'Unsent'", 'max_length': '16'}),
+            'status_message': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'uuid': ('uuidfield.fields.UUIDField', [], {'max_length': '36', 'auto': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'sms.messageset': {
+            'Meta': {'object_name': 'MessageSet'},
+            'data': ('jsonfield.fields.JSONField', [], {'default': '{}'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'uuid': ('uuidfield.fields.UUIDField', [], {'max_length': '36', 'auto': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'sms.reply': {
+            'Meta': {'object_name': 'Reply'},
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'message': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'replies'", 'to': "orm['sms.Message']"})
+        }
+    }
+
+    complete_apps = ['sms']

sms/models/__init__.py

     :members:
 .. autoclass:: Message
     :members:
+.. autoclass:: MessageSet
+    :members:
 .. autoclass:: Reply
     :members:
     
 """
 from gateway import Gateway
-from message import Message
+from message import Message, MessageSet
 from reply import Reply

sms/models/message.py

+from decimal import Decimal
+
 from django.db import models
 from django.contrib.contenttypes import generic
 from django.utils.translation import ugettext as _
 
 import uuidfield.fields
 import picklefield
+import jsonfield
+
 if 'timezones' in settings.INSTALLED_APPS:
     from timezones.utils import adjust_datetime_to_timezone
 else:
             self.sender,
             self.send_date,
             self.length
-        )
+        )
+
+class MessageSet(models.Model):
+    """
+    A way of aggregating messages into a group, for the intended purpose
+    of being able to get status updates on a batch of messages in one go.
+    """
+    uuid = uuidfield.fields.UUIDField(auto=True)
+    data = jsonfield.fields.JSONField(default={})
+    
+    class Meta:
+        app_label = 'sms'
+    
+    def get_messages(self):
+        return Message.objects.filter(uuid__in=[x['uuid'] for x in self.data.keys()])
+    
+    def get_unsent(self):
+        return [x for x in self.data if x['status'] == "Unsent"]
+        
+    @property
+    def percentage_complete(self):
+        message_count = Decimal(len(self.data))
+        unsent_count = len(self.get_unsent())
+        
+        if not unsent_count:
+            return 100
+        
+        return (100 * (1 - unsent_count / message_count)).quantize('0.01')
+    
+    def update_data(self):
+        changed = False
+        for msg in self.get_messages():
+            data = self.data[msg.uuid]
+            if 'status_message' not in data or 'status' not in data or msg.status != data['status'] or msg.status_message != data['status_message']:
+                data['status'] = msg.status
+                data['status_message'] = msg.status_message
+                changed = True
+        if changed:
+            self.save()
+    
+    def is_complete(self):
+        return not self.get_unsent()
+    
+    @models.permalink
+    def get_absolute_url(self):
+        return reverse('messageset-status', self.uuid)
 urlpatterns = patterns('',
     url('^status_postback/$', views.update_delivery_status, name='status_postback'),
     url('^reply_postback/$', views.handle_reply, name='reply_postback'),
+    url('^status/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', views.get_message_set_status, name='messageset-status'),
 )
 from django.http import HttpResponse
 from django.views.decorators.csrf import csrf_exempt
+from django.utils import simplejson as json
+
+from decimal import Decimal
 import datetime
 import logging
 
-from sms.models import Message, Gateway
+from sms.models import Message, MessageSet, Gateway
 
 logger = logging.getLogger('sms-gateway')
 
         logger.debug("Callback found, running that.")
         msg.reply_callback(reply)
     
-    return HttpResponse('OK')
+    return HttpResponse('OK')
+
+
+def get_message_set_status(request, uuid):
+    message_set = MessageSet.objects.get(uuid=uuid)
+    message_set.update_data()
+    if message_set.is_complete():
+        return HttpResponse(json.dumps(message_set.data))
+    return HttpResponse(message_set.percentage_complete)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.