跳转到主要内容

允许Django模型可排序,并为重新排序它们提供简单的管理界面。

项目描述

django-ordered-model

Build Status PyPI version codecov Code style: black

django-ordered-model允许模型可排序,并为重新排序它们提供简单的管理界面。

基于https://djangosnippets.org/snippets/998/https://djangosnippets.org/snippets/259/

请参阅我们的兼容性说明,了解与较旧的Django和Python版本一起使用的适当版本。

安装

请使用Pip进行安装

$ pip install django-ordered-model

或者如果你已经检出存储库

$ python setup.py install

或者要使用我们从master分支的最新开发代码

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

使用方法

ordered_model添加到您的SETTINGS.INSTALLED_APPS

OrderedModel继承您的模型以使其可排序

from django.db import models
from ordered_model.models import OrderedModel


class Item(OrderedModel):
    name = models.CharField(max_length=100)

然后运行常规的$ ./manage.py makemigrations$ ./manage.py migrate以更新您的数据库模式。

模型实例现在有一组方法来相对移动它们。为了演示这些方法,我们创建了两个Item实例。

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

交换位置

foo.swap(bar)

这会交换两个对象的位置。

在位置上移动

foo.up()
foo.down()

移动一个对象向上或向下只是让它与直接上方或下方的相邻对象交换位置,具体取决于方向。

移动到任意位置

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

将对象移动到堆栈中的任意位置。这实际上将顺序值设置为指定的整数。原始位置和新位置之间的对象会根据移动的方向增加或减少其顺序值。

移动到参考对象的上方或下方

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

将对象直接移动到参考对象的上方或下方,根据移动的方向增加或减少两个对象之间的所有对象的顺序值。

移动到堆栈顶部

foo.top()

这会将顺序值设置为堆栈中找到的最低值,并将所有位于移动对象之上的对象的顺序值增加1。

移动到堆栈底部

foo.bottom()

这会将顺序值设置为堆栈中找到的最高值,并将所有位于移动对象之下的对象的顺序值减少1。

更新在save()期间更新的字段

出于性能考虑,delete()to()below()above()top()bottom()方法使用Django的update()方法来更改由这些调用引起的位移的其他对象的顺序。如果模型有在自定义的save()方法或通过其他应用级功能(如DateTimeField(auto_now=True))中通常更新的字段,可以将其他字段添加到传递给update()的字段中。这只会影响由于对目标对象的操作而改变顺序的对象,而不是目标对象本身。

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

获取上一个或下一个对象

foo.previous()
foo.next()

previous()next()方法返回在有序堆栈中直接上方或下方的相邻对象。

子集排序

在某些情况下,只需要对对象子集进行排序。例如,一个管理用户联系列表的应用程序,在多对一/多关系中,可能希望允许每个用户根据自己的选择对联系人进行排序。此选项通过order_with_respect_to参数支持。

一个简单的例子可能如下所示

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

如果对象根据多个字段进行排序,order_with_respect_to支持元组来定义多个字段

class Model(OrderedModel)
    # ...
    order_with_respect_to = ('field_a', 'field_b')

在多对多关系中,您需要使用一个从OrderedModel派生的独立通过模型。例如,一个管理披萨和配料的程序。

一个简单的例子可能如下所示

class Topping(models.Model):
    name = models.CharField(max_length=100)


class Pizza(models.Model):
    name = models.CharField(max_length=100)
    toppings = models.ManyToManyField(Topping, through='PizzaToppingsThroughModel')


class PizzaToppingsThroughModel(OrderedModel):
    pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE)
    topping = models.ForeignKey(Topping, on_delete=models.CASCADE)
    order_with_respect_to = 'pizza'

    class Meta:
        ordering = ('pizza', 'order')

您还可以将order_with_respect_to指定为相关模型上的字段。以下模型可以提供一个用例

