diff --git a/CHANGES.rst b/CHANGES.rst index e97de57..adf4092 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,18 @@ Change Log ========== +0.9.0 +----- +* Added Django 1.8 compatibility. The current implementation has a + hackish (but functional) implementation to use PostgreSQL's + `array_agg` function. Pull requests are welcome. +* Retained compatibility with Django 1.7, though we still depend on the + `dbarray` package for this even though not strictly required with + Django 1.8. Once again, pull requests are welcome. + 0.8.1 ----- -* Add missing dependency on `pybars3` used to render boilerplate HTML +* Add missing dependency on `pybars3` used to render boilerplate HTML template when serving Meteor application files. 0.8.0 @@ -14,7 +23,7 @@ Change Log 0.7.0 ----- -* Refactor serialization to improve performance through reduced number +* Refactor serialization to improve performance through reduced number of database queries, especially on sub/unsub. * Fix login/logout user subscription, now emitting user `added`/ `removed` upon `login`/`logout` respectively. @@ -28,9 +37,9 @@ Change Log ----- * Send `removed` messages when client unsubscribes from publications. * Add support for SSL options and --settings=SETTINGS args in dddp tool. -* Add `optional` and `label` attributes to ManyToManyField simple +* Add `optional` and `label` attributes to ManyToManyField simple schema. -* Check order of added/changed when emitting WebSocket frames rather +* Check order of added/changed when emitting WebSocket frames rather than when queuing messages. * Move test projects into path that can be imported post install. @@ -40,7 +49,7 @@ Change Log 0.6.2 ----- -* Bugfix issue where DDP connection thread stops sending messages after +* Bugfix issue where DDP connection thread stops sending messages after changing item that has subscribers for other connections but not self. 0.6.1 @@ -49,7 +58,7 @@ Change Log * Dump stack trace to console on error for easier debugging DDP apps. * Fix handing of F expressions in object change handler. * Send `nosub` in response to invalid subscription request. -* Per connection tracking of sent objects so changed/added sent +* Per connection tracking of sent objects so changed/added sent appropriately. 0.6.0 diff --git a/README.rst b/README.rst index 1e87a46..9fd100c 100644 --- a/README.rst +++ b/README.rst @@ -18,11 +18,12 @@ Peer servers subscribe to aggregate broadcast events which are de-multiplexed an Limitations ----------- -The current release series only supports DDP via WebSockets_. Future -development may resolve this by using SockJS, to support browsers that -don't have WebSockets. +* No support for the SockJS protocol to support browsers that + don't have WebSockets_ (see http://caniuse.com/websockets for + supported browsers). -Changes must be made via the Django ORM as django-ddp uses `Django signals`_ to receive model save/update signals. +* Changes must be made via the Django ORM as django-ddp uses `Django + signals`_ to receive model save/update signals. Installation diff --git a/dddp/__init__.py b/dddp/__init__.py index a2d4dcd..7c3c404 100644 --- a/dddp/__init__.py +++ b/dddp/__init__.py @@ -26,10 +26,9 @@ REMOVED = 'removed' def greenify(): """Patch threading and psycopg2 modules for green threads.""" - if 'threading' in sys.modules: + from gevent.monkey import patch_all, saved + if ('threading' in sys.modules) and ('threading' not in saved): raise Exception('threading module loaded before patching!') - - from gevent.monkey import patch_all patch_all() from psycogreen.gevent import patch_psycopg diff --git a/dddp/api.py b/dddp/api.py index 603748d..cdce7b7 100644 --- a/dddp/api.py +++ b/dddp/api.py @@ -12,7 +12,10 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.db import connection, connections from django.db.models import aggregates, Q -from django.db.models.expressions import ExpressionNode +try: + from django.db.models.expressions import ExpressionNode +except ImportError: + from django.db.models import Expression as ExpressionNode from django.db.models.sql import aggregates as sql_aggregates from django.utils.encoding import force_text from django.db import DatabaseError @@ -48,6 +51,7 @@ class Array(aggregates.Aggregate): """Array aggregate function.""" func = 'ARRAY' + function = 'array_agg' name = 'Array' def add_to_query(self, query, alias, col, source, is_summary): @@ -64,9 +68,17 @@ class Array(aggregates.Aggregate): return 'ArrayType' new_source = ArrayField() - super(Array, self).add_to_query( - query, alias, col, new_source, is_summary, - ) + try: + super(Array, self).add_to_query( + query, alias, col, new_source, is_summary, + ) + except AttributeError: + query.aggregates[alias] = new_source + + def convert_value(self, value, expression, connection, context): + if not value: + return [] + return value def api_endpoint(path_or_func): diff --git a/dddp/apps.py b/dddp/apps.py index 9cbd8bc..6692e6b 100644 --- a/dddp/apps.py +++ b/dddp/apps.py @@ -4,11 +4,8 @@ from __future__ import print_function from django.apps import AppConfig from django.conf import settings, ImproperlyConfigured -from django.db import DatabaseError -from django.db.models import signals from dddp import autodiscover -from dddp.models import Connection class DjangoDDPConfig(AppConfig): diff --git a/dddp/msg.py b/dddp/msg.py index 892f49f..4029b5b 100644 --- a/dddp/msg.py +++ b/dddp/msg.py @@ -1,16 +1,22 @@ """Django DDP utils for DDP messaging.""" from copy import deepcopy from dddp import THREAD_LOCAL as this, REMOVED -from django.db.models.expressions import ExpressionNode +try: + from django.db.models.expressions import ExpressionNode +except AttributeError: + ExpressionNode = None 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 ExpressionNode is None: + exps = False + else: + # 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) diff --git a/setup.py b/setup.py index 99d473e..c768106 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup, find_packages setup( name='django-ddp', - version='0.8.1', + version='0.9.0', description=__doc__, long_description=open('README.rst').read(), author='Tyson Clugg', @@ -14,10 +14,10 @@ setup( packages=find_packages(), include_package_data=True, install_requires=[ - 'Django>=1.7,<1.8', + 'Django>=1.7', 'psycopg2>=2.5.4', 'gevent>=1.0', - 'gevent-websocket>=0.9', + 'gevent-websocket>=0.9,!=0.9.4', 'meteor-ejson>=1.0', 'psycogreen>=1.0', 'django-dbarray>=0.2',