From acd2b0ef77ccde44d518a998a78a6dfc3d52b43a Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Thu, 20 Oct 2016 00:04:07 -0400 Subject: [PATCH 1/9] First attempt for selective field updates --- beets/dbcore/db.py | 6 ++++-- beets/library.py | 19 ++++++++++--------- beets/ui/commands.py | 23 +++++++++++++++-------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index daa636d24..37ec1a347 100644 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -342,7 +342,7 @@ class Model(object): # Database interaction (CRUD methods). - def store(self): + def store(self, fields_to_store=None): """Save the object's metadata into the library database. """ self._check_db() @@ -350,7 +350,9 @@ class Model(object): # Build assignments for query. assignments = [] subvars = [] - for key in self._fields: + if fields_to_store is None: + fields_to_store = self._fields + for key in fields_to_store: if key != 'id' and key in self._dirty: self._dirty.remove(key) assignments.append(key + '=?') diff --git a/beets/library.py b/beets/library.py index 59784e808..ef079b6b6 100644 --- a/beets/library.py +++ b/beets/library.py @@ -323,8 +323,8 @@ class LibModel(dbcore.Model): funcs.update(plugins.template_funcs()) return funcs - def store(self): - super(LibModel, self).store() + def store(self, fields_to_store=None): + super(LibModel, self).store(fields_to_store) plugins.send('database_change', lib=self._db, model=self) def remove(self): @@ -729,7 +729,8 @@ class Item(LibModel): self._db._memotable = {} - def move(self, copy=False, link=False, basedir=None, with_album=True): + def move(self, copy=False, link=False, basedir=None, with_album=True, + fields_to_store=None): """Move the item to its designated location within the library directory (provided by destination()). Subdirectories are created as needed. If the operation succeeds, the item's path @@ -759,7 +760,7 @@ class Item(LibModel): # Perform the move and store the change. old_path = self.path self.move_file(dest, copy, link) - self.store() + self.store(fields_to_store) # If this item is in an album, move its art. if with_album: @@ -1000,7 +1001,7 @@ class Album(LibModel): util.prune_dirs(os.path.dirname(old_art), self._db.directory) - def move(self, copy=False, link=False, basedir=None): + def move(self, copy=False, link=False, basedir=None, fields_to_store=None): """Moves (or copies) all items to their destination. Any album art moves along with them. basedir overrides the library base directory for the destination. The album is stored to the @@ -1010,7 +1011,7 @@ class Album(LibModel): # Ensure new metadata is available to items for destination # computation. - self.store() + self.store(fields_to_store) # Move items. items = list(self.items()) @@ -1019,7 +1020,7 @@ class Album(LibModel): # Move art. self.move_art(copy, link) - self.store() + self.store(fields_to_store) def item_dir(self): """Returns the directory containing the album's first item, @@ -1108,7 +1109,7 @@ class Album(LibModel): plugins.send('art_set', album=self) - def store(self): + def store(self, fields_to_store=None): """Update the database with the album information. The album's tracks are also updated. """ @@ -1119,7 +1120,7 @@ class Album(LibModel): track_updates[key] = self[key] with self._db.transaction(): - super(Album, self).store() + super(Album, self).store(fields_to_store) if track_updates: for item in self.items(): for key, value in track_updates.items(): diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 2f323b604..88b5de728 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1081,11 +1081,14 @@ default_commands.append(list_cmd) # update: Update library contents according to on-disk tags. -def update_items(lib, query, album, move, pretend): +def update_items(lib, query, album, move, pretend, fields): """For all the items matched by the query, update the library to reflect the item's embedded tags. """ with lib.transaction(): + if fields is None: + fields = library.Item._media_fields + items, _ = _do_query(lib, query, album) # Walk through the items and pick up their changes. @@ -1125,23 +1128,23 @@ def update_items(lib, query, album, move, pretend): # Check for and display changes. changed = ui.show_model_changes(item, - fields=library.Item._media_fields) + fields=fields) # Save changes. if not pretend: if changed: # Move the item if it's in the library. if move and lib.directory in ancestry(item.path): - item.move() + item.move(fields_to_store=fields) - item.store() + item.store(fields_to_store=fields) affected_albums.add(item.album_id) else: # The file's mtime was different, but there were no # changes to the metadata. Store the new mtime, # which is set in the call to read(), so we don't # check this again in the future. - item.store() + item.store(fields_to_store=fields) # Skip album changes while pretending. if pretend: @@ -1160,17 +1163,17 @@ def update_items(lib, query, album, move, pretend): # Update album structure to reflect an item in it. for key in library.Album.item_keys: album[key] = first_item[key] - album.store() + album.store(fields_to_store=fields) # Move album art (and any inconsistent items). if move and lib.directory in ancestry(first_item.path): log.debug(u'moving album {0}', album_id) - album.move() + album.move(fields_to_store=fields) def update_func(lib, opts, args): update_items(lib, decargs(args), opts.album, ui.should_move(opts.move), - opts.pretend) + opts.pretend, opts.fields) update_cmd = ui.Subcommand( @@ -1190,6 +1193,10 @@ update_cmd.parser.add_option( u'-p', u'--pretend', action='store_true', help=u"show all changes but do nothing" ) +update_cmd.parser.add_option( + u'-F', u'--field', default=None, action='append', dest='fields', + help=u'list of fields to update' +) update_cmd.func = update_func default_commands.append(update_cmd) From 679918f191ae6b3255c918f212a4a0920f0b0076 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Thu, 20 Oct 2016 00:28:28 -0400 Subject: [PATCH 2/9] Fix some test failures --- test/test_ui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_ui.py b/test/test_ui.py index 31f3f37da..571f39cf8 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -499,12 +499,13 @@ class UpdateTest(_common.TestCase): self.album.store() os.remove(artfile) - def _update(self, query=(), album=False, move=False, reset_mtime=True): + def _update(self, query=(), album=False, move=False, reset_mtime=True, + fields=None): self.io.addinput('y') if reset_mtime: self.i.mtime = 0 self.i.store() - commands.update_items(self.lib, query, album, move, False) + commands.update_items(self.lib, query, album, move, False, fields=fields) def test_delete_removes_item(self): self.assertTrue(list(self.lib.items())) From f17601e4cd3c748e609a2bd76f310e31c4245395 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Thu, 20 Oct 2016 20:25:38 -0400 Subject: [PATCH 3/9] Fix update test failures --- beets/library.py | 2 +- beets/ui/commands.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/beets/library.py b/beets/library.py index ef079b6b6..edd79d243 100644 --- a/beets/library.py +++ b/beets/library.py @@ -767,7 +767,7 @@ class Item(LibModel): album = self.get_album() if album: album.move_art(copy) - album.store() + album.store(fields_to_store) # Prune vacated directory. if not copy: diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 88b5de728..823e99cfa 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1086,9 +1086,6 @@ def update_items(lib, query, album, move, pretend, fields): reflect the item's embedded tags. """ with lib.transaction(): - if fields is None: - fields = library.Item._media_fields - items, _ = _do_query(lib, query, album) # Walk through the items and pick up their changes. @@ -1127,8 +1124,9 @@ def update_items(lib, query, album, move, pretend, fields): item._dirty.discard(u'albumartist') # Check for and display changes. - changed = ui.show_model_changes(item, - fields=fields) + changed = ui.show_model_changes( + item, + fields=fields or library.Item._media_fields) # Save changes. if not pretend: From 406f3ce8437ffda09ccf25774f9e3f7ff9aa8707 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Thu, 20 Oct 2016 21:07:12 -0400 Subject: [PATCH 4/9] Add tests and make sure they pass --- beets/library.py | 3 ++- beets/ui/commands.py | 5 +++++ test/test_ui.py | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/beets/library.py b/beets/library.py index edd79d243..36e5a5d22 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1016,7 +1016,8 @@ class Album(LibModel): # Move items. items = list(self.items()) for item in items: - item.move(copy, link, basedir=basedir, with_album=False) + item.move(copy, link, basedir=basedir, with_album=False, + fields_to_store=fields_to_store) # Move art. self.move_art(copy, link) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 823e99cfa..d44bbadc6 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1086,6 +1086,11 @@ def update_items(lib, query, album, move, pretend, fields): reflect the item's embedded tags. """ with lib.transaction(): + if move and fields is not None and 'path' not in fields: + # Special case: if an item needs to be moved, the path field has to + # updated; otherwise the new path will not be reflected in the + # database + fields.append('path') items, _ = _do_query(lib, query, album) # Walk through the items and pick up their changes. diff --git a/test/test_ui.py b/test/test_ui.py index 571f39cf8..380b29173 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -550,6 +550,26 @@ class UpdateTest(_common.TestCase): item = self.lib.items().get() self.assertTrue(b'differentTitle' not in item.path) + def test_selective_modified_metadata_moved(self): + mf = MediaFile(self.i.path) + mf.title = u'differentTitle' + mf.genre = u'differentGenre' + mf.save() + self._update(move=True, fields=['title']) + item = self.lib.items().get() + self.assertTrue(b'differentTitle' in item.path) + self.assertNotEqual(item.genre, u'differentGenre') + + def test_selective_modified_metadata_not_moved(self): + mf = MediaFile(self.i.path) + mf.title = u'differentTitle' + mf.genre = u'differentGenre' + mf.save() + self._update(move=False, fields=['title']) + item = self.lib.items().get() + self.assertTrue(b'differentTitle' not in item.path) + self.assertNotEqual(item.genre, u'differentGenre') + def test_modified_album_metadata_moved(self): mf = MediaFile(self.i.path) mf.album = u'differentAlbum' From f42f558db22b81e80ba416327d24f346df45b4b4 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Thu, 20 Oct 2016 21:21:50 -0400 Subject: [PATCH 5/9] Fix long line for flake8 --- test/test_ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_ui.py b/test/test_ui.py index 380b29173..2a07db24d 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -505,7 +505,8 @@ class UpdateTest(_common.TestCase): if reset_mtime: self.i.mtime = 0 self.i.store() - commands.update_items(self.lib, query, album, move, False, fields=fields) + commands.update_items(self.lib, query, album, move, False, + fields=fields) def test_delete_removes_item(self): self.assertTrue(list(self.lib.items())) From 8d07bfe693ffc8e9bc6400c87d44b3a86d648287 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Thu, 20 Oct 2016 23:33:20 -0400 Subject: [PATCH 6/9] Documentation for new flag --- docs/reference/cli.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index 93b9b6253..b2683cd86 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -272,7 +272,7 @@ update `````` :: - beet update [-aM] QUERY + beet update [-F] FIELD [-aM] QUERY Update the library (and, optionally, move files) to reflect out-of-band metadata changes and file deletions. @@ -288,6 +288,11 @@ To perform a "dry run" of an update, just use the ``-p`` (for "pretend") flag. This will show you all the proposed changes but won't actually change anything on disk. +By default, all the changed metadata will be populated back to the database. +If you only want certain fields to be written, specify them with the ```-F``` +flags (which can be used multiple times). For the list of supported fields, +please see ```beet fields```. + When an updated track is part of an album, the album-level fields of *all* tracks from the album are also updated. (Specifically, the command copies album-level data from the first track on the album and applies it to the @@ -318,7 +323,7 @@ You can think of this command as the opposite of :ref:`update-cmd`. The ``-p`` option previews metadata changes without actually applying them. -The ``-f`` option forces a write to the file, even if the file tags match the database. This is useful for making sure that enabled plugins that run on write (e.g., the Scrub and Zero plugins) are run on the file. +The ``-f`` option forces a write to the file, even if the file tags match the database. This is useful for making sure that enabled plugins that run on write (e.g., the Scrub and Zero plugins) are run on the file. From f91362a069ab1ad3292b05f9fd29827578822205 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Fri, 21 Oct 2016 16:15:37 -0400 Subject: [PATCH 7/9] Cosmetic refactors based on feedback --- beets/dbcore/db.py | 10 ++++++---- beets/library.py | 24 +++++++++++++----------- beets/ui/commands.py | 14 ++++++++------ 3 files changed, 27 insertions(+), 21 deletions(-) mode change 100644 => 100755 beets/dbcore/db.py mode change 100644 => 100755 beets/library.py mode change 100644 => 100755 beets/ui/commands.py diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py old mode 100644 new mode 100755 index 37ec1a347..d01e8a5c3 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -342,17 +342,19 @@ class Model(object): # Database interaction (CRUD methods). - def store(self, fields_to_store=None): + def store(self, fields=None): """Save the object's metadata into the library database. + :param fields: the fields to be stored. If not specified, all fields + will be. """ + if fields is None: + fields = self._fields self._check_db() # Build assignments for query. assignments = [] subvars = [] - if fields_to_store is None: - fields_to_store = self._fields - for key in fields_to_store: + for key in fields: if key != 'id' and key in self._dirty: self._dirty.remove(key) assignments.append(key + '=?') diff --git a/beets/library.py b/beets/library.py old mode 100644 new mode 100755 index 36e5a5d22..e545bd314 --- a/beets/library.py +++ b/beets/library.py @@ -323,8 +323,8 @@ class LibModel(dbcore.Model): funcs.update(plugins.template_funcs()) return funcs - def store(self, fields_to_store=None): - super(LibModel, self).store(fields_to_store) + def store(self, fields=None): + super(LibModel, self).store(fields) plugins.send('database_change', lib=self._db, model=self) def remove(self): @@ -730,7 +730,7 @@ class Item(LibModel): self._db._memotable = {} def move(self, copy=False, link=False, basedir=None, with_album=True, - fields_to_store=None): + fields=None): """Move the item to its designated location within the library directory (provided by destination()). Subdirectories are created as needed. If the operation succeeds, the item's path @@ -760,14 +760,14 @@ class Item(LibModel): # Perform the move and store the change. old_path = self.path self.move_file(dest, copy, link) - self.store(fields_to_store) + self.store(fields) # If this item is in an album, move its art. if with_album: album = self.get_album() if album: album.move_art(copy) - album.store(fields_to_store) + album.store(fields) # Prune vacated directory. if not copy: @@ -1001,7 +1001,7 @@ class Album(LibModel): util.prune_dirs(os.path.dirname(old_art), self._db.directory) - def move(self, copy=False, link=False, basedir=None, fields_to_store=None): + def move(self, copy=False, link=False, basedir=None, fields=None): """Moves (or copies) all items to their destination. Any album art moves along with them. basedir overrides the library base directory for the destination. The album is stored to the @@ -1011,17 +1011,17 @@ class Album(LibModel): # Ensure new metadata is available to items for destination # computation. - self.store(fields_to_store) + self.store(fields) # Move items. items = list(self.items()) for item in items: item.move(copy, link, basedir=basedir, with_album=False, - fields_to_store=fields_to_store) + fields=fields) # Move art. self.move_art(copy, link) - self.store(fields_to_store) + self.store(fields) def item_dir(self): """Returns the directory containing the album's first item, @@ -1110,9 +1110,11 @@ class Album(LibModel): plugins.send('art_set', album=self) - def store(self, fields_to_store=None): + def store(self, fields=None): """Update the database with the album information. The album's tracks are also updated. + :param fields: The fields to be stored. If not specified, all fields + will be. """ # Get modified track fields. track_updates = {} @@ -1121,7 +1123,7 @@ class Album(LibModel): track_updates[key] = self[key] with self._db.transaction(): - super(Album, self).store(fields_to_store) + super(Album, self).store(fields) if track_updates: for item in self.items(): for key, value in track_updates.items(): diff --git a/beets/ui/commands.py b/beets/ui/commands.py old mode 100644 new mode 100755 index d44bbadc6..0c1eb3ae9 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1084,12 +1084,14 @@ default_commands.append(list_cmd) def update_items(lib, query, album, move, pretend, fields): """For all the items matched by the query, update the library to reflect the item's embedded tags. + :param fields: The fields to be stored. If not specified, all fields will + be. """ with lib.transaction(): if move and fields is not None and 'path' not in fields: # Special case: if an item needs to be moved, the path field has to # updated; otherwise the new path will not be reflected in the - # database + # database. fields.append('path') items, _ = _do_query(lib, query, album) @@ -1138,16 +1140,16 @@ def update_items(lib, query, album, move, pretend, fields): if changed: # Move the item if it's in the library. if move and lib.directory in ancestry(item.path): - item.move(fields_to_store=fields) + item.move(fields=fields) - item.store(fields_to_store=fields) + item.store(fields=fields) affected_albums.add(item.album_id) else: # The file's mtime was different, but there were no # changes to the metadata. Store the new mtime, # which is set in the call to read(), so we don't # check this again in the future. - item.store(fields_to_store=fields) + item.store(fields=fields) # Skip album changes while pretending. if pretend: @@ -1166,12 +1168,12 @@ def update_items(lib, query, album, move, pretend, fields): # Update album structure to reflect an item in it. for key in library.Album.item_keys: album[key] = first_item[key] - album.store(fields_to_store=fields) + album.store(fields=fields) # Move album art (and any inconsistent items). if move and lib.directory in ancestry(first_item.path): log.debug(u'moving album {0}', album_id) - album.move(fields_to_store=fields) + album.move(fields=fields) def update_func(lib, opts, args): From 04560bd88ed139aa21447186bdea0a1630a5e919 Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Sun, 23 Oct 2016 15:52:27 -0400 Subject: [PATCH 8/9] Stop passing in fields to be stored for Item.move and Album.move --- beets/library.py | 31 +++++++++++++++++++------------ beets/ui/commands.py | 11 +++++++++-- test/test_ui.py | 20 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/beets/library.py b/beets/library.py index e545bd314..6ba81a0be 100755 --- a/beets/library.py +++ b/beets/library.py @@ -730,7 +730,7 @@ class Item(LibModel): self._db._memotable = {} def move(self, copy=False, link=False, basedir=None, with_album=True, - fields=None): + store=True): """Move the item to its designated location within the library directory (provided by destination()). Subdirectories are created as needed. If the operation succeeds, the item's path @@ -746,10 +746,11 @@ class Item(LibModel): move its art. (This can be disabled by passing with_album=False.) - The item is stored to the database if it is in the database, so - any dirty fields prior to the move() call will be written as a + By default, the item is stored to the database if it is in the database, + so any dirty fields prior to the move() call will be written as a side effect. You probably want to call save() to commit the DB - transaction. + transaction. If `store` is true however, the item won't be stored, and + you'll have to manually store it after invoking this method. """ self._check_db() dest = self.destination(basedir=basedir) @@ -760,14 +761,16 @@ class Item(LibModel): # Perform the move and store the change. old_path = self.path self.move_file(dest, copy, link) - self.store(fields) + if store: + self.store() # If this item is in an album, move its art. if with_album: album = self.get_album() if album: album.move_art(copy) - album.store(fields) + if store: + album.store() # Prune vacated directory. if not copy: @@ -1001,27 +1004,31 @@ class Album(LibModel): util.prune_dirs(os.path.dirname(old_art), self._db.directory) - def move(self, copy=False, link=False, basedir=None, fields=None): + def move(self, copy=False, link=False, basedir=None, store=True): """Moves (or copies) all items to their destination. Any album art moves along with them. basedir overrides the library base - directory for the destination. The album is stored to the - database, persisting any modifications to its metadata. + directory for the destination. By default, the album is stored to the + database, persisting any modifications to its metadata. If `store` is + true however, the album is not stored automatically, and you'll have + to manually store it after invoking this method. """ basedir = basedir or self._db.directory # Ensure new metadata is available to items for destination # computation. - self.store(fields) + if store: + self.store() # Move items. items = list(self.items()) for item in items: item.move(copy, link, basedir=basedir, with_album=False, - fields=fields) + store=store) # Move art. self.move_art(copy, link) - self.store(fields) + if store: + self.store() def item_dir(self): """Returns the directory containing the album's first item, diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 0c1eb3ae9..c02f6accf 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1140,7 +1140,7 @@ def update_items(lib, query, album, move, pretend, fields): if changed: # Move the item if it's in the library. if move and lib.directory in ancestry(item.path): - item.move(fields=fields) + item.move(store=False) item.store(fields=fields) affected_albums.add(item.album_id) @@ -1173,7 +1173,14 @@ def update_items(lib, query, album, move, pretend, fields): # Move album art (and any inconsistent items). if move and lib.directory in ancestry(first_item.path): log.debug(u'moving album {0}', album_id) - album.move(fields=fields) + + # Manually moving and storing the album. + items = list(album.items()) + for item in items: + item.move(store=False) + item.store(fields=fields) + album.move(store=False) + album.store(fields=fields) def update_func(lib, opts, args): diff --git a/test/test_ui.py b/test/test_ui.py index 2a07db24d..f1c4c0fc1 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -588,6 +588,26 @@ class UpdateTest(_common.TestCase): album = self.lib.albums()[0] self.assertNotEqual(artpath, album.artpath) + def test_selective_modified_album_metadata_moved(self): + mf = MediaFile(self.i.path) + mf.album = u'differentAlbum' + mf.genre = u'differentGenre' + mf.save() + self._update(move=True, fields=['album']) + item = self.lib.items().get() + self.assertTrue(b'differentAlbum' in item.path) + self.assertNotEqual(item.genre, u'differentGenre') + + def test_selective_modified_album_metadata_not_moved(self): + mf = MediaFile(self.i.path) + mf.album = u'differentAlbum' + mf.genre = u'differentGenre' + mf.save() + self._update(move=True, fields=['genre']) + item = self.lib.items().get() + self.assertTrue(b'differentAlbum' not in item.path) + self.assertEqual(item.genre, u'differentGenre') + def test_mtime_match_skips_update(self): mf = MediaFile(self.i.path) mf.title = u'differentTitle' From 05377ee7c4ff13153598989495b0cfc976e3444c Mon Sep 17 00:00:00 2001 From: Dang Mai Date: Sun, 23 Oct 2016 16:00:36 -0400 Subject: [PATCH 9/9] Fixing line too long --- beets/library.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beets/library.py b/beets/library.py index 6ba81a0be..e80c4da72 100755 --- a/beets/library.py +++ b/beets/library.py @@ -746,9 +746,9 @@ class Item(LibModel): move its art. (This can be disabled by passing with_album=False.) - By default, the item is stored to the database if it is in the database, - so any dirty fields prior to the move() call will be written as a - side effect. You probably want to call save() to commit the DB + By default, the item is stored to the database if it is in the + database, so any dirty fields prior to the move() call will be written + as a side effect. You probably want to call save() to commit the DB transaction. If `store` is true however, the item won't be stored, and you'll have to manually store it after invoking this method. """