Ticket #1561: inspectdb-ordering.3.diff
File inspectdb-ordering.3.diff, 6.9 KB (added by , 19 years ago) |
---|
-
django/core/management.py
a b def inspectdb(db_name): 673 673 674 674 introspection_module = get_introspection_module() 675 675 676 def topological_sort(cursor, introspection_module, table_names): 677 """ 678 Generator, Sorting the table names in a way that there are no forward references, if possible. 679 Yields tuples ( table_name, forward_refs, comment_lines ) in an order that avoids forward references. 680 Here, forward_refs is a set of table names that are referenced with a forward reference 681 that could not be avoided since circular dependencies exist. 682 comment_lines is a list of comment lines for this table (about forward references) 683 """ 684 # tables_in is a list [ (table_name, [ referenced_table_name ] ], excluding self references 685 tables_in = [(name, [val[1] 686 for val in introspection_module.get_relations(cursor, name).values() 687 if val[1] != name]) 688 for name in table_names] 689 # unhandled_tables contains all unhandled table_names as a set for quick lookup 690 unhandled_tables = set(table_names) 691 # tables_in contains all table_names that still need to be handled. 692 # Initially, sort all tables by number of references and lexically 693 # (to get a defined order that doesn't change from inspect to inspect) 694 tables_in.sort(lambda (name1,refs1),(name2,refs2): cmp(len(refs1),len(refs2)) or cmp(name1,name2)) 695 # go through list until everything is handled 696 while tables_in != []: 697 for i, (table_name, refs) in enumerate(tables_in): 698 # does it have references to unhandled tables? 699 if not unhandled_tables.intersection(refs): 700 # no, take it 701 unhandled_tables.remove(table_name) 702 del tables_in[i] 703 yield (table_name, set(), []) 704 break; 705 else: 706 # There is no element without forward references in tables_in 707 # i.e. we have circular references 708 # strategy: use the table that is referenced most often 709 # This will most probably break the cycle 710 # with the least number of trouble makers. 711 # --> First, build a cross-ref: 712 # referrers[table_name] is a set of all unhandled tables referencing table_name 713 referrers = {} 714 max_refcount = -1 715 for table_name, refs in tables_in: 716 for ref in refs: 717 referrers.setdefault(ref,set()).add(table_name) 718 # first entry in table_name getting maximal references 719 for i, (table_name, refs) in enumerate(tables_in): 720 refcount = len(referrers.get(table_name,set())) 721 if refcount > max_refcount: 722 max_refcount, candidate = (refcount, i) 723 # got it. 724 bad_table_name, forward_references = tables_in[candidate] 725 forward_references = unhandled_tables.intersection(forward_references) 726 bad_referrers = list(unhandled_tables.intersection(referrers[bad_table_name])) 727 bad_referrers.sort() 728 comments = ["# cyclic references detected at this point", 729 "# %s contains these forward references: %s" 730 % (bad_table_name, ", ".join(forward_references)), 731 "# and is referenced by %s" % ", ".join(bad_referrers)] 732 unhandled_tables.remove(bad_table_name) 733 del tables_in[candidate] 734 yield (bad_table_name, set(forward_references), comments) 735 return 736 676 737 def table2model(table_name): 677 738 object_name = table_name.title().replace('_', '') 678 739 return object_name.endswith('s') and object_name[:-1] or object_name … … def inspectdb(db_name): 681 742 cursor = connection.cursor() 682 743 yield "# This is an auto-generated Django model module." 683 744 yield "# You'll have to do the following manually to clean this up:" 684 yield "# * Rearrange models' order"685 745 yield "# * Make sure each model has one field with primary_key=True" 686 746 yield "# Feel free to rename the models, but don't rename db_table values or field names." 687 747 yield "#" … … def inspectdb(db_name): 689 749 yield "# into your database." 690 750 yield '' 691 751 yield 'from django.db import models' 692 yield '' 693 for table_name in introspection_module.get_table_list(cursor): 752 yield '' 753 for (table_name, forward_referenced_tables, comments 754 ) in (topological_sort(cursor, introspection_module, 755 introspection_module.get_table_list(cursor))): 756 for comment in comments: 757 yield comment 694 758 yield 'class %s(models.Model):' % table2model(table_name) 695 759 try: 696 760 relations = introspection_module.get_relations(cursor, table_name) 697 761 except NotImplementedError: 698 762 relations = {} 699 try: 763 # relation_count :: {other_table_name: ref_count} 764 # counts for each other table, how often it is referred by this table. 765 # (needed for the decision whether we need related_name attributes) 766 relation_count = {} 767 for (field_index, (field_index_other_table, other_table)) in relations.iteritems(): 768 relation_count[other_table] = relation_count.setdefault(other_table,0) + 1 769 try: 700 770 indexes = introspection_module.get_indexes(cursor, table_name) 701 771 except NotImplementedError: 702 772 indexes = {} … … def inspectdb(db_name): 710 780 att_name += '_field' 711 781 comment_notes.append('Field renamed because it was a Python reserved word.') 712 782 713 if relations.has_key(i): 783 if (relations.has_key(i) 784 and relations[i][1] not in forward_referenced_tables): 714 785 rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1]) 715 field_type = 'ForeignKey(%s'% rel_to786 field_type = "ForeignKey(%s" % rel_to 716 787 if att_name.endswith('_id'): 717 788 att_name = att_name[:-3] 718 789 else: 719 790 extra_params['db_column'] = att_name 720 else: 791 if relation_count[relations[i][1]] > 1: 792 extra_params['related_name'] = '%s_by_%s' % (table_name, att_name) 793 else: 794 if relations.has_key(i): 795 comment_notes.append('This is a forward foreign key reference to %s.' % table2model(relations[i][1])) 721 796 try: 722 797 field_type = introspection_module.DATA_TYPES_REVERSE[row[1]] 723 798 except KeyError: