Repository: readthedocs/readthedocs.org

Issue:
Migration failing on version slugs
Running migrations on #1396, I hit the following error:

```
Traceback (most recent call last):
  File "/home/docs/bin/django-admin.py", line 5, in <module>
    management.execute_from_command_line()
  File "/home/docs/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 399, in execute_from_command_li
ne
    utility.execute()
  File "/home/docs/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/docs/local/lib/python2.7/site-packages/django/core/management/base.py", line 242, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/home/docs/local/lib/python2.7/site-packages/django/core/management/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File "/home/docs/local/lib/python2.7/site-packages/south/management/commands/migrate.py", line 111, in handle
    ignore_ghosts = ignore_ghosts,
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/__init__.py", line 220, in migrate_app
    success = migrator.migrate_many(target, workplan, database)
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/migrators.py", line 256, in migrate_many
    result = migrator.__class__.migrate_many(migrator, target, migrations, database)
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/migrators.py", line 331, in migrate_many
    result = self.migrate(migration, database)
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/migrators.py", line 133, in migrate
    result = self.run(migration, database)
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/migrators.py", line 114, in run
    return self.run_migration(migration, database)
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/migrators.py", line 84, in run_migration
    migration_function()
  File "/home/docs/local/lib/python2.7/site-packages/south/migration/migrators.py", line 60, in <lambda>
    return (lambda: direction(orm))
  File "/home/docs/checkouts/readthedocs.org/readthedocs/builds/migrations/0024_fix_slugs_with_leading_placeholders.py", line
21, in forwards
    version.slug = slug_field.create_slug(version)
  File "/home/docs/checkouts/readthedocs.org/readthedocs/builds/version_slug.py", line 145, in create_slug
    'Invalid generated slug: {slug}'.format(slug=slug))
AssertionError: Invalid generated slug: v
```

cc @gregmuellegger 



Discussion:

Resolving patch:
diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py
--- a/readthedocs/builds/version_slug.py
+++ b/readthedocs/builds/version_slug.py
@@ -27,10 +27,10 @@
 # Regex breakdown:
 #   [a-z0-9] -- start with alphanumeric value
 #   [-._a-z0-9] -- allow dash, dot, underscore, digit, lowercase ascii
-#   +? -- allow multiple of those, but be not greedy about the matching
+#   *? -- allow multiple of those, but be not greedy about the matching
 #   (?: ... ) -- wrap everything so that the pattern cannot escape when used in
 #                regexes.
-VERSION_SLUG_REGEX = '(?:[a-z0-9][-._a-z0-9]+?)'
+VERSION_SLUG_REGEX = '(?:[a-z0-9][-._a-z0-9]*?)'
 
 
 class VersionSlugField(models.CharField):


Test patch:
diff --git a/readthedocs/rtd_tests/tests/test_version_slug.py b/readthedocs/rtd_tests/tests/test_version_slug.py
--- a/readthedocs/rtd_tests/tests/test_version_slug.py
+++ b/readthedocs/rtd_tests/tests/test_version_slug.py
@@ -1,10 +1,30 @@
+import re
 from django.test import TestCase
 
 from builds.models import Version
 from builds.version_slug import VersionSlugField
+from builds.version_slug import VERSION_SLUG_REGEX
 from projects.models import Project
 
 
+class VersionSlugPatternTests(TestCase):
+    pattern = re.compile('^{pattern}$'.format(pattern=VERSION_SLUG_REGEX))
+
+    def test_single_char(self):
+        self.assertTrue(self.pattern.match('v'))
+        self.assertFalse(self.pattern.match('.'))
+
+    def test_trailing_punctuation(self):
+        self.assertTrue(self.pattern.match('with_'))
+        self.assertTrue(self.pattern.match('with.'))
+        self.assertTrue(self.pattern.match('with-'))
+        self.assertFalse(self.pattern.match('with!'))
+
+    def test_multiple_words(self):
+        self.assertTrue(self.pattern.match('release-1.0'))
+        self.assertTrue(self.pattern.match('fix_this-and-that.'))
+
+
 class VersionSlugFieldTests(TestCase):
     fixtures = ["eric", "test_data"]