diff --git a/README.md b/README.md index fb59ca8..1f526cb 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,47 @@ There are also generic equivalents that you can inherit from: """Your generic inline options go here""" +### Overriding `queryset()` +django-admin-sortable now supports custom queryset overrides on admin models +and inline models in Django admin! + +If you're providing an override of a SortableAdmin or Sortable inline model, +you don't need to do anything extra. django-admin-sortable will automatically +honor your queryset. + +Have a look at the WidgetAdmin class in the sample project for an example of +an admin class with a custom `queryset()` override. + +#### Overriding `queryset()` for an inline model +This is a special case, which requires a few lines of extra code to properly +determine the sortability of your model. Example: + + # add this import to your admin.py + from adminsortable.utils import get_is_sortable + + + class ComponentInline(SortableStackedInline): + model = Component + + def queryset(self, request): + qs = super(ComponentInline, self).queryset(request).filter( + title__icontains='foo') + + # You'll need to add these lines to determine if your model + # is sortable once we hit the change_form() for the parent model. + + if get_is_sortable(qs): + self.model.is_sortable = True + else: + self.model.is_sortable = False + return qs + +If you override the queryset of an inline, the number of objects present +may change, and adminsortable won't be able to automatically determine +if the inline model is sortable from here, which is why we have to set the +`is_sortable` property of the model in this method. + + *** IMPORTANT *** With stacked inline models, their height can dynamically increase, which can cause sortable stacked inlines to not behave as expected. @@ -162,9 +203,10 @@ ordering on top of that just seemed a little much in my opinion. django-admin-sortable is currently used in production. -### What's new in 1.4.4? -- Decided to go with the simplest approach to add the sorting urls to inlines -for Django <= 1.4 and Django 1.5.x support +### What's new in 1.4.5? +- Support for queryset overrides! +- More efficient JavaScript in sortables +- Fixed highlight effect for stacked inlines on sort finish ### Future diff --git a/adminsortable/__init__.py b/adminsortable/__init__.py index 07cf343..a5df678 100755 --- a/adminsortable/__init__.py +++ b/adminsortable/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 4, 4) # following PEP 386 +VERSION = (1, 4, 5) # following PEP 386 DEV_N = None diff --git a/adminsortable/admin.py b/adminsortable/admin.py index 6913d90..032cfb0 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -21,13 +21,30 @@ from django.shortcuts import render from django.template.defaultfilters import capfirst from django.views.decorators.csrf import csrf_exempt +from adminsortable.utils import get_is_sortable from adminsortable.fields import SortableForeignKey from adminsortable.models import Sortable STATIC_URL = settings.STATIC_URL -class SortableAdmin(ModelAdmin): +class SortableAdminBase(object): + def changelist_view(self, request, extra_context=None): + """ + If the model that inherits Sortable has more than one object, + its sort order can be changed. This view adds a link to the + object_tools block to take people to the view to change the sorting. + """ + + if get_is_sortable(self.queryset(request)): + self.change_list_template = \ + self.sortable_change_list_with_sort_link_template + self.is_sortable = True + return super(SortableAdminBase, self).changelist_view(request, + extra_context=extra_context) + + +class SortableAdmin(SortableAdminBase, ModelAdmin): """ Admin class to add template overrides and context objects to enable drag-and-drop ordering. @@ -52,18 +69,20 @@ class SortableAdmin(ModelAdmin): break return sortable_foreign_key - def __init__(self, *args, **kwargs): - super(SortableAdmin, self).__init__(*args, **kwargs) + # def __init__(self, *args, **kwargs): + # super(SortableAdmin, self).__init__(*args, **kwargs) - self.has_sortable_tabular_inlines = False - self.has_sortable_stacked_inlines = False - for klass in self.inlines: - if issubclass(klass, SortableTabularInline): - if klass.model.is_sortable(): - self.has_sortable_tabular_inlines = True - if issubclass(klass, SortableStackedInline): - if klass.model.is_sortable(): - self.has_sortable_stacked_inlines = True + # self.has_sortable_tabular_inlines = False + # self.has_sortable_stacked_inlines = False + # for klass in self.inlines: + # print type(klass) + # is_sortable = get_is_sortable( + # klass.model._default_manager.get_query_set()) + # print is_sortable + # if issubclass(klass, SortableTabularInline) and is_sortable: + # self.has_sortable_tabular_inlines = True + # if issubclass(klass, SortableStackedInline) and is_sortable: + # self.has_sortable_stacked_inlines = True def get_urls(self): urls = super(SortableAdmin, self).get_urls() @@ -88,7 +107,8 @@ class SortableAdmin(ModelAdmin): opts = self.model._meta has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label, opts.get_change_permission())) - objects = self.model.objects.all() + + objects = self.queryset(request) # Determine if we need to regroup objects relative to a # foreign key specified on the model class that is extending Sortable. @@ -97,6 +117,7 @@ class SortableAdmin(ModelAdmin): # `sortable_by` defined as a SortableForeignKey sortable_by_fk = self._get_sortable_foreign_key() + sortable_by_class_is_sortable = get_is_sortable(objects) if sortable_by_property: # backwards compatibility for < 1.1.1, where sortable_by was a @@ -110,7 +131,6 @@ class SortableAdmin(ModelAdmin): sortable_by_class_display_name = sortable_by_class._meta \ .verbose_name_plural - sortable_by_class_is_sortable = sortable_by_class.is_sortable() elif sortable_by_fk: # get sortable by properties from the SortableForeignKey @@ -119,10 +139,6 @@ class SortableAdmin(ModelAdmin): ._meta.verbose_name_plural sortable_by_class = sortable_by_fk.rel.to sortable_by_expression = sortable_by_fk.name.lower() - try: - sortable_by_class_is_sortable = sortable_by_class.is_sortable() - except AttributeError: - sortable_by_class_is_sortable = False else: # model is not sortable by another model @@ -132,7 +148,7 @@ class SortableAdmin(ModelAdmin): if sortable_by_property or sortable_by_fk: # Order the objects by the property they are sortable by, - #then by the order, otherwise the regroup + # then by the order, otherwise the regroup # template tag will not show the objects correctly objects = objects.order_by(sortable_by_expression, 'order') @@ -157,19 +173,17 @@ class SortableAdmin(ModelAdmin): } return render(request, self.sortable_change_list_template, context) - def changelist_view(self, request, extra_context=None): - """ - If the model that inherits Sortable has more than one object, - its sort order can be changed. This view adds a link to the - object_tools block to take people to the view to change the sorting. - """ - if self.model.is_sortable(): - self.change_list_template = \ - self.sortable_change_list_with_sort_link_template - return super(SortableAdmin, self).changelist_view(request, - extra_context=extra_context) - def change_view(self, request, object_id, extra_context=None): + self.has_sortable_tabular_inlines = False + self.has_sortable_stacked_inlines = False + + for klass in self.inlines: + is_sortable = klass.model.is_sortable + if issubclass(klass, SortableTabularInline) and is_sortable: + self.has_sortable_tabular_inlines = True + if issubclass(klass, SortableStackedInline) and is_sortable: + self.has_sortable_stacked_inlines = True + if self.has_sortable_tabular_inlines or \ self.has_sortable_stacked_inlines: self.change_form_template = self.sortable_change_form_template @@ -222,7 +236,7 @@ class SortableAdmin(ModelAdmin): mimetype='application/json') -class SortableInlineBase(InlineModelAdmin): +class SortableInlineBase(SortableAdminBase, InlineModelAdmin): def __init__(self, *args, **kwargs): super(SortableInlineBase, self).__init__(*args, **kwargs) @@ -230,7 +244,13 @@ class SortableInlineBase(InlineModelAdmin): raise Warning(u'Models that are specified in SortableTabluarInline' ' and SortableStackedInline must inherit from Sortable') - self.is_sortable = self.model.is_sortable() + def queryset(self, request): + qs = super(SortableInlineBase, self).queryset(request) + if get_is_sortable(qs): + self.model.is_sortable = True + else: + self.model.is_sortable = False + return qs class SortableTabularInline(SortableInlineBase, TabularInline): diff --git a/adminsortable/models.py b/adminsortable/models.py index 26cb682..f7640be 100644 --- a/adminsortable/models.py +++ b/adminsortable/models.py @@ -26,14 +26,11 @@ class Sortable(models.Model): `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) + is_sortable = False # legacy support sortable_by = None @@ -42,14 +39,18 @@ class Sortable(models.Model): abstract = True ordering = ['order'] - @classmethod - def is_sortable(cls): - try: - max_order = cls.objects.aggregate( - models.Max('order'))['order__max'] - except (TypeError, IndexError): - max_order = 0 - return True if max_order > 1 else False + # @classmethod + # def determine_if_sortable(cls): + # try: + # max_order = cls.objects.aggregate( + # models.Max('order'))['order__max'] + # except (TypeError, IndexError): + # max_order = 0 + + # if max_order > 1: + # cls.is_sortable = True + # else: + # cls.is_sortable = False @classmethod def model_type_id(cls): diff --git a/adminsortable/static/adminsortable/js/admin.sortable.js b/adminsortable/static/adminsortable/js/admin.sortable.js index 0374ded..3f1a449 100644 --- a/adminsortable/static/adminsortable/js/admin.sortable.js +++ b/adminsortable/static/adminsortable/js/admin.sortable.js @@ -6,7 +6,7 @@ jQuery(function($){ items : 'li', stop : function(event, ui) { - var indexes = Array(); + var indexes = []; ui.item.parent().children('li').each(function(i) { indexes.push($(this).find(':hidden[name="pk"]').val()); @@ -17,5 +17,7 @@ jQuery(function($){ data: { indexes: indexes.join(',') } }); } + }).click(function(e){ + e.preventDefault(); }); }); diff --git a/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js b/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js index 08971b9..539100e 100644 --- a/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js +++ b/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js @@ -10,21 +10,23 @@ jQuery(function($){ items : '.inline-related', stop : function(event, ui) { - var indexes = Array(); + var indexes = []; ui.item.parent().children('.inline-related').each(function(i) { - index_value = $(this).find(':hidden[name$="-id"]').val(); - if (index_value != "" && index_value != undefined) + var index_value = $(this).find(':hidden[name$="-id"]').val(); + if (index_value !== "" && index_value !== undefined) + { indexes.push(index_value); + } }); - + $.ajax({ url: ui.item.parent().find(':hidden[name="admin_sorting_url"]').val(), type: 'POST', data: { indexes : indexes.join(',') }, success: function() { - ui.item.effect('highlight', {}, 1000); + ui.item.find('.form-row').effect('highlight', {}, 1000); } }); } diff --git a/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js b/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js index f5cfa28..ae032df 100644 --- a/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js +++ b/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js @@ -10,14 +10,16 @@ jQuery(function($){ items : 'tr:not(.add-row)', stop : function(event, ui) { - var indexes = Array(); + var indexes = []; ui.item.parent().children('tr').each(function(i) { - index_value = $(this).find('.original :hidden:first').val(); - if (index_value != "" && index_value != undefined) + var index_value = $(this).find('.original :hidden:first').val(); + if (index_value !== '' && index_value !== undefined) + { indexes.push(index_value); + } }); - + $.ajax({ url: ui.item.parent().find(':hidden[name="admin_sorting_url"]').val(), type: 'POST', diff --git a/adminsortable/utils.py b/adminsortable/utils.py new file mode 100644 index 0000000..5cc41b1 --- /dev/null +++ b/adminsortable/utils.py @@ -0,0 +1,4 @@ +def get_is_sortable(objects): + if len(objects) > 1: + return True + return False diff --git a/sample_project/app/admin.py b/sample_project/app/admin.py index 4975ae0..c382be1 100644 --- a/sample_project/app/admin.py +++ b/sample_project/app/admin.py @@ -2,12 +2,42 @@ from django.contrib import admin from adminsortable.admin import (SortableAdmin, SortableTabularInline, SortableStackedInline, SortableGenericStackedInline) -from app.models import Category, Project, Credit, Note, GenericNote +from adminsortable.utils import get_is_sortable +from app.models import (Category, Widget, Project, Credit, Note, GenericNote, + Component) admin.site.register(Category, SortableAdmin) +class ComponentInline(SortableStackedInline): + model = Component + + def queryset(self, request): + qs = super(ComponentInline, self).queryset(request).exclude(title__icontains='2') + if get_is_sortable(qs): + self.model.is_sortable = True + else: + self.model.is_sortable = False + return qs + + +class WidgetAdmin(SortableAdmin): + def queryset(self, request): + """ + A simple example demonstrating that adminsortable works even in + situations where you need to filter the queryset in admin. Here, + we are just filtering out `widget` instances with an pk higher + than 3 + """ + qs = super(WidgetAdmin, self).queryset(request) + return qs.filter(id__lte=3) + + inlines = [ComponentInline] + +admin.site.register(Widget, WidgetAdmin) + + class CreditInline(SortableTabularInline): model = Credit diff --git a/sample_project/app/migrations/0001_initial.py b/sample_project/app/migrations/0001_initial.py new file mode 100644 index 0000000..4633a2e --- /dev/null +++ b/sample_project/app/migrations/0001_initial.py @@ -0,0 +1,123 @@ +# -*- coding: 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 'Category' + db.create_table(u'app_category', ( + (u'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)), + )) + db.send_create_signal(u'app', ['Category']) + + # Adding model 'Project' + db.create_table(u'app_project', ( + (u'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('adminsortable.fields.SortableForeignKey')(to=orm['app.Category'])), + ('description', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal(u'app', ['Project']) + + # Adding model 'Credit' + db.create_table(u'app_credit', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Project'])), + ('first_name', self.gf('django.db.models.fields.CharField')(max_length=30)), + ('last_name', self.gf('django.db.models.fields.CharField')(max_length=30)), + )) + db.send_create_signal(u'app', ['Credit']) + + # Adding model 'Note' + db.create_table(u'app_note', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Project'])), + ('text', self.gf('django.db.models.fields.CharField')(max_length=100)), + )) + db.send_create_signal(u'app', ['Note']) + + # Adding model 'GenericNote' + db.create_table(u'app_genericnote', ( + (u'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)), + ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='generic_notes', to=orm['contenttypes.ContentType'])), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + )) + db.send_create_signal(u'app', ['GenericNote']) + + + def backwards(self, orm): + # Deleting model 'Category' + db.delete_table(u'app_category') + + # Deleting model 'Project' + db.delete_table(u'app_project') + + # Deleting model 'Credit' + db.delete_table(u'app_credit') + + # Deleting model 'Note' + db.delete_table(u'app_note') + + # Deleting model 'GenericNote' + db.delete_table(u'app_genericnote') + + + models = { + u'app.category': { + 'Meta': {'ordering': "['order']", 'object_name': 'Category'}, + u'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'}) + }, + u'app.credit': { + 'Meta': {'ordering': "['order']", 'object_name': 'Credit'}, + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + u'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': u"orm['app.Project']"}) + }, + u'app.genericnote': { + 'Meta': {'ordering': "['order']", 'object_name': 'GenericNote'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'generic_notes'", 'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'app.note': { + 'Meta': {'ordering': "['order']", 'object_name': 'Note'}, + u'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': u"orm['app.Project']"}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'app.project': { + 'Meta': {'ordering': "['order']", 'object_name': 'Project'}, + 'category': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'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'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['app'] \ No newline at end of file diff --git a/sample_project/app/migrations/0002_add_widget.py b/sample_project/app/migrations/0002_add_widget.py new file mode 100644 index 0000000..b7f66b8 --- /dev/null +++ b/sample_project/app/migrations/0002_add_widget.py @@ -0,0 +1,78 @@ +# -*- coding: 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 'Widget' + db.create_table(u'app_widget', ( + (u'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)), + )) + db.send_create_signal(u'app', ['Widget']) + + + def backwards(self, orm): + # Deleting model 'Widget' + db.delete_table(u'app_widget') + + + models = { + u'app.category': { + 'Meta': {'ordering': "['order']", 'object_name': 'Category'}, + u'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'}) + }, + u'app.credit': { + 'Meta': {'ordering': "['order']", 'object_name': 'Credit'}, + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + u'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': u"orm['app.Project']"}) + }, + u'app.genericnote': { + 'Meta': {'ordering': "['order']", 'object_name': 'GenericNote'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'generic_notes'", 'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'app.note': { + 'Meta': {'ordering': "['order']", 'object_name': 'Note'}, + u'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': u"orm['app.Project']"}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'app.project': { + 'Meta': {'ordering': "['order']", 'object_name': 'Project'}, + 'category': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'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'}) + }, + u'app.widget': { + 'Meta': {'ordering': "['order']", 'object_name': 'Widget'}, + u'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'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['app'] \ No newline at end of file diff --git a/sample_project/app/migrations/0003_add_component.py b/sample_project/app/migrations/0003_add_component.py new file mode 100644 index 0000000..6442d69 --- /dev/null +++ b/sample_project/app/migrations/0003_add_component.py @@ -0,0 +1,86 @@ +# -*- coding: 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 'Component' + db.create_table(u'app_component', ( + (u'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)), + ('widget', self.gf('adminsortable.fields.SortableForeignKey')(to=orm['app.Widget'])), + )) + db.send_create_signal(u'app', ['Component']) + + + def backwards(self, orm): + # Deleting model 'Component' + db.delete_table(u'app_component') + + + models = { + u'app.category': { + 'Meta': {'ordering': "['order']", 'object_name': 'Category'}, + u'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'}) + }, + u'app.component': { + 'Meta': {'ordering': "['order']", 'object_name': 'Component'}, + u'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'}), + 'widget': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Widget']"}) + }, + u'app.credit': { + 'Meta': {'ordering': "['order']", 'object_name': 'Credit'}, + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + u'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': u"orm['app.Project']"}) + }, + u'app.genericnote': { + 'Meta': {'ordering': "['order']", 'object_name': 'GenericNote'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'generic_notes'", 'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'app.note': { + 'Meta': {'ordering': "['order']", 'object_name': 'Note'}, + u'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': u"orm['app.Project']"}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'app.project': { + 'Meta': {'ordering': "['order']", 'object_name': 'Project'}, + 'category': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Category']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'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'}) + }, + u'app.widget': { + 'Meta': {'ordering': "['order']", 'object_name': 'Widget'}, + u'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'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['app'] \ No newline at end of file diff --git a/sample_project/app/migrations/__init__.py b/sample_project/app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sample_project/app/models.py b/sample_project/app/models.py index 0f972b8..b3622b1 100644 --- a/sample_project/app/models.py +++ b/sample_project/app/models.py @@ -16,7 +16,7 @@ class SimpleModel(models.Model): return self.title -#a model that is sortable +# A model that is sortable class Category(SimpleModel, Sortable): class Meta(Sortable.Meta): """ @@ -27,8 +27,17 @@ class Category(SimpleModel, Sortable): verbose_name_plural = 'Categories' -#a model that is sortable relative to a foreign key that is also sortable -#uses SortableForeignKey field. Works with versions 1.3+ +# A model with an override of its queryset for admin +class Widget(SimpleModel, Sortable): + class Meta(Sortable.Meta): + pass + + def __unicode__(self): + return self.title + + +# A model that is sortable relative to a foreign key that is also sortable +# uses SortableForeignKey field. Works with versions 1.3+ class Project(SimpleModel, Sortable): class Meta(Sortable.Meta): pass @@ -37,7 +46,7 @@ class Project(SimpleModel, Sortable): description = models.TextField() -#registered as a tabular inline on `Project` +# Registered as a tabular inline on `Project` class Credit(Sortable): class Meta(Sortable.Meta): pass @@ -50,7 +59,7 @@ class Credit(Sortable): return '{0} {1}'.format(self.first_name, self.last_name) -#registered as a stacked inline on `Project` +# Registered as a stacked inline on `Project` class Note(Sortable): class Meta(Sortable.Meta): pass @@ -62,7 +71,7 @@ class Note(Sortable): return self.text -#a generic bound model +# A generic bound model class GenericNote(SimpleModel, Sortable): content_type = models.ForeignKey(ContentType, verbose_name=u"Content type", related_name="generic_notes") @@ -75,3 +84,14 @@ class GenericNote(SimpleModel, Sortable): def __unicode__(self): return u'{0} : {1}'.format(self.title, self.content_object) + + +# An model registered as an inline that has a custom queryset +class Component(SimpleModel, Sortable): + class Meta(Sortable.Meta): + pass + + widget = SortableForeignKey(Widget) + + def __unicode__(self): + return self.title diff --git a/sample_project/database/test_project.sqlite b/sample_project/database/test_project.sqlite index fd92698..6629776 100644 Binary files a/sample_project/database/test_project.sqlite and b/sample_project/database/test_project.sqlite differ diff --git a/sample_project/sample_project/settings.py b/sample_project/sample_project/settings.py index cc30652..6fa84d6 100644 --- a/sample_project/sample_project/settings.py +++ b/sample_project/sample_project/settings.py @@ -122,13 +122,12 @@ INSTALLED_APPS = ( 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: 'django.contrib.admindocs', 'adminsortable', 'app', + 'south', ) # A sample logging configuration. The only tangible logging