Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Get your Django models in order

License

NotificationsYou must be signed in to change notification settings

django-ordered-model/django-ordered-model

Repository files navigation

Python versionsBuild StatusPyPI versionCode style: black

django-ordered-model allows models to be ordered and provides a simple admininterface for reordering them.

Based onhttps://djangosnippets.org/snippets/998/ andhttps://djangosnippets.org/snippets/259/

See ourcompatability notes for the appropriate version to use with older Django and Python releases.

Installation

Please install using Pip:

$ pip install django-ordered-model

Or if you have checked out the repository:

$ python setup.py install

Or to use the latest development code from our master branch:

$ pip uninstall django-ordered-model$ pip install git+git://github.com/django-ordered-model/django-ordered-model.git

Usage

Addordered_model to yourSETTINGS.INSTALLED_APPS.

Inherit your model fromOrderedModel to make it ordered:

fromdjango.dbimportmodelsfromordered_model.modelsimportOrderedModelclassItem(OrderedModel):name=models.CharField(max_length=100)

Then run the usual$ ./manage.py makemigrations and$ ./manage.py migrate to update your database schema.

Model instances now have a set of methods to move them relative to each other.To demonstrate those methods we create two instances ofItem:

foo=Item.objects.create(name="Foo")bar=Item.objects.create(name="Bar")

Swap positions

foo.swap(bar)

This swaps the position of two objects.

Move position up on position

foo.up()foo.down()

Moving an object up or down just makes it swap its position with the neighbouringobject directly above of below depending on the direction.

Move to arbitrary position

foo.to(12)bar.to(13)

Move the object to an arbitrary position in the stack. This essentially sets theorder value to the specified integer. Objects between the original and the newposition get their order value increased or decreased according to the directionof the move.

Move object above or below reference

foo.above(bar)foo.below(bar)

Move the object directly above or below the reference object, increasing ordecreasing the order value for all objects between the two, depending on thedirection of the move.

Move to top of stack

foo.top()

This sets the order value to the lowest value found in the stack and increasesthe order value of all objects that were above the moved object by one.

Move to bottom of stack

foo.bottom()

This sets the order value to the highest value found in the stack and decreasesthe order value of all objects that were below the moved object by one.

Updating fields that would be updated during save()

For performance reasons, thedelete(),to(),below(),above(),top(), andbottom() methods use Django'supdate() method to change the order of other objectsthat are shifted as a result of one of these calls. If the model has fields thatare typically updated in a customized save() method, or through other app levelfunctionality such asDateTimeField(auto_now=True), you can add additional fieldsto be passed through toupdate(). This will only impact objects where their orderis being shifted as a result of an operation on the target object, not the targetobject itself.

foo.to(12,extra_update={'modified':now()})

Get the previous or next objects

foo.previous()foo.next()

Theprevious() andnext() methods return the neighbouring objects directly above or belowwithin the ordered stack.

Subset Ordering

In some cases, ordering objects is required only on a subset of objects. For example,an application that manages contact lists for users, in a many-to-one/many relationship,would like to allow each user to order their contacts regardless of how other userschoose their order. This option is supported via theorder_with_respect_to parameter.

A simple example might look like so:

classContact(OrderedModel):user=models.ForeignKey(User,on_delete=models.CASCADE)phone=models.CharField()order_with_respect_to='user'

If objects are ordered with respect to more than one field,order_with_respect_to supportstuples to define multiple fields:

classModel(OrderedModel)# ...order_with_respect_to= ('field_a','field_b')

In a many-to-many relationship you need to use a separate through model which is derived from the OrderedModel.For example, an application which manages pizzas with toppings.

A simple example might look like so:

classTopping(models.Model):name=models.CharField(max_length=100)classPizza(models.Model):name=models.CharField(max_length=100)toppings=models.ManyToManyField(Topping,through='PizzaToppingsThroughModel')classPizzaToppingsThroughModel(OrderedModel):pizza=models.ForeignKey(Pizza,on_delete=models.CASCADE)topping=models.ForeignKey(Topping,on_delete=models.CASCADE)order_with_respect_to='pizza'classMeta:ordering= ('pizza','order')

You can also specifyorder_with_respect_to to a field on a related model. An example use-case can be made with the following models:

classItemGroup(models.Model):user=models.ForeignKey(User,on_delete=models.CASCADE)general_info=models.CharField(max_length=100)classGroupedItem(OrderedModel):group=models.ForeignKey(ItemGroup,on_delete=models.CASCADE)specific_info=models.CharField(max_length=100)order_with_respect_to='group__user'

