Code

Ticket #7159: loaddata-delete.diff

File loaddata-delete.diff, 5.4 KB (added by Graham King <graham@…>, 6 years ago)

The patch

Line 
1Index: django/core/management/commands/loaddata.py
2===================================================================
3--- django/core/management/commands/loaddata.py (revision 7513)
4+++ django/core/management/commands/loaddata.py (working copy)
5@@ -14,10 +14,42 @@
6         make_option('--verbosity', action='store', dest='verbosity', default='1',
7             type='choice', choices=['0', '1', '2'],
8             help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
9+        make_option('--delete', action='store_true', dest='delete', default=False,
10+            help='Deletes any items in the database which are not in the fixture'),
11     )
12     help = 'Installs the named fixture(s) in the database.'
13     args = "fixture [fixture ...]"
14 
15+    def remove_objects_not_in(self, objects_to_keep, verbosity):
16+        """
17+        Deletes all the objects in the database that are not in objects_to_keep.
18+        - objects_to_keep: A map where the keys are classes, and the values are a
19+         set of the objects of that class we should keep.
20+        """
21+        for class_ in objects_to_keep.keys():
22+       
23+            current = class_.objects.all()
24+            current_ids = set( [x.id for x in current] )
25+            keep_ids = set( [x.id for x in objects_to_keep[class_]] )
26+
27+            remove_these_ones = current_ids.difference(keep_ids)
28+            if remove_these_ones:
29+               
30+                for obj in current:
31+                    if obj.id in remove_these_ones:
32+                        obj.delete()
33+                        if verbosity >= 2:
34+                            print "Deleted object: "+ unicode(obj)
35+                               
36+            if verbosity > 0 and remove_these_ones:
37+                num_deleted = len(remove_these_ones)
38+                if num_deleted > 1:
39+                    type_deleted = unicode(class_._meta.verbose_name_plural)
40+                else:
41+                    type_deleted = unicode(class_._meta.verbose_name)
42+
43+                print "Deleted "+ str(num_deleted) +" "+ type_deleted
44+   
45     def handle(self, *fixture_labels, **options):
46         from django.db.models import get_apps
47         from django.core import serializers
48@@ -28,7 +60,8 @@
49 
50         verbosity = int(options.get('verbosity', 1))
51         show_traceback = options.get('traceback', False)
52-
53+        is_delete = options.get('delete', False)
54+       
55         # Keep a count of the installed objects and fixtures
56         fixture_count = 0
57         object_count = 0
58@@ -97,11 +130,24 @@
59                                 print "Installing %s fixture '%s' from %s." % \
60                                     (format, fixture_name, humanize(fixture_dir))
61                             try:
62+                               
63+                                objects_to_keep = {}
64+                               
65                                 objects = serializers.deserialize(format, fixture)
66                                 for obj in objects:
67                                     object_count += 1
68-                                    models.add(obj.object.__class__)
69+                                   
70+                                    class_ = obj.object.__class__
71+                                    if not class_ in objects_to_keep:
72+                                        objects_to_keep[class_] = set()
73+                                    objects_to_keep[class_].add(obj.object)
74+                                   
75+                                    models.add(class_)
76                                     obj.save()
77+                               
78+                                if is_delete:
79+                                    self.remove_objects_not_in(objects_to_keep, verbosity)
80+
81                                 label_found = True
82                             except Exception, e:
83                                 fixture.close()
84Index: tests/modeltests/fixtures/models.py
85===================================================================
86--- tests/modeltests/fixtures/models.py (revision 7512)
87+++ tests/modeltests/fixtures/models.py (working copy)
88@@ -54,6 +54,11 @@
89 # object list is unaffected
90 >>> Article.objects.all()
91 [<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>]
92+
93+# Delete two existing objects
94+>>> management.call_command('loaddata', 'fixture4.json', verbosity=0, delete=True)
95+>>> Article.objects.all()
96+[<Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>]
97 """}
98 
99 # Database flushing does not work on MySQL with the default storage engine
100Index: docs/django-admin.txt
101===================================================================
102--- docs/django-admin.txt       (revision 7512)
103+++ docs/django-admin.txt       (working copy)
104@@ -321,6 +321,17 @@
105 
106     django-admin.py loaddata --verbosity=2
107 
108+**New in Django development version**
109+--delete
110+~~~~~~~~
111+
112+Deletes any objects in the database which are not in the fixtures. If you run ``loaddata``
113+with ``--delete``, after the operation the database will exactly match the contents
114+of the fixtures.
115+If you do not specify ``--delete`` new objects only present in the fixture will be created in
116+the database, objects present in both will be updated, but no data will be removed from
117+the database.
118+
119 reset <appname appname ...>
120 ---------------------------
121