diff --git a/dddp/__init__.py b/dddp/__init__.py index 5bdea45..a7daaf3 100644 --- a/dddp/__init__.py +++ b/dddp/__init__.py @@ -52,16 +52,44 @@ class ThreadLocal(local): def get(self, name, factory, *factory_args, **factory_kwargs): """Get attribute, creating if required using specified factory.""" if not hasattr(self, name): - return setattr(self, name, factory(*factory_args, **factory_kwargs)) + obj = factory(*factory_args, **factory_kwargs) + setattr(self, name, obj) + return obj return getattr(self, name) -THREAD_LOCAL = ThreadLocal(alea_random=alea.Alea) +class RandomStreams(object): + + def __init__(self): + self._streams = {} + self._seed = THREAD_LOCAL.alea_random.hex_string(20) + + def get_seed(self): + return self._seed + def set_seed(self, val): + self._streams = {} + self._seed = val + random_seed = property(get_seed, set_seed) + + def __getitem__(self, key): + if key not in self._streams: + return self._streams.setdefault(key, alea.Alea(self._seed, key)) + return self._streams[key] + + +THREAD_LOCAL = ThreadLocal( + alea_random=alea.Alea, + random_streams=RandomStreams, +) METEOR_ID_CHARS = u'23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz' -def meteor_random_id(): - return THREAD_LOCAL.alea_random.random_string(17, METEOR_ID_CHARS) +def meteor_random_id(name=None): + if name is None: + stream = THREAD_LOCAL.alea_random + else: + stream = THREAD_LOCAL.random_streams[name] + return stream.random_string(17, METEOR_ID_CHARS) def autodiscover(): diff --git a/dddp/alea.py b/dddp/alea.py index 9a26e1b..22abd5d 100755 --- a/dddp/alea.py +++ b/dddp/alea.py @@ -155,6 +155,11 @@ class Alea(object): self.choice(alphabet) for n in range(length) ) + def hex_string(self, digits): + """Return a hex string of `digits` length.""" + return self.random_string(digits, '0123456789abcdef') + + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/dddp/migrations/0003_auto_20150413_1328.py b/dddp/migrations/0003_auto_20150413_1328.py new file mode 100644 index 0000000..470ffa6 --- /dev/null +++ b/dddp/migrations/0003_auto_20150413_1328.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dddp', '0002_auto_20150408_0321'), + ] + + operations = [ + migrations.AlterField( + model_name='objectmapping', + name='object_id', + field=models.CharField(max_length=255), + preserve_default=True, + ), + ] diff --git a/dddp/models.py b/dddp/models.py index 46b4e4f..a90be33 100644 --- a/dddp/models.py +++ b/dddp/models.py @@ -3,6 +3,7 @@ from django.db import models, transaction from django.conf import settings from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist from django.utils.encoding import python_2_unicode_compatible import ejson from dddp import meteor_random_id @@ -12,12 +13,22 @@ from dddp import meteor_random_id def get_meteor_id(obj): """Return an Alea ID for the given object.""" # Django model._meta is now public API -> pylint: disable=W0212 - content_type = ContentType.objects.get_for_model(obj._meta.model) - mapping, _ = ObjectMapping.objects.get_or_create( - content_type=content_type, - object_id=obj.pk, - ) - return mapping.meteor_id + meta = obj._meta + obj_pk = str(obj.pk) + content_type = ContentType.objects.get_for_model(meta.model) + try: + return ObjectMapping.objects.values_list( + 'meteor_id', flat=True, + ).get( + content_type=content_type, + object_id=obj_pk, + ) + except ObjectDoesNotExist: + return ObjectMapping.objects.create( + content_type=content_type, + object_id=obj_pk, + meteor_id=meteor_random_id('/collection/%s' % meta), + ).meteor_id @transaction.atomic @@ -51,7 +62,7 @@ class ObjectMapping(models.Model): meteor_id = AleaIdField() content_type = models.ForeignKey(ContentType, db_index=True) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=255) # content_object = GenericForeignKey('content_type', 'object_id') def __str__(self): diff --git a/dddp/notify.py b/dddp/notify.py index e979a7d..29ba0a1 100644 --- a/dddp/notify.py +++ b/dddp/notify.py @@ -8,7 +8,7 @@ from django.db import connections from django.utils.module_loading import import_string from dddp.api import collection_name -from dddp.models import Subscription +from dddp.models import get_meteor_id, Subscription from dddp.msg import obj_change_as_msg @@ -49,6 +49,7 @@ def send_notify(model, obj, msg, using): sub_ids.add(sub.sub_id) if not sub_ids: + get_meteor_id(obj) # force creation of meteor ID using randomSeed return # no subscribers for this object, nothing more to do. name, payload = obj_change_as_msg(obj, msg) diff --git a/dddp/websocket.py b/dddp/websocket.py index 5a0ce20..903b2b7 100644 --- a/dddp/websocket.py +++ b/dddp/websocket.py @@ -306,6 +306,7 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication): def recv_method(self, method, params, id_, randomSeed=None): """DDP method handler.""" if randomSeed is not None: + this.random_streams.random_seed = randomSeed this.alea_random = alea.Alea(randomSeed) API.method(method, params, id_) self.reply('updated', methods=[id_])