Remove sorted m2m fields and prepare for south
authorymh <ymh.work@gmail.com>
Fri, 15 Jul 2011 14:03:33 +0200
changeset 104 28a2c02ef6c8
parent 103 f61dee6527e5
child 105 2adad7698aa5
Remove sorted m2m fields and prepare for south
.settings/org.eclipse.core.resources.prefs
web/hdabo/fields.py
web/hdabo/fixtures/datasheet_10.yaml.bz2
web/hdabo/fixtures/datasheet_347.yaml.bz2
web/hdabo/fixtures/datasheet_all.yaml.bz2
web/hdabo/management/commands/import_csv.py
web/hdabo/models.py
web/hdabo/tests/sortedm2mfield.py
--- a/.settings/org.eclipse.core.resources.prefs	Wed Jul 13 14:16:19 2011 +0200
+++ b/.settings/org.eclipse.core.resources.prefs	Fri Jul 15 14:03:33 2011 +0200
@@ -1,4 +1,4 @@
-#Fri Jun 17 17:33:03 CEST 2011
+#Fri Jul 15 13:58:58 CEST 2011
 eclipse.preferences.version=1
 encoding//virtualenv/web/env/hdabo/lib/python2.6/site-packages/haystack/backends/__init__.py=utf-8
 encoding//virtualenv/web/env/hdabo/lib/python2.6/site-packages/sortedm2m/fields.py=utf-8
@@ -6,7 +6,6 @@
 encoding//virtualenv/web/env/hdabo/lib/python2.6/site-packages/sortedm2m/tests.py=utf-8
 encoding//virtualenv/web/env/hdabo/lib/python2.6/site-packages/whoosh/analysis.py=utf8
 encoding//virtualenv/web/env/hdabo/lib/python2.6/site-packages/wikitools/wiki.py=utf-8
-encoding//web/hdabo/fields.py=utf-8
 encoding//web/hdabo/forms.py=utf-8
 encoding//web/hdabo/management/commands/import_csv.py=utf-8
 encoding//web/hdabo/management/commands/import_tag_popularity.py=utf-8
@@ -14,7 +13,6 @@
 encoding//web/hdabo/models.py=utf-8
 encoding//web/hdabo/search/french_whoosh_backend.py=utf-8
 encoding//web/hdabo/tests/models.py=utf-8
-encoding//web/hdabo/tests/sortedm2mfield.py=utf-8
 encoding//web/hdabo/utils.py=utf-8
 encoding//web/hdabo/views.py=utf-8
 encoding//web/hdabo/wp_utils.py=utf-8