Here items are put into groups that have some general information used by its items, but the ordering of the items is independent of the group the item is in.

In all casesorder_with_respect_to must specify aForeignKey field on the model, or a Django CheckE002,E005 orE006 error will be raised with further help.

When you want ordering on the baseclass instead of subclasses in an ordered list of objects of various classes, specify the full module path of the base class:

classBaseQuestion(OrderedModel):order_class_path=__module__+'.BaseQuestion'question=models.TextField(max_length=100)classMeta:ordering= ('order',)classMultipleChoiceQuestion(BaseQuestion):good_answer=models.TextField(max_length=100)wrong_answer1=models.TextField(max_length=100)wrong_answer2=models.TextField(max_length=100)wrong_answer3=models.TextField(max_length=100)classOpenQuestion(BaseQuestion):answer=models.TextField(max_length=100)

Ordering of ManyToMany Relationship query results

Django ManyToMany relationships created byManyToManyFielddo not respectMeta.ordering on the intermediate model in results fetched from the 'members' queryset. For example with our usualPizza example, getting theToppings for ahawaiian_pizza instance usingPizzaToppingsThroughModel.objects.filter(pizza=hawaiian_pizza).all() is correctly ordered (by the ThroughModelMeta.ordering). Howeverhawaiian_pizza.toppings.all() is not, and returns the objects following the 'to' model ordering.

To work around this, explicitly add an ordering clause, e.g. withhawaiian_pizza.toppings.all().order_by('pizzatoppingsthroughmodel__order') or use ourOrderedManyToManyField which does this by default:

fromordered_model.fieldsimportOrderedManyToManyFieldclassPizza(models.Model):name=models.CharField(max_length=100)toppings=OrderedManyToManyField(Topping,through="PizzaToppingsThroughModel")classPizzaToppingsThroughModel(OrderedModel):pizza=models.ForeignKey(Pizza,on_delete=models.CASCADE)topping=models.ForeignKey(Topping,on_delete=models.CASCADE)order_with_respect_to="pizza"classMeta:ordering= ("pizza","order")

With this definitionhawaiian_pizza.toppings.all() returns toppings in order.

Custom Manager and QuerySet

When your model extendsOrderedModel, it inherits a customModelManager instance which in turn provides additional operations on the resultingQuerySet. For example ifItem is anOrderedModel subclass, the querysetItem.objects.all() has functions:

  • above_instance(object),
  • below_instance(object),
  • get_min_order(),
  • get_max_order(),
  • above(index),
  • below(index)

If yourModel uses a customModelManager (such asItemManager below) please have it extendOrderedModelManager, or else Django CheckE003 will be raised.

If yourModelManager returns a customQuerySet (such asItemQuerySet below) please have it extendOrderedModelQuerySet, or Django CheckE004 will be raised.

fromordered_model.modelsimportOrderedModel,OrderedModelManager,OrderedModelQuerySetclassItemQuerySet(OrderedModelQuerySet):passclassItemManager(OrderedModelManager):defget_queryset(self):returnItemQuerySet(self.model,using=self._db)classItem(OrderedModel):objects=ItemManager()

If another Django plugin requires you to use specificModel,QuerySet orModelManager classes, you might need to construct intermediate classes using multiple inheritance,see an example in issue 270.

Custom ordering field

ExtendingOrderedModel creates amodels.PositiveIntegerField field calledorder and the appropriate migrations. It customises the defaultclass Meta to then order returned querysets by this field. If you wish to use an existing model field to store the ordering, subclassOrderedModelBase instead and set the attributeorder_field_name to match your field name and theordering attribute onMeta:

classMyModel(OrderedModelBase):    ...sort_order=models.PositiveIntegerField(editable=False,db_index=True)order_field_name="sort_order"classMeta:ordering= ("sort_order",)

Settingorder_field_name is specific for this library to know which field to change when ordering actions are taken. TheMetaordering line is existing Django functionality to use a field for sorting.Seetests/models.py objectCustomOrderFieldModel for an example.

Admin integration

To add arrows in the admin change list page to do reordering, you can use theOrderedModelAdmin and themove_up_down_links field:

fromdjango.contribimportadminfromordered_model.adminimportOrderedModelAdminfrommodelsimportItemclassItemAdmin(OrderedModelAdmin):list_display= ('name','move_up_down_links')admin.site.register(Item,ItemAdmin)

