From cb5e2764be48ef22398e51a6956e21995297df83 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Thu, 23 Mar 2006 00:55:30 +0000 Subject: [PATCH] magic-removal: Removed app-specific stuff from django.core.management.syncdb, in favor of an event-based system. Permissions, superusers and the example.com site are now all done via a management.py file within the appropriate contrib app. This also means 'createsuperuser' is no longer a django-admin command...We'll probably want to restore that somehow, or add another utility that does it from the command line. git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2551 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/create_superuser.py | 84 +++++++++++ django/contrib/auth/management.py | 53 +++++++ django/contrib/sites/management.py | 16 ++ django/core/management.py | 185 ++---------------------- django/db/models/signals.py | 2 + docs/django-admin.txt | 25 ---- 6 files changed, 171 insertions(+), 194 deletions(-) create mode 100644 django/contrib/auth/create_superuser.py create mode 100644 django/contrib/auth/management.py create mode 100644 django/contrib/sites/management.py diff --git a/django/contrib/auth/create_superuser.py b/django/contrib/auth/create_superuser.py new file mode 100644 index 0000000000..ab5ca36f50 --- /dev/null +++ b/django/contrib/auth/create_superuser.py @@ -0,0 +1,84 @@ +""" +Helper function for creating superusers in the authentication system. +""" + +from django.core import validators +from django.contrib.auth.models import User +import getpass +import os +import sys + +def createsuperuser(username=None, email=None, password=None): + """ + Helper function for creating a superuser from the command line. All + arguments are optional and will be prompted-for if invalid or not given. + """ + try: + import pwd + except ImportError: + default_username = '' + else: + # Determine the current system user's username, to use as a default. + default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower() + + # Determine whether the default username is taken, so we don't display + # it as an option. + if default_username: + try: + User.objects.get(username=default_username) + except User.DoesNotExist: + pass + else: + default_username = '' + + try: + while 1: + if not username: + input_msg = 'Username' + if default_username: + input_msg += ' (Leave blank to use %r)' % default_username + username = raw_input(input_msg + ': ') + if default_username and username == '': + username = default_username + if not username.isalnum(): + sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") + username = None + try: + User.objects.get(username=username) + except User.DoesNotExist: + break + else: + sys.stderr.write("Error: That username is already taken.\n") + username = None + while 1: + if not email: + email = raw_input('E-mail address: ') + try: + validators.isValidEmail(email, None) + except validators.ValidationError: + sys.stderr.write("Error: That e-mail address is invalid.\n") + email = None + else: + break + while 1: + if not password: + password = getpass.getpass() + password2 = getpass.getpass('Password (again): ') + if password != password2: + sys.stderr.write("Error: Your passwords didn't match.\n") + password = None + continue + if password.strip() == '': + sys.stderr.write("Error: Blank passwords aren't allowed.\n") + password = None + continue + break + except KeyboardInterrupt: + sys.stderr.write("\nOperation cancelled.\n") + sys.exit(1) + u = User.objects.create_user(username, email, password) + u.is_staff = True + u.is_active = True + u.is_superuser = True + u.save() + print "Superuser created successfully." diff --git a/django/contrib/auth/management.py b/django/contrib/auth/management.py new file mode 100644 index 0000000000..6e906e3df1 --- /dev/null +++ b/django/contrib/auth/management.py @@ -0,0 +1,53 @@ +""" +Creates permissions for all installed apps that need permissions. +""" + +from django.dispatch import dispatcher +from django.db.models import get_models, signals +from django.contrib.auth import models as auth_app + +def _get_permission_codename(action, opts): + return '%s_%s' % (action, opts.object_name.lower()) + +def _get_all_permissions(opts): + "Returns (codename, name) for all permissions in the given opts." + perms = [] + for action in ('add', 'change', 'delete'): + perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name))) + return perms + list(opts.permissions) + +def create_permissions(app, created_models): + from django.contrib.contenttypes.models import ContentType + from django.contrib.auth.models import Permission + app_models = get_models(app) + if not app_models: + return + for klass in app_models: + if not klass._meta.admin: + continue + ctype = ContentType.objects.get_for_model(klass) + for codename, name in _get_all_permissions(klass._meta): + try: + Permission.objects.get(name=name, codename=codename, content_type__pk=ctype.id) + except Permission.DoesNotExist: + p = Permission(name=name, codename=codename, content_type=ctype) + p.save() + print "Adding permission '%r'" % p + +def create_superuser(app, created_models): + from django.contrib.auth.models import User + from django.contrib.auth.create_superuser import createsuperuser as do_create + if User in created_models: + msg = "\nYou just installed Django's auth system, which means you don't have " \ + "any superusers defined.\nWould you like to create one now? (yes/no): " + confirm = raw_input(msg) + while 1: + if confirm not in ('yes', 'no'): + confirm = raw_input('Please enter either "yes" or "no": ') + continue + if confirm == 'yes': + do_create() + break + +dispatcher.connect(create_permissions, signal=signals.post_syncdb) +dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb) diff --git a/django/contrib/sites/management.py b/django/contrib/sites/management.py new file mode 100644 index 0000000000..0e1a50227a --- /dev/null +++ b/django/contrib/sites/management.py @@ -0,0 +1,16 @@ +""" +Creates the default Site object. +""" + +from django.dispatch import dispatcher +from django.db.models import signals +from django.contrib.sites.models import Site +from django.contrib.sites import models as site_app + +def create_default_site(app, created_models): + if Site in created_models: + print "Creating example.com Site object" + s = Site(domain="example.com", name="example.com") + s.save() + +dispatcher.connect(create_default_site, sender=site_app, signal=signals.post_syncdb) diff --git a/django/core/management.py b/django/core/management.py index ce89a0f943..cbc1655c59 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -26,23 +26,6 @@ PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template') INVALID_PROJECT_NAMES = ('django', 'test') -def _get_permission_codename(action, opts): - return '%s_%s' % (action, opts.object_name.lower()) - -def _get_all_permissions(opts): - "Returns (codename, name) for all permissions in the given opts." - perms = [] - if opts.admin: - for action in ('add', 'change', 'delete'): - perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name))) - return perms + list(opts.permissions) - -def _get_permission_insert(name, codename, opts): - from django.db import backend - return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \ - (backend.quote_name('auth_permission'), backend.quote_name('name'), backend.quote_name('package'), - backend.quote_name('codename'), name.replace("'", "''"), opts.app_label, codename) - def _is_valid_dir_name(s): return bool(re.search(r'^\w+$', s)) @@ -398,10 +381,21 @@ get_sql_all.args = APP_ARGS def syncdb(): "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." from django.db import connection, transaction, models, get_creation_module + from django.db.models import signals + from django.conf import settings + from django.dispatch import dispatcher # Check that there are no validation errors before continuing _check_for_validation_errors() + # Import the 'management' module within each installed app, to register + # dispatcher events. + for app_name in settings.INSTALLED_APPS: + try: + __import__(app_name + '.management', '', '', ['']) + except ImportError: + pass + data_types = get_creation_module().DATA_TYPES cursor = connection.cursor() @@ -414,7 +408,6 @@ def syncdb(): seen_models = _get_installed_models(table_list) created_models = set() pending_references = {} - install_permissions = True for app in models.get_apps(): model_list = models.get_models(app) @@ -441,21 +434,11 @@ def syncdb(): transaction.commit_unless_managed() - # Install permissions and initial data (first checking that they're installed) + # Send the post_syncdb signal, so individual apps can do whatever they need + # to do at this point. for app in models.get_apps(): - if install_permissions: - try: - installperms(app) - except Exception, e: - # stop trying to install permissions - install_permissions = False - sys.stderr.write("Permissions will not be installed because it "\ - "appears that you are not using Django's auth framework. "\ - "If you want to install them in the future, re-run syncdb."\ - "\n(The full error was: %s)\n" % e) - transaction.rollback_unless_managed() - else: - transaction.commit_unless_managed() + dispatcher.send(signal=signals.post_syncdb, sender=app, + app=app, created_models=created_models) # Install initial data for the app (but only if this is a model we've # just created) @@ -474,27 +457,6 @@ def syncdb(): else: transaction.commit_unless_managed() - # Create an initial "example.com" site (if we need to) - from django.contrib.sites.models import Site - if Site in created_models: - print "Creating example site object" - ex = Site(domain="example.com", name="example.com") - ex.save() - - # If we just installed the User model, ask about creating a superuser - from django.contrib.auth.models import User - if User in created_models: - msg = "\nYou just installed Django's auth system, which means you don't have " \ - "any superusers defined.\nWould you like to create one now? (yes/no): " - confirm = raw_input(msg) - while 1: - if confirm not in ('yes', 'no'): - confirm = raw_input('Please enter either "yes" or "no": ') - continue - if confirm == 'yes': - createsuperuser() - break - syncdb.args = '' def get_admin_index(app): @@ -585,30 +547,6 @@ The full error: %s\n""" % (app_name, app_name, e)) reset.help_doc = "Executes ``sqlreset`` for the given app(s) in the current database." reset.args = APP_ARGS -def installperms(app): - "Installs any permissions for the given app, if needed." - from django.contrib.contenttypes.models import ContentType - from django.contrib.auth.models import Permission - from django.db.models import get_models - app_models = get_models(app) - if not app_models: - return - app_label = app_models[0]._meta.app_label - num_added = 0 - for klass in app_models: - opts = klass._meta - ctype = ContentType.objects.get_for_model(klass) - for codename, name in _get_all_permissions(opts): - try: - Permission.objects.get(name=name, codename=codename, content_type__pk=ctype.id) - except Permission.DoesNotExist: - p = Permission(name=name, codename=codename, content_type=ctype) - p.save() - print "Adding permission '%r'." % p - num_added += 1 -installperms.help_doc = "Installs any permissions for the given model module name(s), if needed." -installperms.args = APP_ARGS - def _start_helper(app_or_project, name, directory, other_name=''): other = {'project': 'app', 'app': 'project'}[app_or_project] if not _is_valid_dir_name(name): @@ -665,83 +603,6 @@ def startapp(app_name, directory): startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory." startapp.args = "[appname]" -def createsuperuser(username=None, email=None, password=None): - "Creates a superuser account." - from django.core import validators - from django.contrib.auth.models import User - import getpass - - try: - import pwd - except ImportError: - default_username = '' - else: - # Determine the current system user's username, to use as a default. - default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower() - - # Determine whether the default username is taken, so we don't display - # it as an option. - if default_username: - try: - User.objects.get(username=default_username) - except User.DoesNotExist: - pass - else: - default_username = '' - - try: - while 1: - if not username: - input_msg = 'Username' - if default_username: - input_msg += ' (Leave blank to use %r)' % default_username - username = raw_input(input_msg + ': ') - if default_username and username == '': - username = default_username - if not username.isalnum(): - sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") - username = None - try: - User.objects.get(username=username) - except User.DoesNotExist: - break - else: - sys.stderr.write("Error: That username is already taken.\n") - username = None - while 1: - if not email: - email = raw_input('E-mail address: ') - try: - validators.isValidEmail(email, None) - except validators.ValidationError: - sys.stderr.write("Error: That e-mail address is invalid.\n") - email = None - else: - break - while 1: - if not password: - password = getpass.getpass() - password2 = getpass.getpass('Password (again): ') - if password != password2: - sys.stderr.write("Error: Your passwords didn't match.\n") - password = None - continue - if password.strip() == '': - sys.stderr.write("Error: Blank passwords aren't allowed.\n") - password = None - continue - break - except KeyboardInterrupt: - sys.stderr.write("\nOperation cancelled.\n") - sys.exit(1) - u = User.objects.create_user(username, email, password) - u.is_staff = True - u.is_active = True - u.is_superuser = True - u.save() - print "User created successfully." -createsuperuser.args = '[username] [email] [password] (Either all or none)' - def inspectdb(db_name): "Generator that introspects the tables in the given database name and returns a Django model, one line at a time." from django.db import connection, get_introspection_module @@ -1120,11 +981,9 @@ run_shell.args = '[--plain]' DEFAULT_ACTION_MAPPING = { 'adminindex': get_admin_index, - 'createsuperuser': createsuperuser, 'createcachetable' : createcachetable, 'inspectdb': inspectdb, 'install': install, - 'installperms': installperms, 'reset': reset, 'runserver': runserver, 'shell': run_shell, @@ -1145,7 +1004,6 @@ NO_SQL_TRANSACTION = ( 'adminindex', 'createcachetable', 'install', - 'installperms', 'reset', 'sqlindexes' ) @@ -1207,18 +1065,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING): from django.utils import translation translation.activate('en-us') - if action == 'createsuperuser': - try: - username, email, password = args[1], args[2], args[3] - except IndexError: - if len(args) == 1: # We got no arguments, just the action. - action_mapping[action]() - else: - sys.stderr.write("Error: %r requires arguments of 'username email password' or no argument at all.\n") - sys.exit(1) - else: - action_mapping[action](username, email, password) - elif action == 'shell': + if action == 'shell': action_mapping[action](options.plain is True) elif action in ('syncdb', 'validate'): action_mapping[action]() diff --git a/django/db/models/signals.py b/django/db/models/signals.py index e73cd40884..2171cb1bf3 100644 --- a/django/db/models/signals.py +++ b/django/db/models/signals.py @@ -8,3 +8,5 @@ post_save = object() pre_delete = object() post_delete = object() + +post_syncdb = object() diff --git a/docs/django-admin.txt b/docs/django-admin.txt index 45cc2363dc..efdb4ae95c 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -64,24 +64,6 @@ backend. See the `cache documentation`_ for more information. .. _cache documentation: http://www.djangoproject.com/documentation/cache/ -createsuperuser ---------------- - -Creates a superuser account interactively. It asks you for a username, e-mail -address and password. - -You can specify ``username email password`` on the command line, for convenient -use in shell scripts. Example:: - - django-admin.py createsuperuser john john@example.com mypassword - -init ----- - -Initializes the database with the tables and data Django needs by default. -Specifically, these are the database tables from the ``auth`` and ``core`` -models. - inspectdb [dbname] ------------------ @@ -128,13 +110,6 @@ install [modelmodule modelmodule ...] Executes the equivalent of ``sqlall`` for the given model module(s). -installperms [modelmodule modelmodule ...] ------------------------------------------- - -Installs any admin permissions for the given model module(s) that aren't -already installed in the database. Outputs a message telling how many -permissions were added, if any. - runserver [optional port number, or ipaddr:port] ------------------------------------------------