1
1
import re
2
2
3
3
import pymongo
4
+ from bson import SON
4
5
from bson .dbref import DBRef
5
6
from pymongo .read_preferences import ReadPreference
6
7
7
8
from mongoengine import signals
8
9
from mongoengine .base import (
9
10
BaseDict ,
10
11
BaseDocument ,
12
+ SaveableBaseField ,
11
13
BaseList ,
12
14
DocumentMetaclass ,
13
15
EmbeddedDocumentList ,
@@ -385,44 +387,34 @@ def save(
385
387
the cascade save using cascade_kwargs which overwrites the
386
388
existing kwargs with custom values.
387
389
"""
388
- signal_kwargs = signal_kwargs or {}
389
-
390
- if self ._meta .get ("abstract" ):
391
- raise InvalidDocumentError ("Cannot save an abstract document." )
392
-
393
- signals .pre_save .send (self .__class__ , document = self , ** signal_kwargs )
394
-
395
- if validate :
396
- self .validate (clean = clean )
397
-
398
- if write_concern is None :
399
- write_concern = {}
390
+ # Used to avoid saving a document that is already saving (infinite loops)
391
+ # this can be caused by the cascade save and circular references
392
+ if getattr (self , "_is_saving" , False ):
393
+ return
394
+ self ._is_saving = True
400
395
401
- doc_id = self . to_mongo ( fields = [ self . _meta [ "id_field" ]])
402
- created = "_id" not in doc_id or self . _created or force_insert
396
+ try :
397
+ signal_kwargs = signal_kwargs or {}
403
398
404
- signals .pre_save_post_validation .send (
405
- self .__class__ , document = self , created = created , ** signal_kwargs
406
- )
407
- # it might be refreshed by the pre_save_post_validation hook, e.g., for etag generation
408
- doc = self .to_mongo ()
399
+ if write_concern is None :
400
+ write_concern = {}
409
401
410
- if self ._meta .get ("auto_create_index" , True ):
411
- self .ensure_indexes ()
412
-
413
- try :
414
- # Save a new document or update an existing one
415
- if created :
416
- object_id = self ._save_create (doc , force_insert , write_concern )
417
- else :
418
- object_id , created = self ._save_update (
419
- doc , save_condition , write_concern
420
- )
402
+ if self ._meta .get ("abstract" ):
403
+ raise InvalidDocumentError ("Cannot save an abstract document." )
421
404
405
+ # Cascade save before validation to avoid child not existing errors
422
406
if cascade is None :
423
407
cascade = self ._meta .get ("cascade" , False ) or cascade_kwargs is not None
424
408
409
+ has_placeholder_saved = False
410
+
425
411
if cascade :
412
+ # If a cascade will occur save a placeholder version of this document to
413
+ # avoid issues with cyclic saves if this doc has not been created yet
414
+ if self .id is None :
415
+ self ._save_place_holder (force_insert , write_concern )
416
+ has_placeholder_saved = True
417
+
426
418
kwargs = {
427
419
"force_insert" : force_insert ,
428
420
"validate" : validate ,
@@ -434,31 +426,74 @@ def save(
434
426
kwargs ["_refs" ] = _refs
435
427
self .cascade_save (** kwargs )
436
428
437
- except pymongo .errors .DuplicateKeyError as err :
438
- message = "Tried to save duplicate unique keys (%s)"
439
- raise NotUniqueError (message % err )
440
- except pymongo .errors .OperationFailure as err :
441
- message = "Could not save document (%s)"
442
- if re .match ("^E1100[01] duplicate key" , str (err )):
443
- # E11000 - duplicate key error index
444
- # E11001 - duplicate key on update
429
+ # update force_insert to reflect that we might have already run the insert for
430
+ # the placeholder
431
+ force_insert = force_insert and not has_placeholder_saved
432
+
433
+ signals .pre_save .send (self .__class__ , document = self , ** signal_kwargs )
434
+
435
+ if validate :
436
+ self .validate (clean = clean )
437
+
438
+ doc_id = self .to_mongo (fields = [self ._meta ["id_field" ]])
439
+ created = "_id" not in doc_id or self ._created or force_insert
440
+
441
+ signals .pre_save_post_validation .send (
442
+ self .__class__ , document = self , created = created , ** signal_kwargs
443
+ )
444
+ # it might be refreshed by the pre_save_post_validation hook, e.g., for etag generation
445
+ doc = self .to_mongo ()
446
+
447
+ if self ._meta .get ("auto_create_index" , True ):
448
+ self .ensure_indexes ()
449
+
450
+ try :
451
+ # Save a new document or update an existing one
452
+ if created :
453
+ object_id = self ._save_create (doc , force_insert , write_concern )
454
+ else :
455
+ object_id , created = self ._save_update (
456
+ doc , save_condition , write_concern
457
+ )
458
+ except pymongo .errors .DuplicateKeyError as err :
445
459
message = "Tried to save duplicate unique keys (%s)"
446
460
raise NotUniqueError (message % err )
447
- raise OperationError (message % err )
461
+ except pymongo .errors .OperationFailure as err :
462
+ message = "Could not save document (%s)"
463
+ if re .match ("^E1100[01] duplicate key" , str (err )):
464
+ # E11000 - duplicate key error index
465
+ # E11001 - duplicate key on update
466
+ message = "Tried to save duplicate unique keys (%s)"
467
+ raise NotUniqueError (message % err )
468
+ raise OperationError (message % err )
469
+
470
+ # Make sure we store the PK on this document now that it's saved
471
+ id_field = self ._meta ["id_field" ]
472
+ if created or id_field not in self ._meta .get ("shard_key" , []):
473
+ self [id_field ] = self ._fields [id_field ].to_python (object_id )
474
+
475
+ signals .post_save .send (
476
+ self .__class__ , document = self , created = created , ** signal_kwargs
477
+ )
448
478
449
- # Make sure we store the PK on this document now that it's saved
450
- id_field = self ._meta ["id_field" ]
451
- if created or id_field not in self ._meta .get ("shard_key" , []):
452
- self [id_field ] = self ._fields [id_field ].to_python (object_id )
479
+ self ._clear_changed_fields ()
480
+ self ._created = False
481
+ except Exception as e :
482
+ raise e
483
+ finally :
484
+ self ._is_saving = False
453
485
454
- signals .post_save .send (
455
- self .__class__ , document = self , created = created , ** signal_kwargs
456
- )
486
+ return self
457
487
458
- self ._clear_changed_fields ()
459
- self ._created = False
488
+ def _save_place_holder (self , force_insert , write_concern ):
489
+ """Save a temp placeholder to the db with nothing but the ID.
490
+ """
491
+ data = SON ()
460
492
461
- return self
493
+ object_id = self ._save_create (data , force_insert , write_concern )
494
+
495
+ id_field = self ._meta ["id_field" ]
496
+ self [id_field ] = self ._fields [id_field ].to_python (object_id )
462
497
463
498
def _save_create (self , doc , force_insert , write_concern ):
464
499
"""Save a new document.
@@ -556,28 +591,11 @@ def cascade_save(self, **kwargs):
556
591
"""Recursively save any references and generic references on the
557
592
document.
558
593
"""
559
- _refs = kwargs .get ("_refs" ) or []
560
-
561
- ReferenceField = _import_class ("ReferenceField" )
562
- GenericReferenceField = _import_class ("GenericReferenceField" )
563
594
564
595
for name , cls in self ._fields .items ():
565
- if not isinstance (cls , (ReferenceField , GenericReferenceField )):
566
- continue
567
-
568
- ref = self ._data .get (name )
569
- if not ref or isinstance (ref , DBRef ):
596
+ if not isinstance (cls , SaveableBaseField ):
570
597
continue
571
-
572
- if not getattr (ref , "_changed_fields" , True ):
573
- continue
574
-
575
- ref_id = f"{ ref .__class__ .__name__ } ,{ str (ref ._data )} "
576
- if ref and ref_id not in _refs :
577
- _refs .append (ref_id )
578
- kwargs ["_refs" ] = _refs
579
- ref .save (** kwargs )
580
- ref ._changed_fields = []
598
+ cls .save (self , ** kwargs )
581
599
582
600
@property
583
601
def _qs (self ):
0 commit comments