1- # Copyright The IETF Trust 2015-2019 , All Rights Reserved
1+ # Copyright The IETF Trust 2015-2020 , All Rights Reserved
22# -*- coding: utf-8 -*-
33
44
55from __future__ import absolute_import , print_function , unicode_literals
66
7- import six
8-
97from tqdm import tqdm
108
119import django
1412from django .apps import apps
1513from django .core .management .base import BaseCommand #, CommandError
1614from django .core .exceptions import FieldError
17- from django .db .models .fields .related import ForeignKey , OneToOneField
15+ from django .db import IntegrityError
16+ from django .db .models .fields .related import ForeignKey , OneToOneField , ManyToManyField
17+
1818
1919import debug # pyflakes:ignore
2020
2121class Command (BaseCommand ):
2222 help = "Check all models for referential integrity."
2323
24+ def add_arguments (self , parser ):
25+ parser .add_argument (
26+ '--delete' , action = 'store_true' , default = False ,
27+ help = "Delete dangling references" ,
28+ )
29+
30+
2431 def handle (self , * args , ** options ):
2532 verbosity = options .get ("verbosity" , 1 )
2633 verbose = verbosity > 1
34+ if verbosity > 1 :
35+ self .stdout .ending = None
36+ self .stderr .ending = None
2737
2838 def check_field (field ):
2939 try :
@@ -32,20 +42,48 @@ def check_field(field):
3242 debug .pprint ('dir(field)' )
3343 raise
3444 if verbosity > 1 :
35- six .print_ (" %s -> %s.%s" % (field .name ,foreign_model .__module__ ,foreign_model .__name__ ), end = ' ' )
36- used = set (field .model .objects .values_list (field .name ,flat = True ))
45+ self .stdout .write (" [....] %s -> %s.%s" % (
46+ field .name , foreign_model .__module__ , foreign_model .__name__ ))
47+ self .stdout .flush ()
48+ used = set (field .model .objects .values_list (field .name , flat = True ))
3749 used .discard (None )
38- exists = set (foreign_model .objects .values_list ('pk' ,flat = True ))
50+ exists = set (foreign_model .objects .values_list ('pk' , flat = True ))
51+ dangling = used - exists
3952 if verbosity > 1 :
40- if used - exists :
41- six . print_ ( " ** Bad key values:" , list ( used - exists ))
53+ if dangling :
54+ self . stdout . write ( " \r [" + self . style . ERROR ( "fail" ) + "] \n ** Bad key values: %s \n " % sorted ( list ( dangling ) ))
4255 else :
43- six . print_ ( " ok " )
56+ self . stdout . write ( " \r [ " + self . style . SUCCESS ( 'ok' ) + " ] \n " )
4457 else :
45- if used - exists :
46- six .print_ ("\n %s.%s.%s -> %s.%s ** Bad key values:" % (model .__module__ ,model .__name__ ,field .name ,foreign_model .__module__ ,foreign_model .__name__ ),list (used - exists ))
58+ if dangling :
59+ self .stdout .write ("\n %s.%s.%s -> %s.%s ** Bad key values:\n %s\n " % (model .__module__ , model .__name__ , field .name , foreign_model .__module__ , foreign_model .__name__ , sorted (list (dangling ))))
60+
61+ if dangling and options .get ('delete' ):
62+ if verbosity > 1 :
63+ self .stdout .write ("Removing dangling values: %s.%s.%s\n " % (model .__module__ , model .__name__ , field .name , ))
64+ for value in dangling :
65+ kwargs = { field .name : value }
66+ for obj in field .model .objects .filter (** kwargs ):
67+ if verbosity > 1 :
68+ self .stdout .write ('.' , ending = None )
69+ self .stdout .flush ()
70+ try :
71+ if isinstance (field , (ForeignKey , OneToOneField )):
72+ setattr (obj , field .name , None )
73+ obj .save ()
74+ elif isinstance (field , (ManyToManyField , )):
75+ manager = getattr (obj , field .name )
76+ manager .remove (value )
77+ else :
78+ self .stderr .write ("\n Unexpected field type: %s\n " % type (field ))
79+ except IntegrityError as e :
80+ self .stderr .write ('\n ' )
81+ self .stderr .write ("Tried setting %s[%s].%s to %s, but got:\n " % (model .__name__ , obj .pk , field .name , None ))
82+ self .stderr .write ("Exception: %s\n " % e )
83+ if verbosity > 1 :
84+ self .stdout .write ('\n ' )
4785
48- def check_reverse_field (field ):
86+ def check_many_to_many_field (field ):
4987 try :
5088 foreign_model = field .related_model
5189 except Exception :
@@ -56,35 +94,61 @@ def check_reverse_field(field):
5694 foreign_field_name = field .remote_field .name
5795 foreign_accessor_name = field .remote_field .get_accessor_name ()
5896 if verbosity > 1 :
59- six .print_ (" %s <- %s -> %s.%s" % (field .model .__name__ , field .remote_field .through ._meta .db_table , foreign_model .__module__ , foreign_model .__name__ ), end = ' ' )
97+ self .stdout .write (" [....] %s <- %s ( -> %s.%s)" %
98+ (field .model .__name__ , field .remote_field .through ._meta .db_table ,
99+ foreign_model .__module__ , foreign_model .__name__ ))
100+ self .stdout .flush ()
101+
60102 try :
61103 used = set (foreign_model .objects .values_list (foreign_field_name , flat = True ))
104+ accessor_name = foreign_field_name
62105 except FieldError :
63106 try :
64107 used = set (foreign_model .objects .values_list (foreign_accessor_name , flat = True ))
108+ accessor_name = foreign_accessor_name
65109 except FieldError :
66- six .print_ (" ** Warning: could not find reverse name for %s.%s -> %s.%s" % (field .model .__module__ , field .model .__name__ , foreign_model .__name__ , foreign_field_name ), end = ' ' )
110+ self .stdout .write ("\n ** Warning: could not find foreign field name for %s.%s -> %s.%s\n " %
111+ (field .model .__module__ , field .model .__name__ ,
112+ foreign_model .__name__ , foreign_field_name ))
67113 used .discard (None )
68114 exists = set (field .model .objects .values_list ('pk' ,flat = True ))
115+ dangling = used - exists
69116 if verbosity > 1 :
70- if used - exists :
71- six . print_ ( " ** Bad key values:\n " , list ( used - exists ))
117+ if dangling :
118+ self . stdout . write ( " \r [" + self . style . ERROR ( "fail" ) + "] \n ** Bad key values:\n %s \n " % sorted ( list ( dangling ) ))
72119 else :
73- six . print_ ( " ok " )
120+ self . stdout . write ( " \r [ " + self . style . SUCCESS ( "ok" ) + " ] \n " )
74121 else :
75- if used - exists :
76- six .print_ ("\n %s.%s <- %s -> %s.%s ** Bad key values:\n " % (field .model .__module__ , field .model .__name__ , field .remote_field .through ._meta .db_table , foreign_model .__module__ , foreign_model .__name__ ), list (used - exists ))
122+ if dangling :
123+ self .stdout .write ("\n %s.%s <- %s (-> %s.%s) ** Bad target key values:\n %s\n " %
124+ (field .model .__module__ , field .model .__name__ ,
125+ field .remote_field .through ._meta .db_table ,
126+ foreign_model .__module__ , foreign_model .__name__ ,
127+ sorted (list (dangling ))))
128+
129+ if dangling and options .get ('delete' ):
130+ through = field .remote_field .through
131+ if verbosity > 1 :
132+ self .stdout .write ("Removing dangling entries from %s.%s\n " % (through ._meta .app_label , through .__name__ ))
133+
134+ kwargs = { accessor_name + '_id__in' : dangling }
135+ to_delete = field .remote_field .through .objects .filter (** kwargs )
136+ count = to_delete .count ()
137+ to_delete .delete ()
138+ if verbosity > 1 :
139+ self .stdout .write ("Removed %s entries from through table %s.%s\n " % (count , through ._meta .app_label , through .__name__ ))
140+
77141
78- for conf in tqdm ([ c for c in apps .get_app_configs () if c .name .startswith ('ietf. ' )], desc = 'apps' , disable = verbose ):
142+ for conf in tqdm ([ c for c in apps .get_app_configs () if c .name .startswith ('ietf' )], desc = 'apps ' , disable = verbose ):
79143 if verbosity > 1 :
80- six . print_ ( "Checking" , conf .name )
144+ self . stdout . write ( " \n Checking %s \n " % conf .name )
81145 for model in tqdm (list (conf .get_models ()), desc = 'models' , disable = verbose ):
82146 if model ._meta .proxy :
83147 continue
84148 if verbosity > 1 :
85- six . print_ (" %s.%s" % (model .__module__ ,model .__name__ ))
149+ self . stdout . write (" %s.%s\n " % (model .__module__ ,model .__name__ ))
86150 for field in [f for f in model ._meta .fields if isinstance (f , (ForeignKey , OneToOneField )) ]:
87151 check_field (field )
88152 for field in [f for f in model ._meta .many_to_many ]:
89153 check_field (field )
90- check_reverse_field (field )
154+ check_many_to_many_field (field )
0 commit comments