From 1b9c5c0a641e3b8f7055157792af0fb262a4a433 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 Nov 2011 20:42:36 -0600 Subject: [PATCH 1/2] Refactored sortable_by into a property instead of a classmethod. Added backwards compatibility for pre 1.1.1 versions that still have sortable_by defined as a classmethod. Need to address dynamic regroup template tag to see why categories are not being grouped properly in sample app. --- adminsortable/admin.py | 15 ++-- adminsortable/models.py | 4 ++ .../templates/adminsortable/change_list.html | 2 +- .../templatetags/django_template_additions.py | 2 +- sample_project/adminsortable.sqlite | Bin 56320 -> 62464 bytes sample_project/app/admin.py | 3 +- .../app/migrations/0003_add_sample.py | 68 ++++++++++++++++++ sample_project/app/models.py | 21 +++++- sample_project/app/tests.py | 6 +- sample_project/settings.py | 1 - 10 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 sample_project/app/migrations/0003_add_sample.py diff --git a/adminsortable/admin.py b/adminsortable/admin.py index f80064a..b389546 100755 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -53,13 +53,18 @@ class SortableAdmin(ModelAdmin): has_perm = request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) objects = self.model.objects.all() - """ - Determine if we need to regroup objects relative to a foreign key specified on the - model class that is extending Sortable. - """ + #Determine if we need to regroup objects relative to a foreign key specified on the + # model class that is extending Sortable. sortable_by = getattr(self.model, 'sortable_by', None) if sortable_by: - sortable_by_class, sortable_by_expression = sortable_by() + #backwards compatibility for < 1.1.1, where sortable_by was a classmethod instead of a property + try: + sortable_by_class, sortable_by_expression = sortable_by() + except ValueError: + sortable_by_class = self.model.sortable_by + sortable_by_expression = sortable_by_class.__name__.lower() + + print sortable_by_expression sortable_by_class_display_name = sortable_by_class._meta.verbose_name_plural sortable_by_class_is_sortable = sortable_by_class.is_sortable() else: diff --git a/adminsortable/models.py b/adminsortable/models.py index 0d74262..af299de 100755 --- a/adminsortable/models.py +++ b/adminsortable/models.py @@ -14,8 +14,12 @@ class Sortable(models.Model): `model_type_id` returns the ContentType.id for the Model that inherits Sortable `save` the override of save increments the last/highest value of order by 1 + + Override `sortable_by` method to make your model be sortable by a foreign key field. + Set `sortable_by` to the class specified in the foreign key relationship. """ order = models.PositiveIntegerField(editable=False, default=1, db_index=True) + sortable_by = None class Meta: abstract = True diff --git a/adminsortable/templates/adminsortable/change_list.html b/adminsortable/templates/adminsortable/change_list.html index f5e7562..9d85464 100755 --- a/adminsortable/templates/adminsortable/change_list.html +++ b/adminsortable/templates/adminsortable/change_list.html @@ -55,7 +55,7 @@ {% if objects %}
{% if group_expression %} - {% render_nested_sortable_objects objects group_expression %} + {% render_nested_sortable_objects objects group_expression %} {% else %} {% render_sortable_objects objects %} {% endif %} diff --git a/adminsortable/templatetags/django_template_additions.py b/adminsortable/templatetags/django_template_additions.py index 252b2e8..4de7630 100755 --- a/adminsortable/templatetags/django_template_additions.py +++ b/adminsortable/templatetags/django_template_additions.py @@ -69,7 +69,7 @@ def dynamic_regroup(parser, token): """ expression = lastbits_reversed[2][::-1] var_name = lastbits_reversed[0][::-1] - + print expression """ We also need to hand the parser to the node in order to convert the value for `expression` to a FilterExpression. diff --git a/sample_project/adminsortable.sqlite b/sample_project/adminsortable.sqlite index 5f71f74fd2089379a0c0d5353eee0de128b0ba1b..df957570192a8276eb1960cebd565c98cfd72807 100755 GIT binary patch delta 1905 zcmb_dU2GIp6u$SK{i$u4ZMRK-P|9>IZFiyD`RV@53YZmIMeUE$1V~7i+1=T;xZT}s zw?*PZTk)-lptoq^AA*5k2oEww;i0jBKA4bD->}F7^#LDHVjq04A@T0aZr2(JFHSNy zckcPlx#ynmJ9nlp!}M=(;_2!=2%#P9Ek2#^ZIfJh59(NMva+=@xRtj&2dnTG{0aBr z9xTHhxb@Zaekf-K(ZdE$i@ppl3wY3%)Lu4V{Rz#pnOfGX)L3vE=HgmX%W08ubu5$A zJmy+#L`@AB*W6VW+>ALj9xs5}_2se^uR{`fNe+pDkT2l%2L=7tKyxL}ky!+H;RalR z&tVb{KsU6LRdSd7NOW?V%<9#Z!=$d3v!ceDMsrU!lQAdy>7E#_;s6jFhMlk(oZtn} zoi9Bg4$e~3h}V#u?tEpXtlUN(B6tzLArJM+!3V_M0=?KTCj5ymEf5zH0Yy}mgsAwt zB*CvFl&)AniC5%7bmTTqd-nBjQB_e(B%<-4FY41221>+KpBVK=eLi0>;aAHIv|}%? zFYRl!u&fcnu*ycX16FnJ;O|h$=Gz_lYJ0#!?T7kpPzB{+VFx`==;C)4j`LUnW6o`i z?Rs78j~1s5dh%tu24BECpg|9`F?=mNzf%%Ku|t$P1YfDRa=g;NYKk|t$eVIlugn(r8p>2>5w%XMP3>0X#?ARyEU6--l%Ia; zQ_aSDXAYq`+(RU2fjM|L&u8hOnX_3vbM^$GFJJrR()mfigdVxz!=PWhFjPh>ZfwvS zzFq>la7XrHz+wApnGoG=IrsCS0tX_o{ zipWt_2x>94w505My*oVE&F|^k-Myc8l?)VLuW^_qaq;c0bT+PK=?B*jJKktBgFDRN zwDtNDy>oSpHeCIIURj9H!3FKo_mrn^U7nEfPKIy|lQ(sQ$K5uefm!p23iBAVQ2b!p0d$7?Ch|;C*%m znQWg)Y_-=nH{)aVMu-xPrCG4R^kDdfSG(6St)9F1ze#3l+$iFjWL)lKhpCt9|BA6o zipRQ?XsncC=y76<6tOP8zwh6<=l6-JIc+$dJsKH}yWD3ETDUcmZ^CCqk~^JGp)kgd zO?boFZDIULWFaI5z5XtCSqPYu3oOaFv-&X(k`j^xuP-1P4pPBE!ktx*cMxg=UQv+@ K53%4O;(q|7JO-!$ delta 625 zcmZvZUr3Wt7{<>z&(`;))3q_IOy>HU8lmI+=KeSd!HX;kOGvQ1sB~^Yl#BwUs}^V(P_b5$(dZ&igm#n7x(K@PD##k-yeLAbya>vh&Wf;$Ue3Asz0Y~x=bV%Ng!BwD z+gt(0SXk}9)q1rxXt1xWt=wQ&Ohc*Cv9N~k_=XjHL>VP*^P}_;(1#{_MYGG0Wf(TJ zurg_SAGsY9vaGKLoL1NuZ0~H3bcUhk)c>9X(q$T;LuR}NF-v8grZi5IimZ~%gK4zCl?2rZFg2|T9wM*f;!Sp98#m_ z1T8$X$@=(b|0X*Um7GP~#bqSWquOEkx`L)4Wwqo!$Y^rD+n?_xb&nz#rj z?kUqobt;XDDLuF{_y=bGg*2!OWJXNi9|H%C7s6b~hlOK0RnNF&!{axg)#a_Sdgg~# zr?Y-0_KT-z#&ureTiC1ujOyp2P|kE?K0&+ZkJEVZB~=yAkbmmE8nZb4!sgKhA7$bs cA5{T|xg34_lT-76j}G?jpu)Ru&;MTM7gsy9ng9R* diff --git a/sample_project/app/admin.py b/sample_project/app/admin.py index a1eb445..f365aab 100755 --- a/sample_project/app/admin.py +++ b/sample_project/app/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from adminsortable.admin import SortableAdmin, SortableTabularInline, SortableStackedInline -from app.models import Category, Project, Credit, Note +from app.models import Category, Project, Credit, Note, Sample admin.site.register(Category, SortableAdmin) @@ -20,3 +20,4 @@ class ProjectAdmin(SortableAdmin): list_display = ['__unicode__', 'category'] admin.site.register(Project, ProjectAdmin) +admin.site.register(Sample, SortableAdmin) diff --git a/sample_project/app/migrations/0003_add_sample.py b/sample_project/app/migrations/0003_add_sample.py new file mode 100644 index 0000000..f683706 --- /dev/null +++ b/sample_project/app/migrations/0003_add_sample.py @@ -0,0 +1,68 @@ +# 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 model 'Sample' + db.create_table('app_sample', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=50)), + ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Category'])), + ('description', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('app', ['Sample']) + + + def backwards(self, orm): + + # Deleting model 'Sample' + db.delete_table('app_sample') + + + models = { + 'app.category': { + 'Meta': {'ordering': "['order']", 'object_name': 'Category'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'app.credit': { + 'Meta': {'ordering': "['order']", 'object_name': 'Credit'}, + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"}) + }, + 'app.note': { + 'Meta': {'ordering': "['order']", 'object_name': 'Note'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'app.project': { + 'Meta': {'ordering': "['order']", 'object_name': 'Project'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'app.sample': { + 'Meta': {'ordering': "['order']", 'object_name': 'Sample'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['app'] diff --git a/sample_project/app/models.py b/sample_project/app/models.py index caba787..2e8aab0 100755 --- a/sample_project/app/models.py +++ b/sample_project/app/models.py @@ -29,13 +29,28 @@ class Project(SimpleModel, Sortable): class Meta(Sortable.Meta): pass - @classmethod - def sortable_by(cls): - return Category, 'category' +# @classmethod +# def sortable_by(cls): +# return Category, 'category' category = models.ForeignKey(Category) description = models.TextField() + sortable_by = Category + + +#a model that is sortable relative to a foreign key that is also sortable +class Sample(SimpleModel, Sortable): + class Meta(Sortable.Meta): + pass + + category = models.ForeignKey(Category) + description = models.TextField() + + #field to define which foreign key the model is sortable by. + #works with versions > 1.1.1 + sortable_by = Category + #registered as a tabular inline on project class Credit(Sortable): diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index 3eb1677..13a59ad 100755 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -1,12 +1,9 @@ import httplib import json -from django.contrib.auth.forms import authenticate, UserCreationForm from django.contrib.auth.models import User from django.core.urlresolvers import reverse -from django.middleware import csrf from django.db import models -from django.template.defaultfilters import urlencode from django.test import TestCase from django.test.client import Client, RequestFactory @@ -83,6 +80,9 @@ class SortableTestCase(TestCase): def get_category_indexes(self, *categories): return {'indexes' : ','.join([str(c.id) for c in categories])} + def test_sortable_by_backwards_compatibility(self): + pass + def test_adminsortable_changelist_templates(self): logged_in = self.client.login(username=self.user.username, password=self.user_raw_password) self.assertTrue(logged_in, 'User is not logged in') diff --git a/sample_project/settings.py b/sample_project/settings.py index c16655e..0309bf8 100755 --- a/sample_project/settings.py +++ b/sample_project/settings.py @@ -102,7 +102,6 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', ) TEMPLATE_CONTEXT_PROCESSORS = ( From 63a80f595302bd45259fbe628d027066168a14c1 Mon Sep 17 00:00:00 2001 From: Brandon Taylor Date: Tue, 22 Nov 2011 22:19:39 -0600 Subject: [PATCH 2/2] Incremented version to 1.2. Refactored ORM calls to properly order objects by the sortable_by property to ensure objects are grouped correctly in the sortable change list template after being passed through dynamic_regroup. Fixed missing import for jquery.effects.core, again. Refactored sortable_by classmethod into a property. --- README | 20 ++- adminsortable/__init__.py | 2 +- adminsortable/admin.py | 19 ++- .../shared/javascript_includes.html | 3 +- .../adminsortable/shared/nested_objects.html | 31 ++-- .../templatetags/django_template_additions.py | 33 ++--- sample_project/adminsortable.sqlite | Bin 62464 -> 63488 bytes .../app/migrations/0003_add_sample.py | 136 +++++++++--------- sample_project/app/models.py | 12 +- sample_project/app/tests.py | 3 - 10 files changed, 132 insertions(+), 127 deletions(-) mode change 100644 => 100755 sample_project/app/migrations/0003_add_sample.py diff --git a/README b/README index f132a19..1f6f71a 100755 --- a/README +++ b/README @@ -46,8 +46,9 @@ have an inner Meta class that inherits from ``Sortable.Meta`` For models that you want sortable relative to a ``ForeignKey`` field, you need to -specify an ``@classmethod`` that returns a double: the foreign key class, and the -name of the foreign key property as defined on your model, as a string. +specify a property: ``sortable_by`` that is equal to the class defined as your ForeignKey field. +If you're upgrading from a version < 1.2, you do not need to redefine sortable_by. +1.2 is backwards compatible to 1.0. #admin.py class Category(models.Model): @@ -63,9 +64,7 @@ name of the foreign key property as defined on your model, as a string. def __unicode__(self): return self.title - @classmethod - def sortable_by(cls): - return Category, 'category' + sortable_by = Category Sortable has one field: `order` and adds a default ordering value set to `order`. @@ -132,7 +131,16 @@ Status ============= admin-sortable is currently used in production. -Feautures + +What's new in 1.2 +============= +- Refactored ``sortable_by`` to be a property rather than a classmethod, which is much less work to implement. +- Fixed an issue with ordering which could result in sortable change list view objects not being grouped properly. +- Refactored the ORM calls to determine if an object is sortable, and what the next order should be, to return +scalar values and to not hydrate any objects whatsoever. This potentially decreases memory usage by exponential +factors. + +Features ============= Current --------- diff --git a/adminsortable/__init__.py b/adminsortable/__init__.py index e429140..44f81a6 100755 --- a/adminsortable/__init__.py +++ b/adminsortable/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 1, 1, "f", 0) # following PEP 386 +VERSION = (1, 2, "f", 0) # following PEP 386 DEV_N = None diff --git a/adminsortable/admin.py b/adminsortable/admin.py index b389546..8097647 100755 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -60,16 +60,21 @@ class SortableAdmin(ModelAdmin): #backwards compatibility for < 1.1.1, where sortable_by was a classmethod instead of a property try: sortable_by_class, sortable_by_expression = sortable_by() - except ValueError: + except TypeError, ValueError: sortable_by_class = self.model.sortable_by sortable_by_expression = sortable_by_class.__name__.lower() - print sortable_by_expression sortable_by_class_display_name = sortable_by_class._meta.verbose_name_plural sortable_by_class_is_sortable = sortable_by_class.is_sortable() + + # Order the objects by the property they are sortable by, then by the order, otherwise the regroup + # template tag will not show the objects correctly as + # shown in https://docs.djangoproject.com/en/1.3/ref/templates/builtins/#regroup + objects = objects.order_by(sortable_by_expression, 'order') + else: - sortable_by_class = sortable_by_expression = sortable_by_class_display_name = \ - sortable_by_class_is_sortable = None + sortable_by_class = sortable_by_expression = sortable_by_class_display_name =\ + sortable_by_class_is_sortable = None try: verbose_name_plural = opts.verbose_name_plural.__unicode__() @@ -115,7 +120,6 @@ class SortableAdmin(ModelAdmin): This view sets the ordering of the objects for the model type and primary keys passed in. It must be an Ajax POST. """ - if request.is_ajax() and request.method == 'POST': try: indexes = map(str, request.POST.get('indexes', []).split(',')) @@ -136,7 +140,7 @@ class SortableAdmin(ModelAdmin): else: response = {'objects_sorted' : False} return HttpResponse(json.dumps(response, ensure_ascii=False), - mimetype='application/json') + mimetype='application/json') class SortableInlineBase(InlineModelAdmin): @@ -144,7 +148,8 @@ class SortableInlineBase(InlineModelAdmin): super(SortableInlineBase, self).__init__(*args, **kwargs) if not issubclass(self.model, Sortable): - raise Warning(u'Models that are specified in SortableTabluarInline and SortableStackedInline must inherit from Sortable') + raise Warning(u'Models that are specified in SortableTabluarInline and SortableStackedInline ' + 'must inherit from Sortable') self.is_sortable = self.model.is_sortable() diff --git a/adminsortable/templates/adminsortable/shared/javascript_includes.html b/adminsortable/templates/adminsortable/shared/javascript_includes.html index 3aa396b..73c9590 100755 --- a/adminsortable/templates/adminsortable/shared/javascript_includes.html +++ b/adminsortable/templates/adminsortable/shared/javascript_includes.html @@ -5,6 +5,7 @@ + - \ No newline at end of file + diff --git a/adminsortable/templates/adminsortable/shared/nested_objects.html b/adminsortable/templates/adminsortable/shared/nested_objects.html index 850a37c..bcabb55 100755 --- a/adminsortable/templates/adminsortable/shared/nested_objects.html +++ b/adminsortable/templates/adminsortable/shared/nested_objects.html @@ -1,19 +1,18 @@ {% load django_template_additions adminsortable_tags %} {% dynamic_regroup objects by group_expression as regrouped_objects %} {% if regrouped_objects %} - -
    - {% for regrouped_object in regrouped_objects %} -
  • - {% with object=regrouped_object.grouper %} - {% render_object_rep object %} - {% endwith %} - {% if regrouped_object.list %} -
      - {% render_list_items regrouped_object.list %} -
    - {% endif %} -
  • - {% endfor %} -
-{% endif %} \ No newline at end of file +
    + {% for regrouped_object in regrouped_objects %} +
  • + {% with object=regrouped_object.grouper %} + {% render_object_rep object %} + {% endwith %} + {% if regrouped_object.list %} +
      + {% render_list_items regrouped_object.list %} +
    + {% endif %} +
  • + {% endfor %} +
+{% endif %} diff --git a/adminsortable/templatetags/django_template_additions.py b/adminsortable/templatetags/django_template_additions.py index 4de7630..435ab90 100755 --- a/adminsortable/templatetags/django_template_additions.py +++ b/adminsortable/templatetags/django_template_additions.py @@ -25,11 +25,9 @@ class DynamicRegroupNode(template.Node): # List of dictionaries in the format: # {'grouper': 'key', 'list': [list of contents]}. - """ - Try to resolve the filter expression from the template context. - If the variable doesn't exist, accept the value that passed to the - template tag and convert it to a string - """ + #Try to resolve the filter expression from the template context. + #If the variable doesn't exist, accept the value that passed to the + #template tag and convert it to a string try: exp = self.expression.resolve(context) except template.VariableDoesNotExist: @@ -47,6 +45,15 @@ class DynamicRegroupNode(template.Node): @register.tag def dynamic_regroup(parser, token): + """ + Django expects the value of `expression` to be an attribute available on + your objects. The value you pass to the template tag gets converted into a + FilterExpression object from the literal. + + Sometimes we need the attribute to group on to be dynamic. So, instead + of converting the value to a FilterExpression here, we're going to pass the + value as-is and convert it in the Node. + """ firstbits = token.contents.split(None, 3) if len(firstbits) != 4: raise TemplateSyntaxError("'regroup' tag takes five arguments") @@ -58,20 +65,8 @@ def dynamic_regroup(parser, token): raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" " be 'as'") - """ - Django expects the value of `expression` to be an attribute available on - your objects. The value you pass to the template tag gets converted into a - FilterExpression object from the literal. - - Sometimes we need the attribute to group on to be dynamic. So, instead - of converting the value to a FilterExpression here, we're going to pass the - value as-is and convert it in the Node. - """ expression = lastbits_reversed[2][::-1] var_name = lastbits_reversed[0][::-1] - print expression - """ - We also need to hand the parser to the node in order to convert the value - for `expression` to a FilterExpression. - """ + #We also need to hand the parser to the node in order to convert the value + #for `expression` to a FilterExpression. return DynamicRegroupNode(target, parser, expression, var_name) diff --git a/sample_project/adminsortable.sqlite b/sample_project/adminsortable.sqlite index df957570192a8276eb1960cebd565c98cfd72807..7df8bd7716b5bfd11ade2e5e2b53ee69a2933774 100755 GIT binary patch delta 577 zcmZp8!QAkHd4e=+2?GOz-9!a@#)ypxrRID_3Py%j2If|##(EYe=9?dzD@yP%F@FOp zVz6WW&HQb%K)^F*5g^Z;2S}K)axqV2U}w6)z;t6XN60xQRTXAmMq`T=pu=!TFmVpR6vpNIwL*_%wieE)tNaq3-Wwsnyl5$WoU+Q zwwa}+fq?<99J3>%k%6J1uAz~x0g|+(i7`levs-tK1S9k2!;4?CGcs*nyV{Z;&DEf0dhXOy)o6L-&49x6Id_byjvLH`N^b+63Ku1Cz@DK6IiVVp% z@po~{i3|y?^v(4(@O4Sa@^kks_ce;j@OO91igNKZjLP+`ipp{|jmnM6_RX4nFi65I zEYv;4$g(ogC@ecU(5uQP-PV>1!!st9dS-?ozc!X}v-&nwJ5FW{SJ*uF#$l#ScS=Fd tFS}pE0;E1a<`v-L1$u*#nH}g2V4(eF(r7H>1?wRcSCjj1?G?cmf&k)yoaX=l delta 368 zcmZqpz})bHd4e>nCj$e6?L-B8M$e52rRIEw3P#3OhUQkL=6Yrp2Adz7D@yP%GXDlD zVlW3%g_{Keo-qqqnHcDq1BJ|3xtJ$1urd8**vu7jj%jiMhx+8dVKY<{6Ae?-(vngv zOp{DgEzK;`(vlNR4U^20OifKK(##UMRG57kC-Vm@Y`zt)Wgx`H?8do qGz!a3t~9abGBPkU)HO8HH9&Rc=D)Wqn1N0!yI;csq&`396#xLTj%)k? diff --git a/sample_project/app/migrations/0003_add_sample.py b/sample_project/app/migrations/0003_add_sample.py old mode 100644 new mode 100755 index f683706..474c29e --- a/sample_project/app/migrations/0003_add_sample.py +++ b/sample_project/app/migrations/0003_add_sample.py @@ -1,68 +1,68 @@ -# 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 model 'Sample' - db.create_table('app_sample', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=50)), - ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Category'])), - ('description', self.gf('django.db.models.fields.TextField')()), - )) - db.send_create_signal('app', ['Sample']) - - - def backwards(self, orm): - - # Deleting model 'Sample' - db.delete_table('app_sample') - - - models = { - 'app.category': { - 'Meta': {'ordering': "['order']", 'object_name': 'Category'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'app.credit': { - 'Meta': {'ordering': "['order']", 'object_name': 'Credit'}, - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), - 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"}) - }, - 'app.note': { - 'Meta': {'ordering': "['order']", 'object_name': 'Note'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"}), - 'text': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'app.project': { - 'Meta': {'ordering': "['order']", 'object_name': 'Project'}, - 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'app.sample': { - 'Meta': {'ordering': "['order']", 'object_name': 'Sample'}, - 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['app'] +# 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 model 'Sample' + db.create_table('app_sample', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=50)), + ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Category'])), + ('description', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('app', ['Sample']) + + + def backwards(self, orm): + + # Deleting model 'Sample' + db.delete_table('app_sample') + + + models = { + 'app.category': { + 'Meta': {'ordering': "['order']", 'object_name': 'Category'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'app.credit': { + 'Meta': {'ordering': "['order']", 'object_name': 'Credit'}, + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"}) + }, + 'app.note': { + 'Meta': {'ordering': "['order']", 'object_name': 'Note'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'app.project': { + 'Meta': {'ordering': "['order']", 'object_name': 'Project'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'app.sample': { + 'Meta': {'ordering': "['order']", 'object_name': 'Sample'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['app'] diff --git a/sample_project/app/models.py b/sample_project/app/models.py index 2e8aab0..9b58536 100755 --- a/sample_project/app/models.py +++ b/sample_project/app/models.py @@ -29,20 +29,20 @@ class Project(SimpleModel, Sortable): class Meta(Sortable.Meta): pass -# @classmethod -# def sortable_by(cls): -# return Category, 'category' + #deprecated: shown for backward compatibility only. Reference class "Sample" for proper + # designation of `sortable_by` as a property + @classmethod + def sortable_by(cls): + return Category, 'category' category = models.ForeignKey(Category) description = models.TextField() - sortable_by = Category - #a model that is sortable relative to a foreign key that is also sortable class Sample(SimpleModel, Sortable): class Meta(Sortable.Meta): - pass + ordering = Sortable.Meta.ordering + ['category'] category = models.ForeignKey(Category) description = models.TextField() diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index 13a59ad..a086099 100755 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -80,9 +80,6 @@ class SortableTestCase(TestCase): def get_category_indexes(self, *categories): return {'indexes' : ','.join([str(c.id) for c in categories])} - def test_sortable_by_backwards_compatibility(self): - pass - def test_adminsortable_changelist_templates(self): logged_in = self.client.login(username=self.user.username, password=self.user_raw_password) self.assertTrue(logged_in, 'User is not logged in')