Repository: translate/pootle

Issue:
Running initdb twice causes a traceback
If you somehow manage to run `initdb` twice you will get the following error.  I'm not sure if it is possible to detect that initdb has already been run and bail out.

``` pytb
(python2.7) $> pootle initdb
2015-11-25 01:02:41,471 INFO Loading custom settings from '/home/arashm/.pootle/pootle.conf'...
2015-11-24 21:32:41,699 INFO Using Python PO
Populating the database.
Traceback (most recent call last):
  File "/home/arashm/.pyenvs/python2.7/bin/pootle", line 9, in <module>
    load_entry_point('Pootle==2.7.3.dev20151124100802', 'console_scripts', 'pootle')()
  File "/home/arashm/workspace/Mozilla/pootle/pootle/runner.py", line 319, in main
    django_settings_module='pootle.settings')
  File "/home/arashm/workspace/Mozilla/pootle/pootle/runner.py", line 299, in run_app
    management.execute_from_command_line(command)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/core/management/base.py", line 533, in handle
    return self.handle_noargs(**options)
  File "/home/arashm/workspace/Mozilla/pootle/pootle/apps/pootle_app/management/commands/initdb.py", line 25, in handle_noargs
    initdb()
  File "/home/arashm/workspace/Mozilla/pootle/pootle/core/initdb.py", line 35, in initdb
    create_default_projects()
  File "/home/arashm/workspace/Mozilla/pootle/pootle/core/initdb.py", line 255, in create_default_projects
    tutorial.save()
  File "/home/arashm/workspace/Mozilla/pootle/pootle/apps/pootle_project/models.py", line 368, in save
    super(Project, self).save(*args, **kwargs)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/base.py", line 589, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/base.py", line 617, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/base.py", line 698, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/base.py", line 731, in _do_insert
    using=using, raw=raw)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/manager.py", line 92, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/query.py", line 921, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 921, in execute_sql
    cursor.execute(sql, params)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/backends/utils.py", line 81, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/home/arashm/.pyenvs/python2.7/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 485, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: UNIQUE constraint failed: pootle_app_project.directory_id
```



Discussion:
`initdb` adds some default data https://github.com/translate/pootle/blob/master/pootle/core/initdb.py#L28-L36 part of which didn't change in eons. So perhaps it is safe to check if `en` language is present on DB:

``` python
from pootle_language.models import Language


initdb_already_run = True

try:
    Language.objects.get(code='en')
except Exception:
    initdb_already_run = False

if initdb_already_run:
    self.stdout.write('This command has already being run.')
else:
    self.stdout.write('Populating the database.')
    initdb()
    self.stdout.write('Successfully populated the database.')
    self.stdout.write("To create an admin user, use the `pootle "
                      "createsuperuser` command.")