ItemAdmin screenshot

For a many-to-many relationship you need one of the following inlines.

OrderedTabularInline orOrderedStackedInline just like the django admin.

For theOrderedTabularInline it will look like this:

fromdjango.contribimportadminfromordered_model.adminimportOrderedTabularInline,OrderedInlineModelAdminMixinfrommodelsimportPizza,PizzaToppingsThroughModelclassPizzaToppingsTabularInline(OrderedTabularInline):model=PizzaToppingsThroughModelfields= ('topping','order','move_up_down_links',)readonly_fields= ('order','move_up_down_links',)ordering= ('order',)extra=1classPizzaAdmin(OrderedInlineModelAdminMixin,admin.ModelAdmin):model=Pizzalist_display= ('name', )inlines= (PizzaToppingsTabularInline, )admin.site.register(Pizza,PizzaAdmin)

PizzaAdmin screenshot

For theOrderedStackedInline it will look like this:

fromdjango.contribimportadminfromordered_model.adminimportOrderedStackedInline,OrderedInlineModelAdminMixinfrommodelsimportPizza,PizzaToppingsThroughModelclassPizzaToppingsStackedInline(OrderedStackedInline):model=PizzaToppingsThroughModelfields= ('topping','move_up_down_links',)readonly_fields= ('move_up_down_links',)ordering= ('order',)extra=1classPizzaAdmin(OrderedInlineModelAdminMixin,admin.ModelAdmin):list_display= ('name', )inlines= (PizzaToppingsStackedInline, )admin.site.register(Pizza,PizzaAdmin)

PizzaAdmin screenshot

Note:OrderedModelAdmin requires the inline subclasses ofOrderedTabularInline andOrderedStackedInline to be listed oninlines so that we register appropriate URL routes. If you are using Django 3.0 featureget_inlines() orget_inline_instances() to return the list of inlines dynamically, consider it a filter and still add them toinlines or you might encounter a “No Reverse Match” error when accessing model change view.

Re-ordering models

In certain cases the models will end up in a not properly ordered state. This can be causedby bypassing the 'delete' / 'save' methods, or when a user changes a foreign key of a objectwhich is part of the 'order_with_respect_to' fields. You can use the following command tore-order one or more models.

$ ./manage.py reorder_model <app_name>.<model_name> \        [<app_name>.<model_name> ... ]The arguments are as follows:- `<app_name>`: Name of the application for the model.- `<model_name>`: Name of the model that's an OrderedModel.

Django Rest Framework

To support updating ordering fields by Django Rest Framework, we include a serializerOrderedModelSerializer that intercepts writes to the ordering field, and callsOrderedModel.to() method to effect a re-ordering:

fromrest_frameworkimportrouters,serializers,viewsetsfromordered_model.serializersimportOrderedModelSerializerfromtests.modelsimportCustomItemclassItemSerializer(serializers.HyperlinkedModelSerializer,OrderedModelSerializer):classMeta:model=CustomItemfields= ['pkid','name','modified','order']classItemViewSet(viewsets.ModelViewSet):queryset=CustomItem.objects.all()serializer_class=ItemSerializerrouter=routers.DefaultRouter()router.register(r'items',ItemViewSet)

Note that you need to include the 'order' field (or your custom field name) in theSerializer'sfields list, either explicitly or using__all__. Seeordered_model/serializers.py for the implementation.

Test suite

To run the tests against your current environment, use:

$ pip install djangorestframework$ django-admintest --pythonpath=. --settings=tests.settings

Otherwise please installtox and run the tests for a specific environment with-e or all environments:

$ tox -e py36-django30$ tox

Compatibility with Django and Python

django-ordered-model versionDjango versionPython versionDRF (optional)
3.8.x3.x,4.x,5.x3.10 to3.123.15 and above
3.7.x3.x,4.x3.5 and above3.12 and above
3.6.x3.x,4.x3.5 and above3.12 and above
3.5.x3.x,4.x3.5 and above-
3.4.x2.x,3.x3.5 and above-
3.3.x2.x3.4 and above-
3.2.x2.x3.4 and above-
3.1.x2.x3.4 and above-
3.0.x2.x3.4 and above-
2.1.x1.x2.7 to 3.6-
2.0.x1.x2.7 to 3.6-

Maintainers


[8]ページ先頭

©2009-2025 Movatter.jp