From b6a4388b93129b7c3a70ae5953b93608f8b9e997 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 27 Apr 2015 10:29:00 +1000 Subject: [PATCH 1/3] Move serializer factory into global thread context. --- dddp/__init__.py | 8 ++++++++ dddp/msg.py | 10 +++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/dddp/__init__.py b/dddp/__init__.py index 0047026..3a16a98 100644 --- a/dddp/__init__.py +++ b/dddp/__init__.py @@ -1,6 +1,7 @@ """Django/PostgreSQL implementation of the Meteor DDP service.""" from __future__ import unicode_literals import os.path +import sys from pkg_resources import get_distribution, DistributionNotFound from gevent.local import local from dddp import alea @@ -89,9 +90,16 @@ class RandomStreams(object): return self._streams[key] +def serializer_factory(): + """Make a new DDP serializer.""" + from django.core.serializers import get_serializer + return get_serializer('ddp')() + + THREAD_LOCAL = ThreadLocal( alea_random=alea.Alea, random_streams=RandomStreams, + serializer=serializer_factory, ) METEOR_ID_CHARS = u'23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz' diff --git a/dddp/msg.py b/dddp/msg.py index 9e0a3e4..01ba853 100644 --- a/dddp/msg.py +++ b/dddp/msg.py @@ -1,17 +1,13 @@ """Django DDP utils for DDP messaging.""" from dddp import THREAD_LOCAL as this -from django.core.serializers import get_serializer - - -def serializer_factory(): - """Make a new DDP serializer.""" - return get_serializer('ddp')() def obj_change_as_msg(obj, msg): """Generate a DDP msg for obj with specified msg type.""" - serializer = this.get('serializer', serializer_factory) + serializer = this.serializer data = serializer.serialize([obj])[0] + + # collection name is . name = data['model'] # cast ID as string From abe6b12ece9079553606605589aa7bb48e244488 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 27 Apr 2015 22:26:39 +1000 Subject: [PATCH 2/3] Allow `fresh` connections from browsers that have not established a session in the database yet, also allow subscriptions from unauthenticated sessions (but don\'t show any data for collections that have user_rel items defined). --- dddp/api.py | 6 ++++-- dddp/migrations/0005_auto_20150427_1209.py | 21 +++++++++++++++++++++ dddp/models.py | 2 +- dddp/websocket.py | 4 ++-- 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 dddp/migrations/0005_auto_20150427_1209.py diff --git a/dddp/api.py b/dddp/api.py index 05a1447..a2bee16 100644 --- a/dddp/api.py +++ b/dddp/api.py @@ -191,6 +191,8 @@ class Collection(APIMixin): qs = self.get_queryset(qs) user_rels = self.user_rel if user_rels: + if user is None: + return qs.none() # no user but we need one: return no objects. if isinstance(user_rels, basestring): user_rels = [user_rels] user_filter = None @@ -399,9 +401,9 @@ class DDP(APIMixin): this.error('Invalid publication name: %r' % name) return obj, created = Subscription.objects.get_or_create( - connection=this.ws.connection, + connection_id=this.ws.connection.pk, sub_id=id_, - user=this.request.user, + user_id=this.request.user.pk, defaults={ 'publication': pub.name, 'publication_class': '%s.%s' % ( diff --git a/dddp/migrations/0005_auto_20150427_1209.py b/dddp/migrations/0005_auto_20150427_1209.py new file mode 100644 index 0000000..c7adc86 --- /dev/null +++ b/dddp/migrations/0005_auto_20150427_1209.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('dddp', '0004_connection_server_addr'), + ] + + operations = [ + migrations.AlterField( + model_name='subscription', + name='user', + field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True), + preserve_default=True, + ), + ] diff --git a/dddp/models.py b/dddp/models.py index 2dc1cf9..8559ad7 100644 --- a/dddp/models.py +++ b/dddp/models.py @@ -136,7 +136,7 @@ class Subscription(models.Model, object): _publication_cache = {} connection = models.ForeignKey(Connection) sub_id = models.CharField(max_length=17) - user = models.ForeignKey(settings.AUTH_USER_MODEL) + user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True) publication = models.CharField(max_length=255) publication_class = models.CharField(max_length=255) params_ejson = models.TextField(default='{}') diff --git a/dddp/websocket.py b/dddp/websocket.py index 0afe891..2f25385 100644 --- a/dddp/websocket.py +++ b/dddp/websocket.py @@ -123,7 +123,7 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication): this.send_msg = self.send_msg this.reply = self.reply this.error = self.error - this.session_key = this.request.session.session_key + this.request.session.save() this.remote_addr = self.remote_addr = \ '{0[REMOTE_ADDR]}:{0[REMOTE_PORT]}'.format( @@ -280,7 +280,7 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication): this.version = version this.support = support self.connection = Connection.objects.create( - session_id=this.session_key, + session_id=this.request.session.session_key, server_addr='%d:%s' % ( backend_pid, self.ws.handler.socket.getsockname(), From 8931025d53f472c3f1cb9c320eb796f0ea14274e Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Mon, 27 Apr 2015 22:30:52 +1000 Subject: [PATCH 3/3] Support serializing objects that are saved with F expressions by reading field values for F expressions from database explicitly before serializing. --- dddp/msg.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dddp/msg.py b/dddp/msg.py index 01ba853..5cf55de 100644 --- a/dddp/msg.py +++ b/dddp/msg.py @@ -1,9 +1,24 @@ """Django DDP utils for DDP messaging.""" +from copy import deepcopy from dddp import THREAD_LOCAL as this +from django.db.models.expressions import ExpressionNode def obj_change_as_msg(obj, msg): """Generate a DDP msg for obj with specified msg type.""" + # check for F expressions + exps = [ + name for name, val in vars(obj).items() + if isinstance(val, ExpressionNode) + ] + if exps: + # clone and update obj with values but only for the expression fields + obj = deepcopy(obj) + # Django 1.8 makes obj._meta public --> pylint: disable=W0212 + for name, val in obj._meta.model.objects.values(*exps).get(pk=obj.pk): + setattr(obj, name, val) + + # run serialization now that all fields are "concrete" (not F expressions) serializer = this.serializer data = serializer.serialize([obj])[0]