Skip to content

Commit 61178cf

Browse files
committed
Added a ModelClass.copy_model() class constructor to cover unsaved 'casting' between model types. Basically just copies over fields and relationships. Fixes scholrly#7.
1 parent 95b1bcf commit 61178cf

File tree

3 files changed

+38
-24
lines changed

3 files changed

+38
-24
lines changed

neo4django/models/base.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -150,26 +150,32 @@ def using(self):
150150
@classmethod
151151
def from_model(cls, neo_model):
152152
"""
153-
Factory method that essentially allows "casting" from model to model.
154-
The newly returned model instance is validated, but unsaved.
153+
Factory method that essentially allows "casting" from a saved model
154+
instance to another saved model instance. These instances are both
155+
represented by the same node in the graph, but allow different views
156+
of the same properties and relationships.
155157
"""
156158
if neo_model.node is not None:
157159
new_model = cls._neo4j_instance(neo_model.node)
158160
return new_model
159161
else:
160-
#clone field by field
161-
onto_field_names = [f.attname for f in neo_model._meta.fields]
162-
new_model = cls()
163-
for field in neo_model._meta.fields:
164-
name = field.attname
165-
if name not in onto_field_names or name in ('pk', 'id'): continue
166-
val = getattr(neo_model, name)
167-
if isinstance(val, models.Manager):
168-
for obj in val.all():
169-
getattr(new_model, name).add(obj)
170-
else:
171-
setattr(new_model, name, val)
172-
return new_model
162+
raise TypeError('from_model() only operates on saved models.')
163+
164+
165+
@classmethod
166+
def copy_model(cls, neo_model):
167+
onto_field_names = [f.attname for f in neo_model._meta.fields]
168+
new_model = cls()
169+
for field in neo_model._meta.fields:
170+
name = field.attname
171+
if name not in onto_field_names or name in ('pk', 'id'): continue
172+
val = getattr(neo_model, name)
173+
if isinstance(val, models.Manager):
174+
for obj in val.all():
175+
getattr(new_model, name).add(obj)
176+
else:
177+
setattr(new_model, name, val)
178+
return new_model
173179

174180
@classmethod
175181
def index(cls, using=DEFAULT_DB_ALIAS):

neo4django/tests/tests.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ class TestSubclass(neo4django.NodeModel):
260260

261261
assert not hasattr(n2, 'age'), "Age should not be defined, as the new class didn't define it."
262262

263-
def test_saved_model_casting():
264-
"""Tests functional model to model "casting"."""
263+
def test_model_casting():
264+
"""Tests functional saved model to model "casting"."""
265265
#create a model similar to person, but with relationships
266266
class Doppelganger(neo4django.NodeModel):
267267
name = neo4django.StringProperty()
@@ -285,14 +285,22 @@ class Vierfachganger(neo4django.NodeModel):
285285
double_imposter = Vierfachganger.from_model(imposter)
286286
eq_(abe, double_imposter.original)
287287

288+
def test_model_casting_validation():
289+
raise NotImplementedError('Write this test!')
290+
288291
def test_model_copy():
289-
pass
292+
class NameOwner(neo4django.NodeModel):
293+
name = neo4django.StringProperty()
294+
confidantes = neo4django.Relationship(Person, neo4django.Outgoing.KNOWS)
290295

291-
def test_unsaved_model_casting():
292-
pass
296+
pete = Person(name='Pete')
297+
pete2 = NameOwner.copy_model(pete)
298+
eq_(pete.name, pete2.name)
293299

294-
def test_model_casting_validation():
295-
raise NotImplementedError('Write this test!')
300+
pete2.confidantes.add(pete)
301+
pete3 = NameOwner.copy_model(pete2)
302+
assert pete in list(pete3.confidantes.all()),\
303+
"Copying isn't commuting relationships!"
296304

297305
def test_array_property_validator():
298306
"""Tests that ArrayProperty validates properly."""

reg_settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
'test_filter_lte',
5454
'test_filter_date_range',
5555
'test_model_casting',
56-
'test_model_casting_validation']
56+
'test_model_casting_validation',
57+
'test_model_copy']
5758
should_fail=[
5859
'test_dates',
5960
'test_filter_iexact',
@@ -64,5 +65,4 @@
6465
'test_str_array_property_validator',
6566
'test_url_array_property_validator',
6667
'test_type_query',
67-
'test_model_casting',
6868
'test_model_casting_validation']

0 commit comments

Comments
 (0)