--- a/web/hdabo/fields.py	Wed Jul 13 14:16:19 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.db import models, router
-from django.db.models import signals
-from django.db.models.fields.related import (create_many_related_manager, 
-    ManyToManyField, ReverseManyRelatedObjectsDescriptor)
-from hdabo.forms import SortedMultipleChoiceField
-from hdabo.utils import OrderedSet
-
-
-SORT_VALUE_FIELD_NAME = 'sort_value'
-
-
-def create_sorted_many_related_manager(superclass, rel):
-    RelatedManager = create_many_related_manager(superclass, rel)
-
-    class SortedRelatedManager(RelatedManager):
-        def get_query_set(self):
-            # We use ``extra`` method here because we have no other access to
-            # the extra sorting field of the intermediary model. The fields
-            # are hidden for joins because we set ``auto_created`` on the
-            # intermediary's meta options.
-            return super(SortedRelatedManager, self).\
-                get_query_set().\
-                order_by('%s.%s' % (
-                    rel.through._meta.db_table,
-                    rel.through._sort_field_name,))
-
-        def _add_items(self, source_field_name, target_field_name, *objs):
-            # join_table: name of the m2m link table
-            # source_field_name: the PK fieldname in join_table for the source object
-            # target_field_name: the PK fieldname in join_table for the target object
-            # *objs - objects to add. Either object instances, or primary keys of object instances.
-
-            # If there aren't any objects, there is nothing to do.
-            from django.db.models import Model
-            if objs:
-                count = self.through._default_manager.count
-                new_ids = OrderedSet()
-                for obj in objs:
-                    if isinstance(obj, self.model):
-                        if not router.allow_relation(obj, self.instance):
-                            raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' % 
-                                               (obj, self.instance._state.db, obj._state.db))
-                        new_ids.add(obj.pk)
-                    elif isinstance(obj, Model):
-                        raise TypeError("'%s' instance expected" % self.model._meta.object_name)
-                    else:
-                        new_ids.add(obj)
-                db = router.db_for_write(self.through, instance=self.instance)
-                vals = self.through._default_manager.using(db).values_list(target_field_name, flat=True)
-                vals = vals.filter(**{
-                    source_field_name: self._pk_val,
-                    '%s__in' % target_field_name: new_ids,
-                })
-                new_ids = new_ids - OrderedSet(vals)
-
-                if self.reverse or source_field_name == self.source_field_name:
-                    # Don't send the signal when we are inserting the
-                    # duplicate data row for symmetrical reverse entries.
-                    signals.m2m_changed.send(sender=rel.through, action='pre_add',
-                        instance=self.instance, reverse=self.reverse,
-                        model=self.model, pk_set=new_ids, using=db)
-                # Add the ones that aren't there already
-                count = self.through._default_manager.count
-                for obj_id in new_ids:
-                    self.through._default_manager.using(db).create(**{
-                        '%s_id' % source_field_name: self._pk_val,
-                        '%s_id' % target_field_name: obj_id,
-                        self.through._sort_field_name: count(),
-                    })
-                if self.reverse or source_field_name == self.source_field_name:
-                    # Don't send the signal when we are inserting the
-                    # duplicate data row for symmetrical reverse entries.
-                    signals.m2m_changed.send(sender=rel.through, action='post_add',
-                        instance=self.instance, reverse=self.reverse,
-                        model=self.model, pk_set=new_ids, using=db)
-
-
-    return SortedRelatedManager
-
-
-class ReverseSortedManyRelatedObjectsDescriptor(ReverseManyRelatedObjectsDescriptor):
-    def __get__(self, instance, instance_type=None):
-        if instance is None:
-            return self
-
-        # Dynamically create a class that subclasses the related
-        # model's default manager.
-        rel_model = self.field.rel.to
-        superclass = rel_model._default_manager.__class__
-        RelatedManager = create_sorted_many_related_manager(superclass, self.field.rel)
-
-        manager = RelatedManager(
-            model=rel_model,
-            core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
-            instance=instance,
-            symmetrical=(self.field.rel.symmetrical and isinstance(instance, rel_model)),
-            source_field_name=self.field.m2m_field_name(),
-            target_field_name=self.field.m2m_reverse_field_name(),
-            reverse=False
-        )
-
-        return manager
-
-    def __set__(self, instance, value):
-        if instance is None:
-            raise AttributeError, "Manager must be accessed via instance"
-
-        manager = self.__get__(instance)
-        manager.clear()
-        manager.add(*value)
-
-
-class SortedManyToManyField(ManyToManyField):
-    '''
-    Providing a many to many relation that remembers the order of related
-    objects.
-
-    Accept a boolean ``sorted`` attribute which specifies if relation is
-    ordered or not. Default is set to ``True``. If ``sorted`` is set to
-    ``False`` the field will behave exactly like django's ``ManyToManyField``.
-    '''
-    def __init__(self, to, sorted=True, **kwargs):
-        self.sorted = sorted
-        if self.sorted:
-            # This is very hacky and should be removed if a better solution is
-            # found.
-            kwargs.setdefault('through', True)
-        super(SortedManyToManyField, self).__init__(to, **kwargs)
-        self.help_text = kwargs.get('help_text', None)
-
-    def create_intermediary_model(self, cls, field_name):
-        '''
-        Create intermediary model that stores the relation's data.
-        '''
-        module = ''
-
-        # make sure rel.to is a model class and not a string
-        if isinstance(self.rel.to, basestring):
-            bits = self.rel.to.split('.')
-            if len(bits) == 1:
-                bits = cls._meta.app_label.lower(), bits[0]
-            self.rel.to = models.get_model(*bits)
-
-        model_name = '%s_%s_%s' % (
-            cls._meta.app_label,
-            cls._meta.object_name,
-            field_name)
-        from_ = '%s.%s' % (
-            cls._meta.app_label,
-            cls._meta.object_name)
-
-        def default_sort_value():
-            model = models.get_model(cls._meta.app_label, model_name)
-            return model._default_manager.count()
-
-        # Using from and to model's name as field names for relations. This is
-        # also django default behaviour for m2m intermediary tables.
-        fields = {
-            cls._meta.object_name.lower():
-                models.ForeignKey(from_),
-            # using to model's name as field name for the other relation
-            self.rel.to._meta.object_name.lower():
-                models.ForeignKey(self.rel.to),
-            SORT_VALUE_FIELD_NAME:
-                models.IntegerField(default=default_sort_value),
-        }
-
-        class Meta:
-            db_table = '%s_%s_%s' % (
-                cls._meta.app_label.lower(),
-                cls._meta.object_name.lower(),
-                field_name.lower())
-            app_label = cls._meta.app_label
-            ordering = (SORT_VALUE_FIELD_NAME,)
-            auto_created = cls
-
-        attrs = {
-            '__module__': module,
-            'Meta': Meta,
-            '_sort_field_name': SORT_VALUE_FIELD_NAME,
-            '__unicode__': lambda s: 'pk=%d' % s.pk,
-        }
-
-        # Add in any fields that were provided
-        if fields:
-            attrs.update(fields)
-
-        # Create the class, which automatically triggers ModelBase processing
-        model = type(model_name, (models.Model,), attrs)
-
-        return model
-
-    def contribute_to_class(self, cls, name):
-        if self.sorted:
-            self.rel.through = self.create_intermediary_model(cls, name)
-            super(SortedManyToManyField, self).contribute_to_class(cls, name)
-            # overwrite default descriptor with reverse and sorted one
-            setattr(cls, self.name, ReverseSortedManyRelatedObjectsDescriptor(self))
-        else:
-            super(SortedManyToManyField, self).contribute_to_class(cls, name)
-
-    def formfield(self, **kwargs):
-        defaults = {}
-        if self.sorted:
-            defaults['form_class'] = SortedMultipleChoiceField
-        defaults.update(kwargs)
-        return super(SortedManyToManyField, self).formfield(**defaults)
Binary file web/hdabo/fixtures/datasheet_10.yaml.bz2 has changed
Binary file web/hdabo/fixtures/datasheet_347.yaml.bz2 has changed
Binary file web/hdabo/fixtures/datasheet_all.yaml.bz2 has changed
--- a/web/hdabo/management/commands/import_csv.py	Wed Jul 13 14:16:19 2011 +0200
+++ b/web/hdabo/management/commands/import_csv.py	Fri Jul 15 14:03:33 2011 +0200
@@ -170,13 +170,13 @@
         
         datasheet.save()
         