```


Resolving patch:
diff --git a/pootle/core/initdb.py b/pootle/core/initdb.py
--- a/pootle/core/initdb.py
+++ b/pootle/core/initdb.py
@@ -7,6 +7,8 @@
 # or later license. See the LICENSE file for a copy of the license and the
 # AUTHORS file for copyright and authorship information.
 
+import logging
+
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Permission
 from django.contrib.contenttypes.models import ContentType
@@ -20,6 +22,9 @@
 from staticpages.models import StaticPage as Announcement
 
 
+logger = logging.getLogger(__name__)
+
+
 def initdb(create_projects=True):
     """Populate the database with default initial data.
 
@@ -38,6 +43,35 @@ def initdb(create_projects=True):
     create_default_languages()
 
 
+def _create_object(model_klass, **criteria):
+    instance, created = model_klass.objects.get_or_create(**criteria)
+    if created:
+        logger.debug(
+            "Created %s: '%s'"
+            % (instance.__class__.__name__, instance))
+    else:
+        logger.debug(
+            "%s already exists - skipping: '%s'"
+            % (instance.__class__.__name__, instance))
+    return instance, created
+
+
+def _create_pootle_user(**criteria):
+    user, created = _create_object(get_user_model(), **criteria)
+    if created:
+        user.set_unusable_password()
+        user.save()
+    return user
+
+
+def _create_pootle_permission_set(permissions, **criteria):
+    permission_set, created = _create_object(PermissionSet, **criteria)
+    if created:
+        permission_set.positive_permissions = permissions
+        permission_set.save()
+    return permission_set
+
+
 def create_revision():
     Revision.initialize()
 
@@ -47,8 +81,6 @@ def create_essential_users():
 
     These users are required for Pootle's permission system.
     """
-    User = get_user_model()
-
     # The nobody user is used to represent an anonymous user in cases where
     # we need to associate model information with such a user. An example is
     # in the permission system: we need a way to store rights for anonymous
@@ -58,10 +90,7 @@ def create_essential_users():
         'full_name': u"any anonymous user",
         'is_active': True,
     }
-    nobody, created = User.objects.get_or_create(**criteria)
-    if created:
-        nobody.set_unusable_password()
-        nobody.save()
+    _create_pootle_user(**criteria)
 
     # The 'default' user represents any valid, non-anonymous user and is used
     # to associate information any such user. An example is in the permission
@@ -75,10 +104,7 @@ def create_essential_users():
         'full_name': u"any authenticated user",
         'is_active': True,
     }
-    default, created = User.objects.get_or_create(**criteria)
-    if created:
-        default.set_unusable_password()
-        default.save()
+    _create_pootle_user(**criteria)
 
     # The system user represents a system, and is used to
     # associate updates done by bulk commands as update_stores.
@@ -87,10 +113,7 @@ def create_essential_users():
         'full_name': u"system user",
         'is_active': True,
     }
-    system, created = User.objects.get_or_create(**criteria)
-    if created:
-        system.set_unusable_password()
-        system.save()
+    _create_pootle_user(**criteria)
 
 
 def create_pootle_permissions():
@@ -100,7 +123,8 @@ def create_pootle_permissions():
         'app_label': "pootle_app",
         'model': "directory",
     }
-    pootle_content_type, created = ContentType.objects.get_or_create(**args)
+
+    pootle_content_type, created = _create_object(ContentType, **args)
     pootle_content_type.name = 'pootle'
     pootle_content_type.save()
 
@@ -138,7 +162,7 @@ def create_pootle_permissions():
 
     for permission in permissions:
         criteria.update(permission)
-        obj, created = Permission.objects.get_or_create(**criteria)
+        _create_object(Permission, **criteria)
 
 
 def create_pootle_permission_sets():
@@ -161,16 +185,10 @@ def create_pootle_permission_sets():
         'user': nobody,
         'directory': Directory.objects.root,
     }
-    permission_set, created = PermissionSet.objects.get_or_create(**criteria)
-    if created:
-        permission_set.positive_permissions = [view, suggest]
-        permission_set.save()
+    _create_pootle_permission_set([view, suggest], **criteria)
 
     criteria['user'] = default
-    permission_set, created = PermissionSet.objects.get_or_create(**criteria)
-    if created:
-        permission_set.positive_permissions = [view, suggest, translate]
-        permission_set.save()
+    _create_pootle_permission_set([view, suggest, translate], **criteria)
 
     # Default permissions for templates language.
     # Override with no permissions for templates language.
@@ -178,16 +196,10 @@ def create_pootle_permission_sets():
         'user': nobody,
         'directory': Directory.objects.get(pootle_path="/templates/"),
     }
-    permission_set, created = PermissionSet.objects.get_or_create(**criteria)
-    if created:
-        permission_set.positive_permissions = []
-        permission_set.save()
+    _create_pootle_permission_set([], **criteria)
 
     criteria['user'] = default
-    permission_set, created = PermissionSet.objects.get_or_create(**criteria)
-    if created:
-        permission_set.positive_permissions = []
-        permission_set.save()
+    _create_pootle_permission_set([], **criteria)
 
 
 def require_english():
@@ -198,15 +210,14 @@ def require_english():
         'nplurals': 2,
         'pluralequation': "(n != 1)",
     }
-    en, created = Language.objects.get_or_create(**criteria)
+    en, created = _create_object(Language, **criteria)
     return en
 
 
 def create_root_directories():
     """Create the root Directory items."""
-    root, created = Directory.objects.get_or_create(name='')
-    projects, created = Directory.objects.get_or_create(name='projects',
-                                                        parent=root)
+    root, created = _create_object(Directory, **dict(name=""))
+    _create_object(Directory, **dict(name="projects", parent=root))
 
 
 def create_template_languages():
@@ -215,8 +226,7 @@ def create_template_languages():
     The 'templates' language is used to give users access to the untranslated
     template files.
     """
-    templates, created = Language.objects.get_or_create(code="templates",
-                                                        fullname=u'Templates')
+    _create_object(Language, **dict(code="templates", fullname="Templates"))
     require_english()
 
 
@@ -232,7 +242,7 @@ def create_terminology_project():
         'source_language': require_english(),
         'checkstyle': "terminology",
     }
-    terminology, created = Project.objects.get_or_create(**criteria)
+    _create_object(Project, **criteria)
 
 
 def create_default_projects():
@@ -253,8 +263,7 @@ def create_default_projects():
         'localfiletype': "po",
         'treestyle': "auto",
     }
-    tutorial = Project(**criteria)
-    tutorial.save()
+    tutorial, created = _create_object(Project, **criteria)
 
     criteria = {
         'active': True,
@@ -267,8 +276,7 @@ def create_default_projects():
                  'guide</a>.</div>'),
         'virtual_path': "announcements/projects/"+tutorial.code,
     }
-    ann = Announcement(**criteria)
-    ann.save()
+    _create_object(Announcement, **criteria)
 
 
 def create_default_languages():
@@ -291,6 +299,6 @@ def create_default_languages():
                 criteria['specialchars'] = tk_lang.specialchars
             except AttributeError:
                 pass
-            lang, created = Language.objects.get_or_create(**criteria)
+            _create_object(Language, **criteria)
         except:
             pass


Test patch: