Add models and various shortcuts to allow for AleaIdField with primary_key=True.

This commit is contained in:
Tyson Clugg 2015-07-14 09:31:42 +10:00
parent cb46caf9ff
commit 43b35a12ce
3 changed files with 105 additions and 14 deletions

View file

@ -104,7 +104,7 @@ class SubscriptionCollection(admin.ModelAdmin):
for name, attr in vars(models).items():
if hasattr(attr, '_meta'):
if hasattr(attr, '_meta') and not getattr(attr._meta, 'abstract'):
model_admin = locals().get(name, None)
if model_admin is not False:
admin.site.register(attr, model_admin)

View file

@ -28,7 +28,9 @@ import ejson
from dddp import (
AlreadyRegistered, THREAD_LOCAL as this, ADDED, CHANGED, REMOVED,
)
from dddp.models import Connection, Subscription, get_meteor_id, get_meteor_ids
from dddp.models import (
AleaIdField, Connection, Subscription, get_meteor_id, get_meteor_ids,
)
XMIN = {'select': {'xmin': "'xmin'"}}
@ -600,9 +602,12 @@ class DDP(APIMixin):
model_name=model_name(qs.model),
collection_name=col.name,
)
meteor_ids = get_meteor_ids(
qs.model, qs.values_list('pk', flat=True),
)
if isinstance(col.model._meta.pk, AleaIdField):
meteor_ids = None
else:
meteor_ids = get_meteor_ids(
qs.model, qs.values_list('pk', flat=True),
)
for obj in qs:
payload = col.obj_change_as_msg(obj, ADDED, meteor_ids)
this.send(payload)
@ -615,9 +620,12 @@ class DDP(APIMixin):
connection=this.ws.connection, sub_id=id_,
)
for col, qs in self.sub_unique_objects(sub):
meteor_ids = get_meteor_ids(
qs.model, qs.values_list('pk', flat=True),
)
if isinstance(col.model._meta.pk, AleaIdField):
meteor_ids = None
else:
meteor_ids = get_meteor_ids(
qs.model, qs.values_list('pk', flat=True),
)
for obj in qs:
payload = col.obj_change_as_msg(obj, REMOVED, meteor_ids)
this.send(payload)

View file

@ -23,10 +23,29 @@ def get_meteor_id(obj_or_model, obj_pk=None):
if model is ObjectMapping:
# this doesn't make sense - raise TypeError
raise TypeError("Can't map ObjectMapping instances through self.")
if obj_or_model is not model and obj_pk is None:
obj_pk = str(obj_or_model.pk)
# try getting value of AleaIdField straight from instance if possible
if isinstance(obj_or_model, model):
# obj_or_model is an instance, not a model.
if isinstance(meta.pk, AleaIdField):
return obj_or_model.pk
alea_unique_fields = [
field
for field in meta.local_fields
if isinstance(field, AleaIdField) and field.unique
]
if len(alea_unique_fields) == 1:
# found an AleaIdField with unique=True, assume it's got the value.
return getattr(obj_or_model, alea_unique_fields[0].attname)
if obj_pk is None:
# fall back to primary key, but coerce as string type for lookup.
obj_pk = str(obj_or_model.pk)
if obj_pk is None:
# bail out if args are (model, pk) but pk is None.
return None
# fallback to using AleaIdField from ObjectMapping model.
content_type = ContentType.objects.get_for_model(model)
try:
return ObjectMapping.objects.values_list(
@ -47,6 +66,13 @@ def get_meteor_id(obj_or_model, obj_pk=None):
def get_meteor_ids(model, object_ids):
"""Return Alea ID mapping for all given ids of specified model."""
content_type = ContentType.objects.get_for_model(model)
# Django model._meta is now public API -> pylint: disable=W0212
meta = model._meta
if isinstance(meta.pk, AleaIdField):
# primary_key is an AleaIdField, use it.
return collections.OrderedDict(
(obj_pk, obj_pk) for obj_pk in object_ids
)
result = collections.OrderedDict(
(str(obj_pk), None)
for obj_pk
@ -59,11 +85,10 @@ def get_meteor_ids(model, object_ids):
result[obj_pk] = meteor_id
for obj_pk, meteor_id in result.items():
if meteor_id is None:
# Django model._meta is now public API -> pylint: disable=W0212
result[obj_pk] = ObjectMapping.objects.create(
content_type=content_type,
object_id=obj_pk,
meteor_id=meteor_random_id('/collection/%s' % model._meta),
meteor_id=meteor_random_id('/collection/%s' % meta),
).meteor_id
return result
@ -71,10 +96,32 @@ def get_meteor_ids(model, object_ids):
@transaction.atomic
def get_object_id(model, meteor_id):
"""Return an object ID for the given meteor_id."""
if meteor_id is None:
return None
# Django model._meta is now public API -> pylint: disable=W0212
meta = model._meta
if model is ObjectMapping:
# this doesn't make sense - raise TypeError
raise TypeError("Can't map ObjectMapping instances through self.")
# Django model._meta is now public API -> pylint: disable=W0212
if isinstance(meta.pk, AleaIdField):
# meteor_id is the primary key
return meteor_id
alea_unique_fields = [
field
for field in meta.local_fields
if isinstance(field, AleaIdField) and field.unique
]
if len(alea_unique_fields) == 1:
# found an AleaIdField with unique=True, assume it's got the value.
return model.objects.values_list(
'pk', flat=True,
).get(**{
alea_unique_fields[0].attname: meteor_id,
})
content_type = ContentType.objects.get_for_model(model)
return ObjectMapping.objects.filter(
content_type=content_type,
@ -85,6 +132,12 @@ def get_object_id(model, meteor_id):
@transaction.atomic
def get_object(model, meteor_id, *args, **kwargs):
"""Return an object for the given meteor_id."""
# Django model._meta is now public API -> pylint: disable=W0212
meta = model._meta
if isinstance(meta.pk, AleaIdField):
# meteor_id is the primary key
return model.objects.filter(*args, **kwargs).get(pk=meteor_id)
return model.objects.filter(*args, **kwargs).get(
pk=get_object_id(model, meteor_id),
)
@ -97,11 +150,41 @@ class AleaIdField(models.CharField):
def __init__(self, *args, **kwargs):
"""Assume max_length of 17 to match Meteor implementation."""
kwargs.update(
default=meteor_random_id,
editable=False,
max_length=17,
)
super(AleaIdField, self).__init__(*args, **kwargs)
def deconstruct(self):
"""Return details on how this field was defined."""
name, path, args, kwargs = super(AleaIdField, self).deconstruct()
del kwargs['max_length']
return name, path, args, kwargs
def pre_save(self, model_instance, add):
"""Generate ID if required."""
_, _, _, kwargs = self.deconstruct()
val = getattr(model_instance, self.attname)
if val is None and kwargs.get('default', None) is None:
val = meteor_random_id('/collection/%s' % model_instance._meta)
setattr(model_instance, self.attname, val)
return val
class AleaIdMixin(models.Model):
"""Django model mixin that provides AleaIdField field (as _id)."""
id = AleaIdField(
primary_key=True,
)
class Meta(object):
"""Model meta options for AleaIdMixin."""
abstract = True
@python_2_unicode_compatible
class ObjectMapping(models.Model):