# HG changeset patch # User ymh # Date 1310731413 -7200 # Node ID 28a2c02ef6c82407bc3c2f32cc446f06d09d16f3 # Parent f61dee6527e540fec9db83ae293ee7d8b6b8541d Remove sorted m2m fields and prepare for south diff -r f61dee6527e5 -r 28a2c02ef6c8 .settings/org.eclipse.core.resources.prefs --- 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 diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/fields.py --- 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) diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/fixtures/datasheet_10.yaml.bz2 Binary file web/hdabo/fixtures/datasheet_10.yaml.bz2 has changed diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/fixtures/datasheet_347.yaml.bz2 Binary file web/hdabo/fixtures/datasheet_347.yaml.bz2 has changed diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/fixtures/datasheet_all.yaml.bz2 Binary file web/hdabo/fixtures/datasheet_all.yaml.bz2 has changed diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/management/commands/import_csv.py --- 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']: diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/models.py --- 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) diff -r f61dee6527e5 -r 28a2c02ef6c8 web/hdabo/tests/sortedm2mfield.py --- 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))