Permalink
Browse files

Merge pull request #107 from ubernostrum/717380-locale-uniqueness

[bug 717380] Simplified version
  • Loading branch information...
2 parents de4d760 + 71b338d commit 593e407af9765535dfabedbdf0c91affe4c4415e @lmorchard lmorchard committed Feb 10, 2012
@@ -0,0 +1,165 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Removing unique constraint on 'Document', fields ['locale', 'title']
+ db.delete_unique('wiki_document', ['locale', 'title'])
+
+
+ def backwards(self, orm):
+
+ # Adding unique constraint on 'Document', fields ['locale', 'title']
+ db.create_unique('wiki_document', ['locale', 'title'])
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'notifications.watch': {
+ 'Meta': {'object_name': 'Watch'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'db_index': 'True', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'event_type': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ },
+ 'taggit.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+ },
+ 'taggit.taggeditem': {
+ 'Meta': {'object_name': 'TaggedItem'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+ },
+ 'wiki.document': {
+ 'Meta': {'unique_together': "(('parent', 'locale'), ('slug', 'locale'))", 'object_name': 'Document'},
+ 'category': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'current_revision': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'current_for+'", 'null': 'True', 'to': "orm['wiki.Revision']"}),
+ 'html': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_localizable': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'is_template': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'locale': ('sumo.models.LocaleField', [], {'default': "'en-US'", 'max_length': '7', 'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'translations'", 'null': 'True', 'to': "orm['wiki.Document']"}),
+ 'related_documents': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['wiki.Document']", 'through': "orm['wiki.RelatedDocument']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+ },
+ 'wiki.editortoolbar': {
+ 'Meta': {'object_name': 'EditorToolbar'},
+ 'code': ('django.db.models.fields.TextField', [], {'max_length': '2000'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_toolbars'", 'to': "orm['auth.User']"}),
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'wiki.firefoxversion': {
+ 'Meta': {'unique_together': "(('item_id', 'document'),)", 'object_name': 'FirefoxVersion'},
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'firefox_version_set'", 'to': "orm['wiki.Document']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'item_id': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'wiki.helpfulvote': {
+ 'Meta': {'object_name': 'HelpfulVote'},
+ 'anonymous_id': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'poll_votes'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'poll_votes'", 'to': "orm['wiki.Document']"}),
+ 'helpful': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
+ },
+ 'wiki.operatingsystem': {
+ 'Meta': {'unique_together': "(('item_id', 'document'),)", 'object_name': 'OperatingSystem'},
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'operating_system_set'", 'to': "orm['wiki.Document']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'item_id': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'wiki.relateddocument': {
+ 'Meta': {'ordering': "['-in_common']", 'object_name': 'RelatedDocument'},
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'related_from'", 'to': "orm['wiki.Document']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_common': ('django.db.models.fields.IntegerField', [], {}),
+ 'related': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'related_to'", 'to': "orm['wiki.Document']"})
+ },
+ 'wiki.reviewtag': {
+ 'Meta': {'object_name': 'ReviewTag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+ },
+ 'wiki.reviewtaggedrevision': {
+ 'Meta': {'object_name': 'ReviewTaggedRevision'},
+ 'content_object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Revision']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ReviewTag']"})
+ },
+ 'wiki.revision': {
+ 'Meta': {'object_name': 'Revision'},
+ 'based_on': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Revision']", 'null': 'True', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_revisions'", 'to': "orm['auth.User']"}),
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['wiki.Document']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'reviewed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'reviewer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviewed_revisions'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'significance': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}),
+ 'summary': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['wiki']
View
@@ -162,10 +162,6 @@ def __init__(self, existing):
self.existing = existing
-class TitleCollision(UniqueCollision):
- """An attempt to create two pages of the same title in one locale"""
-
-
class SlugCollision(UniqueCollision):
"""An attempt to create two pages of the same slug in one locale"""
@@ -271,8 +267,7 @@ class Document(NotificationsMixin, ModelBase, BigVocabTaggableMixin):
# how MySQL uses indexes, we probably don't need individual indexes on
# title and locale as well as a combined (title, locale) one.
class Meta(object):
- unique_together = (('parent', 'locale'), ('title', 'locale'),
- ('slug', 'locale'))
+ unique_together = (('parent', 'locale'), ('slug', 'locale'))
def _existing(self, attr, value):
"""Return an existing doc (if any) in this locale whose `attr` attr is
@@ -370,9 +365,8 @@ def save(self, *args, **kwargs):
self.is_template = self.title.startswith(TEMPLATE_TITLE_PREFIX)
try:
- # Check if the slug or title would collide with an existing doc
+ # Check if the slug would collide with an existing doc
self._raise_if_collides('slug', SlugCollision)
- self._raise_if_collides('title', TitleCollision)
except UniqueCollision, e:
if e.existing.redirect_url() is not None:
# If the existing doc is a redirect, delete it and clobber it.
View
@@ -16,7 +16,7 @@
from sumo.urlresolvers import reverse
from sumo.utils import chunked
-from wiki.models import Document, SlugCollision, TitleCollision
+from wiki.models import Document, SlugCollision
log = logging.getLogger('k.task')
@@ -97,8 +97,6 @@ def _rebuild_kb_chunk(data, **kwargs):
message = 'ValidationError for %d: %s' % (pk, e.messages[0])
except SlugCollision:
message = 'SlugCollision: %d' % pk
- except TitleCollision:
- message = 'TitleCollision: %d' % pk
if message:
log.debug(message)
@@ -371,20 +371,15 @@ def test_slug_collision_validation(self):
eq_('Document with this Slug and Locale already exists.',
ul('li').text())
- def test_title_collision_validation(self):
- """Trying to create document with existing locale/slug should
- show validation error."""
+ def test_title_no_collision(self):
+ """Only slugs and not titles are required to be unique per
+ locale now, so test that we actually allow that."""
d = _create_document()
self.client.login(username='admin', password='testpass')
data = new_document_data()
- data['title'] = d.title
+ data['slug'] = '%s-once-more-with-feeling' % d.slug
response = self.client.post(reverse('wiki.new_document'), data)
- eq_(200, response.status_code)
- doc = pq(response.content)
- ul = doc('article.article > ul.errorlist')
- eq_(1, len(ul))
- eq_('Document with this Title and Locale already exists.',
- ul('li').text())
+ eq_(302, response.status_code)
def test_slug_3_chars(self):
"""Make sure we can create a slug with only 3 characters."""

0 comments on commit 593e407

Please sign in to comment.