Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Reworked categories to enfore uniqueness at python-level.

To work around MySQL bug with fields of > 255 chars not being allowed to
have unique indexes.
  • Loading branch information...
commit 166d7be192798391074801ff413926399bee96d3 1 parent c117c43
@codeinthehole codeinthehole authored
View
12 oscar/apps/catalogue/abstract_models.py
@@ -56,7 +56,7 @@ class AbstractCategory(MP_Node):
name = models.CharField(max_length=255, db_index=True)
description = models.TextField(blank=True, null=True)
image = models.ImageField(upload_to='categories', blank=True, null=True)
- slug = models.SlugField(max_length=1024, db_index=True, editable=False, unique=True)
+ slug = models.SlugField(max_length=1024, db_index=True, editable=False)
full_name = models.CharField(max_length=1024, db_index=True, editable=False)
_slug_separator = '/'
@@ -76,6 +76,16 @@ def save(self, update_slugs=True, *args, **kwargs):
else:
self.slug = slug
self.full_name = self.name
+
+ # Enforce slug uniqueness here as MySQL can't handle a unique index on
+ # the slug field
+ try:
+ match = self.__class__.objects.get(slug=self.slug)
+ except self.__class__.DoesNotExist:
+ pass
+ else:
+ if match.id != self.id:
+ raise ValidationError(_("A category with slug '%(slug)s' already exists") % {'slug': self.slug})
super(AbstractCategory, self).save(*args, **kwargs)
def move(self, target, pos=None):
View
161 oscar/apps/catalogue/migrations/0005_auto__add_unique_category_slug.py
@@ -1,161 +0,0 @@
-# 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):
-
- # Adding unique constraint on 'Category', fields ['slug']
- db.create_unique('catalogue_category', ['slug'])
-
-
- def backwards(self, orm):
-
- # Removing unique constraint on 'Category', fields ['slug']
- db.delete_unique('catalogue_category', ['slug'])
-
-
- models = {
- 'catalogue.attributeentity': {
- 'Meta': {'object_name': 'AttributeEntity'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
- 'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
- },
- 'catalogue.attributeentitytype': {
- 'Meta': {'object_name': 'AttributeEntityType'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
- },
- 'catalogue.attributeoption': {
- 'Meta': {'object_name': 'AttributeOption'},
- 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- },
- 'catalogue.attributeoptiongroup': {
- 'Meta': {'object_name': 'AttributeOptionGroup'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'catalogue.category': {
- 'Meta': {'ordering': "['full_name']", 'object_name': 'Category'},
- 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
- 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
- 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
- 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024', 'db_index': 'True'})
- },
- 'catalogue.contributor': {
- 'Meta': {'object_name': 'Contributor'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.contributorrole': {
- 'Meta': {'object_name': 'ContributorRole'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
- 'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
- },
- 'catalogue.option': {
- 'Meta': {'object_name': 'Option'},
- 'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
- },
- 'catalogue.product': {
- 'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
- 'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
- 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
- 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
- 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
- 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
- 'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
- 'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
- 'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
- 'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
- 'status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
- 'upc': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.productattribute': {
- 'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
- 'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
- 'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
- 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
- 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
- },
- 'catalogue.productattributevalue': {
- 'Meta': {'object_name': 'ProductAttributeValue'},
- 'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': "orm['catalogue.Product']"}),
- 'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
- 'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
- 'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
- 'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
- 'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
- 'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.productcategory': {
- 'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
- 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
- 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
- },
- 'catalogue.productclass': {
- 'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
- },
- 'catalogue.productcontributor': {
- 'Meta': {'object_name': 'ProductContributor'},
- 'contributor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Contributor']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
- 'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ContributorRole']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.productimage': {
- 'Meta': {'ordering': "['display_order']", 'unique_together': "(('product', 'display_order'),)", 'object_name': 'ProductImage'},
- 'caption': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
- 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'display_order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'original': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- 'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['catalogue.Product']"})
- },
- 'catalogue.productrecommendation': {
- 'Meta': {'object_name': 'ProductRecommendation'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
- 'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
- 'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
- }
- }
-
- complete_apps = ['catalogue']
View
0  ...ttributevalue_value_boolean__add_field_product.py → ...ttributevalue_value_boolean__add_field_product.py
File renamed without changes
View
20 oscar/templates/dashboard/catalogue/category_list.html
@@ -24,17 +24,15 @@
{% block dashboard_content %}
-<div class="well well-info">
- <p>You are editing:
- <a href="{% url dashboard:catalogue-category-list %}">Home</a>
- {% if ancestors %}
- &gt;
- {% for ancestor in ancestors %}
- <a href="{% url dashboard:catalogue-category-detail-list pk=ancestor.pk %}">{{ ancestor.name }}</a>{% if not forloop.last %} > {% endif %}
- {% endfor %}
- {% endif %}
- </p>
-</div>
+<p>You are editing:
+<a href="{% url dashboard:catalogue-category-list %}">Home</a>
+{% if ancestors %}
+ &gt;
+ {% for ancestor in ancestors %}
+ <a href="{% url dashboard:catalogue-category-detail-list pk=ancestor.pk %}">{{ ancestor.name }}</a>{% if not forloop.last %} > {% endif %}
+ {% endfor %}
+{% endif %}
+</p>
<table class="table table-striped table-bordered">
<thead>
View
6 tests/unit/catalogue_tests.py
@@ -124,6 +124,12 @@ def test_supports_has_children_method(self):
root.add_child(name="Books")
self.assertTrue(root.has_children())
+ def test_enforces_slug_uniqueness(self):
+ root = Category.add_root(name="Products")
+ root.add_child(name="Books")
+ with self.assertRaises(ValidationError):
+ root.add_child(name="Books")
+
class ProductTests(TestCase):
Please sign in to comment.
Something went wrong with that request. Please try again.