-        datasheet.domains = domains
-        datasheet.primary_periods = primary_periods
-        datasheet.college_periods = college_periods
-        datasheet.highschool_periods = highschool_periods
-        datasheet.primary_themes = primary_themes
-        datasheet.college_themes = college_themes
-        datasheet.highschool_themes = highschool_themes
+        datasheet.set_domains(domains)
+        datasheet.set_primary_periods(primary_periods)
+        datasheet.set_college_periods(college_periods)
+        datasheet.set_highschool_periods(highschool_periods)
+        datasheet.set_primary_themes(primary_themes)
+        datasheet.set_college_themes(college_themes)
+        datasheet.set_highschool_themes(highschool_themes)
 
         
         if row[u'Tag']:
--- a/web/hdabo/models.py	Wed Jul 13 14:16:19 2011 +0200
+++ b/web/hdabo/models.py	Fri Jul 15 14:03:33 2011 +0200
@@ -3,17 +3,26 @@
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.db import models
-from hdabo.fields import SortedManyToManyField
 from hdabo.utils import Property, normalize
 import datetime
 
+
+
+class SortedModelManager(models.Manager):
+    use_for_related_fields = True
+    def get_query_set(self):
+        qs = super(SortedModelManager, self).get_query_set()
+        if getattr(self, 'through', None) is not None and getattr(self.through, 'Meta', None) is not None and getattr(self.through.Meta, 'ordering', None) is not None:
+            qs = qs.order_by(*[self.through._meta.db_table + "." + f for f in self.through.Meta.ordering])
+        return qs
+
+
 class Organisation(models.Model):
     hda_id = models.CharField(max_length=512, unique=True, blank=False, null=False)
     name = models.CharField(max_length=512, unique=False, blank=False, null=False)
     location = models.CharField(max_length=512, unique=False, blank=True, null=True)
     website = models.CharField(max_length=2048, unique=False, blank=True, null=True)
     
-
 class Author(models.Model):
     hda_id = models.CharField(max_length=512, unique=True, blank=False, null=False)
     lastname = models.CharField(max_length=512, unique=False, blank=True, null=True)