class ItemGroup(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    general_info = models.CharField(max_length=100)

class GroupedItem(OrderedModel):
    group = models.ForeignKey(ItemGroup, on_delete=models.CASCADE)
    specific_info = models.CharField(max_length=100)
    order_with_respect_to = 'group__user'

在这里,项目被放入组中,这些组包含一些由其项目使用的一般信息,但项目的排序与其所在的组是独立的。

在所有情况下,order_with_respect_to必须指定模型上的ForeignKey字段,否则将引发Django检查错误E002E005E006,并提供进一步的帮助。

当您想在有序对象列表的各种类的基类中进行排序而不是在子类中进行时,请指定基类的完整模块路径

class BaseQuestion(OrderedModel):
    order_class_path = __module__ + '.BaseQuestion'
    question = models.TextField(max_length=100)

    class Meta:
        ordering = ('order',)

class MultipleChoiceQuestion(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)

class OpenQuestion(BaseQuestion):
    answer = models.TextField(max_length=100)

自定义管理器和查询集

当您的模型扩展OrderedModel时,它继承了一个自定义的ModelManager实例,该实例反过来提供对结果QuerySet的附加操作。例如,如果ItemOrderedModel的子类,则查询集Item.objects.all()具有以下函数

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

如果你的 Model 使用自定义的 ModelManager(例如下面的 ItemManager),请让它扩展 OrderedModelManager,否则将引发 Django 检查错误 E003

如果你的 ModelManager 返回自定义的 QuerySet(例如下面的 ItemQuerySet),请让它扩展 OrderedModelQuerySet,否则将引发 Django 检查错误 E004

from ordered_model.models import OrderedModel, OrderedModelManager, OrderedModelQuerySet

class ItemQuerySet(OrderedModelQuerySet):
    pass

class ItemManager(OrderedModelManager):
    def get_queryset(self):
        return ItemQuerySet(self.model, using=self._db)

class Item(OrderedModel):
    objects = ItemManager()

如果另一个 Django 插件要求你使用特定的 ModelQuerySetModelManager 类,你可能需要通过多重继承构造中间类,在第 270 个问题中查看示例

自定义排序字段

扩展 OrderedModel 会创建一个名为 ordermodels.PositiveIntegerField 字段和相应的迁移。它自定义默认的 class Meta,以便按此字段返回查询集。如果你希望使用现有的模型字段来存储排序,则改为子类化 OrderedModelBase 并将属性 order_field_name 设置为与你的字段名匹配,并在 Meta 上的 ordering 属性

class MyModel(OrderedModelBase):
    ...
    sort_order = models.PositiveIntegerField(editable=False, db_index=True)
    order_field_name = "sort_order"

    class Meta:
        ordering = ("sort_order",)

order_field_name 的设置仅针对此库,以便在执行排序操作时知道更改哪个字段。Meta 上的 ordering 行是现有的 Django 功能,用于使用字段进行排序。请参阅 tests/models.py 中的对象 CustomOrderFieldModel 以获取示例。

管理器集成

要在管理更改列表页面添加箭头以执行重新排序,可以使用 OrderedModelAdminmove_up_down_links 字段。

from django.contrib import admin
from ordered_model.admin import OrderedModelAdmin
from models import Item


class ItemAdmin(OrderedModelAdmin):
    list_display = ('name', 'move_up_down_links')

admin.site.register(Item, ItemAdmin)

ItemAdmin screenshot

对于多对多关系,你需要以下其中一个内联。

OrderedTabularInlineOrderedStackedInline,类似于 django 管理员。

对于 OrderedTabularInline,它看起来像这样

from django.contrib import admin
from ordered_model.admin import OrderedTabularInline, OrderedInlineModelAdminMixin
from models import Pizza, PizzaToppingsThroughModel


class PizzaToppingsTabularInline(OrderedTabularInline):
    model = PizzaToppingsThroughModel
    fields = ('topping', 'order', 'move_up_down_links',)
    readonly_fields = ('order', 'move_up_down_links',)
    ordering = ('order',)
    extra = 1


class PizzaAdmin(OrderedInlineModelAdminMixin, admin.ModelAdmin):
    model = Pizza
    list_display = ('name', )
    inlines = (PizzaToppingsTabularInline, )


admin.site.register(Pizza, PizzaAdmin)

PizzaAdmin screenshot

对于 OrderedStackedInline,它看起来像这样

from django.contrib import admin
from ordered_model.admin import OrderedStackedInline, OrderedInlineModelAdminMixin
from models import Pizza, PizzaToppingsThroughModel


class PizzaToppingsStackedInline(OrderedStackedInline):
    model = PizzaToppingsThroughModel
    fields = ('topping', 'move_up_down_links',)
    readonly_fields = ('move_up_down_links',)
    ordering = ('order',)
    extra = 1


class PizzaAdmin(OrderedInlineModelAdminMixin, admin.ModelAdmin):
    list_display = ('name', )
    inlines = (PizzaToppingsStackedInline, )


admin.site.register(Pizza, PizzaAdmin)

PizzaAdmin screenshot

注意:OrderedModelAdmin 要求将 OrderedTabularInlineOrderedStackedInline 的内联子类列在 inlines 中,以便注册适当的 URL 路由。如果你正在使用 Django 3.0 功能 get_inlines()get_inline_instances() 来动态返回内联列表,请将其视为过滤器,并仍然将其添加到 inlines 中,否则在访问模型更改视图时可能会遇到“无反向匹配”错误。

重新排序模型

在某些情况下,模型最终会处于未正确排序的状态。这可能是由于绕过 'delete'/'save' 方法或当用户更改 'order_with_respect_to' 字段中对象的 foreign key 时引起的。你可以使用以下命令重新排序一个或多个模型。

$ ./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

为了支持通过 Django Rest Framework 更新排序字段,我们包含了一个序列化器 OrderedModelSerializer,它拦截对排序字段的写入,并调用 OrderedModel.to() 方法来执行重新排序。

from rest_framework import routers, serializers, viewsets
from ordered_model.serializers import OrderedModelSerializer
from tests.models import CustomItem

class ItemSerializer(serializers.HyperlinkedModelSerializer, OrderedModelSerializer):
    class Meta:
        model = CustomItem
        fields = ['pkid', 'name', 'modified', 'order']

class ItemViewSet(viewsets.ModelViewSet):
    queryset = CustomItem.objects.all()
    serializer_class = ItemSerializer

router = routers.DefaultRouter()
router.register(r'items', ItemViewSet)

请注意,你需要将 'order' 字段(或你的自定义字段名)包含在 Serializerfields 列表中,无论是显式地还是使用 __all__。请参阅 ordered_model/serializers.py 以获取实现。

测试套件

要针对你的当前环境运行测试,请使用

$ pip install djangorestframework
$ django-admin test --pythonpath=. --settings=tests.settings

否则,请安装 tox 并使用 -e 运行特定环境的测试或所有环境的测试。

$ tox -e py36-django30
$ tox

Django 和 Python 的兼容性

django-ordered-model 版本 Django 版本 Python 版本 DRF(可选)
3.6.x 3.x4.x 3.5 及以上 3.12 及以上
3.5.x 3.x4.x 3.5 及以上 -
3.4.x 2.x3.x 3.5 及以上 -
3.3.x 2.x 3.4 及以上 -
3.2.x 2.x 3.4 及以上 -
3.1.x 2.x 3.4 及以上 -
3.0.x 2.x 3.4 及以上 -
2.1.x 1.x 2.7 至 3.6 -
2.0.x 1.x 2.7 至 3.6 -

维护者

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源代码分发

django-ordered-model-3.7.4.tar.gz (203.5 kB 查看散列值)

上传时间 源代码

构建分发

django_ordered_model-3.7.4-py3-none-any.whl (20.5 kB 查看散列值)

上传时间 Python 3

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面