Ticket #1561: inspectdb-ordering.diff

File inspectdb-ordering.diff, 9.6 KB (added by mir@…, 18 years ago)
  • django/core/management.py

    diff --git a/django/core/management.py b/django/core/management.py
    index 7851403..894ca47 100644
    a b def startapp(app_name, directory):  
    638638startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
    639639startapp.args = "[appname]"
    640640
     641def createsuperuser(username=None, email=None, password=None):
     642    "Creates a superuser account."
     643    from django.core import validators
     644    from django.contrib.auth.models import User
     645    import getpass
     646
     647    try:
     648        import pwd
     649    except ImportError:
     650        default_username = ''
     651    else:
     652        # Determine the current system user's username, to use as a default.
     653        default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower()
     654
     655    # Determine whether the default username is taken, so we don't display
     656    # it as an option.
     657    if default_username:
     658        try:
     659            User.objects.get(username=default_username)
     660        except User.DoesNotExist:
     661            pass
     662        else:
     663            default_username = ''
     664
     665    try:
     666        while 1:
     667            if not username:
     668                input_msg = 'Username'
     669                if default_username:
     670                    input_msg += ' (Leave blank to use %r)' % default_username
     671                username = raw_input(input_msg + ': ')
     672            if default_username and username == '':
     673                username = default_username
     674            if not username.isalnum():
     675                sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
     676                username = None
     677            try:
     678                User.objects.get(username=username)
     679            except User.DoesNotExist:
     680                break
     681            else:
     682                sys.stderr.write("Error: That username is already taken.\n")
     683                username = None
     684        while 1:
     685            if not email:
     686                email = raw_input('E-mail address: ')
     687            try:
     688                validators.isValidEmail(email, None)
     689            except validators.ValidationError:
     690                sys.stderr.write("Error: That e-mail address is invalid.\n")
     691                email = None
     692            else:
     693                break
     694        while 1:
     695            if not password:
     696                password = getpass.getpass()
     697                password2 = getpass.getpass('Password (again): ')
     698                if password != password2:
     699                    sys.stderr.write("Error: Your passwords didn't match.\n")
     700                    password = None
     701                    continue
     702            if password.strip() == '':
     703                sys.stderr.write("Error: Blank passwords aren't allowed.\n")
     704                password = None
     705                continue
     706            break
     707    except KeyboardInterrupt:
     708        sys.stderr.write("\nOperation cancelled.\n")
     709        sys.exit(1)
     710    u = User.objects.create_user(username, email, password)
     711    u.is_staff = True
     712    u.is_active = True
     713    u.is_superuser = True
     714    u.save()
     715    print "User created successfully."
     716createsuperuser.args = '[username] [email] [password] (Either all or none)'
     717
     718def _topological_sort(cursor, introspection_module, table_names):
     719    """
     720    sorts the table names in a way that there are no forward references.
     721    At least it tries.
     722    generates the pairs for the ordered list of table names: ( table_name, forward_refs )
     723    forward_refs is a set of table names that are referenced with a forward reference
     724    that could not be avoided
     725    """
     726    # tables_in is a list [ (table_name, [ referenced_table_name ] ], excluding self references
     727    tables_in = [ ( name, [ val[1]
     728                            for val in introspection_module.get_relations(cursor, name).values()
     729                            if val[1] != name ])
     730                  for name in table_names ]
     731    #
     732    # build the sorted list
     733    tables_out_set = set()
     734    # sort by number of references and lexically (to get a defined order that doesn't change from inspect to inspect)
     735    tables_in.sort(lambda (name1,refs1),(name2,refs2): cmp(len(refs1),len(refs2)) or cmp(name1,name2))
     736    # go through list until everything is handled
     737    while tables_in != [ ]:
     738        for (i, (table_name, refs)) in enumerate(tables_in):
     739            for ref in refs:
     740                if ref not in tables_out_set:
     741                    # forward reference found, try next element
     742                    break
     743            else:
     744                # bingo! take this element to the ordered tables
     745                # and start processing the list with the first element
     746                tables_out_set.add(table_name)
     747                del tables_in[i]
     748                yield (table_name, set())
     749                break
     750        else:
     751            # There is no element without forward references in tables_in
     752            # i.e. we have circular references
     753            # strategy: find the table that is referenced most often
     754            # and put it here. This will most probably break the cycle
     755            # with only one trouble maker.
     756            refcount = { }
     757            referrers = { }
     758            max_ref = 0
     759            max_reffered = None
     760            for (referrer_name, refs) in tables_in:
     761                for ref in refs:
     762                    if (not ref in tables_out_set
     763                        and not referrer_name in referrers.setdefault(ref,[ ])):
     764                        referrers[ref].append(referrer_name)
     765                        refcount[ref] = refcount.get(ref,0) + 1
     766                        if refcount[ref] > max_ref:
     767                            max_ref = refcount[ref]
     768                            max_referred = ref
     769            for (i,(table_name,refs)) in enumerate(tables_in):
     770                if table_name == max_referred:
     771                    candidate = i
     772                    break
     773            else:
     774                # paranoia, cannot really happen
     775                candidate = 0
     776            # removing the references from the first entry will
     777            # make sure that the first entry will be taken anyway
     778            # during the next while loop run.
     779            # possible improvement: reorder tables_in according to
     780            #                       number of remaining unresolved references
     781            bad_table_name = tables_in[candidate][0]
     782            forward_references = [ x for x in tables_in[candidate][1]
     783                                   if x not in tables_out_set ]
     784            print "# cyclic references detected at this point"
     785            print "# %s contains these forward references: %s" % (
     786                bad_table_name,
     787                ", ".join(forward_references))
     788            bad_referrers = referrers[bad_table_name]
     789            bad_referrers.sort()
     790            print "# and is referenced by %s" % ", ".join(bad_referrers)
     791            tables_out_set.add(bad_table_name)
     792            del tables_in[candidate]
     793            yield (bad_table_name, set(forward_references))
     794    return
     795
     796
    641797def inspectdb(db_name):
    642798    "Generator that introspects the tables in the given database name and returns a Django model, one line at a time."
    643799    from django.db import connection, get_introspection_module
    def inspectdb(db_name):  
    663819    yield ''
    664820    yield 'from django.db import models'
    665821    yield ''
    666     for table_name in introspection_module.get_table_list(cursor):
     822    for (table_name, forward_referenced_tables) in (
     823           _topological_sort(cursor, introspection_module,
     824                             introspection_module.get_table_list(cursor))):
    667825        yield 'class %s(models.Model):' % table2model(table_name)
    668826        try:
    669827            relations = introspection_module.get_relations(cursor, table_name)
    670828        except NotImplementedError:
    671829            relations = {}
     830        # relation_count :: {other_table_name: ref_count}
     831        # counts for each other table, how often it is referred by this table.
     832        # (needed for the decision whether we need related_name attributes)
     833        relation_count = {}
     834        for (field_index, (field_index_other_table, other_table)) in relations.iteritems():
     835            relation_count[other_table] = relation_count.setdefault(other_table,0) + 1
    672836        try:
    673837            indexes = introspection_module.get_indexes(cursor, table_name)
    674838        except NotImplementedError:
    def inspectdb(db_name):  
    683847                att_name += '_field'
    684848                comment_notes.append('Field renamed because it was a Python reserved word.')
    685849
    686             if relations.has_key(i):
    687                 rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
    688                 field_type = 'ForeignKey(%s' % rel_to
     850            if (relations.has_key(i)
     851                and relations[i][1] not in forward_referenced_tables):
     852                rel_to = relations[i][1] == table_name and "'self'" or "%s" % table2model(relations[i][1])
     853                field_type = "ForeignKey(%s" % rel_to
    689854                if att_name.endswith('_id'):
    690855                    att_name = att_name[:-3]
    691856                else:
    692857                    extra_params['db_column'] = att_name
     858                if relation_count[relations[i][1]] > 1:
     859                    extra_params['related_name'] = '%s_by_%s' % (table_name, att_name)
    693860            else:
     861                if relations.has_key(i):
     862                    comment_notes.append('This is a forward foreign key reference to %s.' % table2model(relations[i][1]))
    694863                try:
    695864                    field_type = introspection_module.DATA_TYPES_REVERSE[row[1]]
    696865                except KeyError:
Back to Top