@@ -33,13 +42,14 @@
     label = models.CharField(max_length=512, unique=False, blank=False, null=False)
     school_period = models.IntegerField(choices=TIME_PERIOD_CHOICES)
     
+    objects = SortedModelManager()
+    
     class Meta:
         unique_together = ("label", "school_period")
 
     def __unicode__(self):
         return unicode(self.label)
 
-
 class Domain(models.Model):
     DOMAIN_PERIOD_CHOICES = (
         (0, u'Global'),
@@ -56,6 +66,8 @@
     label = models.CharField(max_length=512, unique=False, blank=False, null=False)
     school_period = models.IntegerField(choices=DOMAIN_PERIOD_CHOICES)
 
+    objects = SortedModelManager()
+
     class Meta:
         unique_together = ("label", "school_period")
 
@@ -125,6 +137,38 @@
     def __unicode__(self):
         return unicode("%s : %s" % (self.name, self.insee))
 
+
+def generate_m2m_setter(m2m_field_name):
+    
+    def set_m2m_field(self, list):
+        
+        m2m_manager = getattr(self, m2m_field_name)
+        m2m_manager.clear()
+        
+        through_klass = set_m2m_field.cache.get('through_klass', None)
+        if through_klass is None:
+            field = getattr(self.__class__, m2m_field_name)
+            through_klass = field.through
+            set_m2m_field.cache['through_klass'] = through_klass
+            for f in through_klass._meta.fields:
+                if isinstance(f, models.ForeignKey) and f.name != "datasheet":
+                    set_m2m_field.cache['target_obj_field_name'] = f.name
+                    break
+        target_obj_field_name = set_m2m_field.cache['target_obj_field_name']
+
+        for i, obj in enumerate(list):
+            kwargs = {
+                'datasheet': self,
+                'sort_value' : i,
+                target_obj_field_name: obj
+            }
+            new_rel = through_klass(**kwargs)
+            new_rel.save()
+    set_m2m_field.cache = {}
+            
+    return set_m2m_field
+
+
 class Datasheet(models.Model):
     hda_id = models.CharField(max_length=512, unique=True, blank=False, null=False)
     author = models.ForeignKey(Author, null=True, blank=True)
@@ -132,13 +176,13 @@
     title = models.CharField(max_length=2048, unique=False, blank=False, null=False)
     description = models.TextField(blank=True, null=True)
     url = models.URLField(verify_exists=False, max_length=2048, blank=True, null=True)
-    domains = SortedManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Global']}, related_name="datasheets")
-    primary_periods = SortedManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Primaire']}, related_name="primary_periods_datasheets")
-    college_periods = SortedManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Collège']}, related_name="college_periods_datasheets")
-    highschool_periods = SortedManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Lycée']}, related_name="highschool_periods_datasheets")
-    primary_themes = SortedManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Primaire']}, related_name="primary_themes_datasheets")
-    college_themes = SortedManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Collège']}, related_name="college_themes_datasheets")
-    highschool_themes = SortedManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Lycée']}, related_name="highschool_themes_datasheets")
+    domains = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Global']}, related_name="datasheets", through="Datasheet_domains")
+    primary_periods = models.ManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Primaire']}, related_name="primary_periods_datasheets", through="Datasheet_primary_periods")
+    college_periods = models.ManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Collège']}, related_name="college_periods_datasheets", through="Datasheet_college_periods")
+    highschool_periods = models.ManyToManyField(TimePeriod, limit_choices_to={'school_period':TimePeriod.TIME_PERIOD_DICT[u'Lycée']}, related_name="highschool_periods_datasheets", through="Datasheet_highschool_periods")
+    primary_themes = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Primaire']}, related_name="primary_themes_datasheets", through="Datasheet_primary_themes")
+    college_themes = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Collège']}, related_name="college_themes_datasheets", through="Datasheet_college_themes")
+    highschool_themes = models.ManyToManyField(Domain, limit_choices_to={'school_period':Domain.DOMAIN_PERIOD_DICT[u'Lycée']}, related_name="highschool_themes_datasheets", through="Datasheet_highschool_themes")
     town = models.ForeignKey(Location, null=True, blank=True)
     format = models.ForeignKey(DocumentFormat, null=True, blank=True)
     original_creation_date = models.DateField()
@@ -150,7 +194,7 @@
     manual_order = models.BooleanField(default=False, db_index=True)
     tags = models.ManyToManyField(Tag, through='TaggedSheet')
     
-    
+        
     def validate(self, user):
         self.validation_date = datetime.datetime.now()
         self.validated = True
@@ -162,7 +206,10 @@
         self.validated = False
         self.validator = None
         self.save()
-
+        
+        
+    set_domains = generate_m2m_setter("domains")
+    
     @Property
     def domains_list(): #@NoSelf
         def fget(self):
@@ -177,6 +224,9 @@
         
         return locals() 
 
+
+    set_primary_periods = generate_m2m_setter("primary_periods")
+
     @Property
     def primary_periods_list(): #@NoSelf
         def fget(self):
@@ -192,6 +242,8 @@
 
         return locals() 
 
+    set_college_periods = generate_m2m_setter("college_periods")
+
     @Property
     def college_periods_list(): #@NoSelf
         def fget(self):
@@ -206,6 +258,8 @@
 
         return locals() 
 
+    set_highschool_periods = generate_m2m_setter("highschool_periods")
+
     @Property
     def highschool_periods_list(): #@NoSelf
         def fget(self):
@@ -220,6 +274,7 @@
 
         return locals() 
 
+    set_primary_themes = generate_m2m_setter("primary_themes")
 
     @Property
     def primary_themes_list(): #@NoSelf
@@ -236,6 +291,8 @@
 
         return locals() 
 
+    set_college_themes = generate_m2m_setter("college_themes")
+
     @Property
     def college_themes_list(): #@NoSelf
         def fget(self):
@@ -250,6 +307,8 @@
 
         return locals() 
 
+    set_highschool_themes = generate_m2m_setter("highschool_themes")
+
     @Property
     def highschool_themes_list(): #@NoSelf
         def fget(self):
@@ -299,5 +358,36 @@
             return settings.WIKIPEDIA_VERSION_PERMALINK_TEMPLATE % (unicode(self.wikipedia_revision_id))
         
         return locals()
+
+
+class SortedDatasheetLink(models.Model):    
+    datasheet = models.ForeignKey(Datasheet, db_index=True, null=False, blank=False)
+    sort_value = models.IntegerField(null=False, blank=False)
+
+    class Meta:
+        abstract = True
+        ordering = ['sort_value']
+        
+
+class Datasheet_domains(SortedDatasheetLink):
+    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)
+
+class Datasheet_highschool_periods(SortedDatasheetLink):
+    timeperiod = models.ForeignKey(TimePeriod, db_index=True, null=False, blank=False)
+
+class Datasheet_highschool_themes(SortedDatasheetLink):
+    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)
+
+class Datasheet_college_periods(SortedDatasheetLink):
+    timeperiod = models.ForeignKey(TimePeriod, db_index=True, null=False, blank=False)
+
+class Datasheet_college_themes(SortedDatasheetLink):
+    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)
+
+class Datasheet_primary_periods(SortedDatasheetLink):
+    timeperiod = models.ForeignKey(TimePeriod, db_index=True, null=False, blank=False)
+
+class Datasheet_primary_themes(SortedDatasheetLink):
+    domain = models.ForeignKey(Domain, db_index=True, null=False, blank=False)
     
     
--- a/web/hdabo/tests/sortedm2mfield.py	Wed Jul 13 14:16:19 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-# -*- coding: utf-8 -*-
-from django import forms
-from django.db import models
-from django.test import TestCase
-from hdabo.fields import SortedManyToManyField
-from hdabo.forms import SortedMultipleChoiceField
-
-
-class Book(models.Model):
-    name = models.CharField(max_length=50)
-
-    def __unicode__(self):
-        return self.name
-
-class Shelf(models.Model):
-    books = SortedManyToManyField(Book, related_name='shelves')
-
-
-class Store(models.Model):
-    books = SortedManyToManyField('sortedm2m.Book', related_name='stores')
-
-
-class MessyStore(models.Model):
-    books = SortedManyToManyField('Book',
-        sorted=False,
-        related_name='messy_stores')
-
-
-class TestSortedManyToManyField(TestCase):
-    model = Shelf
-
-    def setUp(self):
-        self.books = [Book.objects.create(name=c) for c in 'abcdefghik']
-
-    def test_adding_items(self):
-        shelf = self.model.objects.create()
-        self.assertEqual(list(shelf.books.all()), [])
-
-        shelf.books.add(self.books[2])
-        self.assertEqual(list(shelf.books.all()), [self.books[2]])
-
-        shelf.books.add(self.books[5], self.books[1])
-        self.assertEqual(list(shelf.books.all()), [
-            self.books[2],
-            self.books[5],
-            self.books[1]])
-
-        # adding the same item again will append it another time
-        shelf.books.add(self.books[2])
-        self.assertEqual(list(shelf.books.all()), [
-            self.books[2],
-            self.books[5],
-            self.books[1],
-            self.books[2]])
-
-        shelf.books.clear()
-        self.assertEqual(list(shelf.books.all()), [])
-
-        shelf.books.add(self.books[3], self.books[1], self.books[2])
-        self.assertEqual(list(shelf.books.all()), [
-            self.books[3],
-            self.books[1],
-            self.books[2]])
-
-    def test_set_items(self):
-        shelf = self.model.objects.create()
-        self.assertEqual(list(shelf.books.all()), [])
-
-        books = self.books[5:2:-1]
-        shelf.books = books
-        self.assertEqual(list(shelf.books.all()), books)
-
-        books.reverse()
-        shelf.books = books
-        self.assertEqual(list(shelf.books.all()), books)
-
-        shelf.books.add(self.books[8])
-        self.assertEqual(list(shelf.books.all()), books + [self.books[8]])
-
-        shelf.books = []
-        self.assertEqual(list(shelf.books.all()), [])
-
-        shelf.books = [self.books[9]]
-        self.assertEqual(list(shelf.books.all()), [
-            self.books[9]])
-
-        shelf.books = []
-        self.assertEqual(list(shelf.books.all()), [])
-
-    def test_remove_items(self):
-        shelf = self.model.objects.create()
-        shelf.books = self.books[2:5]
-        self.assertEqual(list(shelf.books.all()), [
-            self.books[2],
-            self.books[3],
-            self.books[4]])
-
-        shelf.books.remove(self.books[3])
-        self.assertEqual(list(shelf.books.all()), [
-            self.books[2],
-            self.books[4]])
-
-        shelf.books.remove(self.books[2], self.books[4])
-        self.assertEqual(list(shelf.books.all()), [])
-
-#    def test_add_relation_by_hand(self):
-#        shelf = self.model.objects.create()
-#        shelf.books = self.books[2:5]
-#        self.assertEqual(list(shelf.books.all()), [
-#            self.books[2],
-#            self.books[3],
-#            self.books[4]])
-#
-#        shelf.books.create()
-#        self.assertEqual(list(shelf.books.all()), [
-#            self.books[2],
-#            self.books[3],
-#            self.books[4]])
-
-
-class TestStringReference(TestSortedManyToManyField):
-    '''
-    Test the same things as ``TestSortedManyToManyField`` but using a model
-    that using a string to reference the relation where the m2m field should
-    point to.
-    '''
-    model = Store
-
-
-class SortedForm(forms.Form):
-    values = SortedMultipleChoiceField(
-        queryset=Book.objects.all(),
-        required=False)
-
-class TestSortedFormField(TestCase):
-    def setUp(self):
-        self.books = [Book.objects.create(name=c) for c in 'abcdefghik']
-
-    def test_empty_field(self):
-        form = SortedForm({'values': []})
-        self.assertTrue(form.is_valid())
-        self.assertFalse(form.cleaned_data['values'])
-
-    def test_sorted_field_input(self):
-        form = SortedForm({'values': [4, 2, 9]})
-        self.assertTrue(form.is_valid())
-        self.assertEqual(list(form.cleaned_data['values']), [
-                self.books[3],
-                self.books[1],
-                self.books[8]])
-
-        form = SortedForm({'values': [book.pk for book in self.books[::-1]]})
-        self.assertTrue(form.is_valid())
-        self.assertEqual(list(form.cleaned_data['values']), self.books[::-1])
-
-    def test_form_field_on_model_field(self):
-        class ShelfForm(forms.ModelForm):
-            class Meta:
-                model = Shelf
-
-        form = ShelfForm()
-        self.assertTrue(
-            isinstance(form.fields['books'], SortedMultipleChoiceField))
-
-        class MessyStoreForm(forms.ModelForm):
-            class Meta:
-                model = MessyStore
-
-        form = MessyStoreForm()
-        self.assertFalse(
-            isinstance(form.fields['books'], SortedMultipleChoiceField))
-        self.assertTrue(
-            isinstance(form.fields['books'], forms.ModelMultipleChoiceField))