跳转到主要内容

非官方的Django可交换模型API。

项目描述

Swapper

Dependency monitoring

Django Swappable Models - 不再仅适用于auth.User!

Swapper是一个非官方API,用于Django的一个未记录但非常强大的功能:可交换模型。Swapper简化了在您自己的可重用应用中实现任意可交换模型的过程。

Latest PyPI Release Release Notes License GitHub Stars GitHub Forks GitHub Issues

Build Status Python Support Django Support

动机

假设您的可重用应用有两个相关表

from django.db import models
class Parent(models.Model):
    name = models.TextField()

class Child(models.Model):
    name = models.TextField()
    parent = models.ForeignKey(Parent)

假设进一步,您希望允许用户子类化这两个模型之一或两个,并补充它们自己的额外字段。您可以为此使用抽象类(例如BaseParentBaseChild),但这样您就需要

  1. 避免在BaseChild上放置外键,并告诉用户他们需要这样做。
  2. BaseChild上放置外键,但使Parent成为一个不能交换的实体模型
  3. 使用可交换模型,并结合读取可交换设置的ForeignKeys

这种第三种方法是由Django采用的,以便于交换auth.User模型auth.User的可交换代码以通用方式实现,使其可以用于任何模型。尽管这种功能目前尚无文档记录,且任何剩余问题都在解决中,但根据我们的经验,它已经证明非常稳定且非常有用。

Swapper本质上是围绕现有功能的简单API包装器。请注意,Swapper主要是一个面向库作者的工具;通常,你的可重用应用程序的用户不需要了解Swapper就能使用它。(见下面的最终用户文档。)

现实世界的示例

Swapper在几个OpenWISP软件包中被广泛使用,以方便定制和扩展。值得注意的例子包括

在这些软件包中使用Swapper促进了软件重用,这是OpenWISP项目的核心价值观之一。

创建可重用应用程序

首先,确保你已经安装了swapper。如果你将你的可重用应用程序作为Python包发布,请确保将swapper添加到你的项目依赖项中(例如setup.py),以确保你的应用程序用户在集成时不会出错。

pip3 install swapper

扩展上面的示例,你可以创建两个抽象基类及其相应的默认实现

# reusableapp/models.py
from django.db import models
import swapper

class BaseParent(models.Model):
    # minimal base implementation ...
    class Meta:
        abstract = True

class Parent(BaseParent):
    # default (swappable) implementation ...
    class Meta:
       swappable = swapper.swappable_setting('reusableapp', 'Parent')

class BaseChild(models.Model):
    parent = models.ForeignKey(swapper.get_model_name('reusableapp', 'Parent'))
    # minimal base implementation ...
    class Meta:
        abstract = True

class Child(BaseChild):
    # default (swappable) implementation ...
    class Meta:
       swappable = swapper.swappable_setting('reusableapp', 'Child')

加载交换模型

在你的可重用视图和其他函数中,始终使用Swapper而不是直接导入可交换模型。这是因为你可能不知道你的应用程序的用户是使用你的默认实现还是他们自己的版本。

# reusableapp/views.py

# Might work, might not
# from .models import Parent

import swapper
Parent = swapper.load_model("reusableapp", "Parent")
Child = swapper.load_model("reusableapp", "Child")

def view(request, *args, **kwargs):
    qs = Parent.objects.all()
    # ...

注意:swapper.load_model()get_user_model()的一般等效,并受到相同的约束:例如,它应该在模型系统完全初始化之后再使用。

迁移脚本

Swapper也可以用于迁移脚本,以方便依赖项排序和外键引用。要在你的库中使用此功能,使用makemigrations生成迁移脚本,并做出以下更改。一般来说,你的库用户不需要对其自己的迁移脚本做出任何类似的更改。唯一的例外是,如果你有多个具有相互指向的外键的可交换模型层级。

  # reusableapp/migrations/0001_initial.py

  from django.db import models, migrations
< from django.conf import settings
> import swapper

  class Migration(migrations.Migration):

      dependencies = [
<          migrations.swappable_dependency(settings.REUSABLEAPP_PARENT_MODEL),
>          swapper.dependency('reusableapp', 'Parent')
      ]

      operations = [
          migrations.CreateModel(
              name='Child',
              fields=[
                  ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')),
              ],
              options={
<                 'swappable': 'REUSABLEAPP_CHILD_MODEL',
>                 'swappable': swapper.swappable_setting('reusableapp', 'Child'),
              },
              bases=(models.Model,),
          ),
          migrations.CreateModel(
              name='Parent',
              fields=[
                  ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')),
              ],
              options={
<                 'swappable': 'REUSABLEAPP_PARENT_MODEL',
>                 'swappable': swapper.swappable_setting('reusableapp', 'Parent'),
              },
              bases=(models.Model,),
          ),
          migrations.AddField(
              model_name='child',
              name='parent',
<             field=models.ForeignKey(to=settings.REUSABLEAPP_PARENT_MODEL),
>             field=models.ForeignKey(to=swapper.get_model_name('reusableapp', 'Parent')),
              preserve_default=True,
          ),
      ]

最终用户文档

有了上述设置,你的应用程序用户可以在他们的应用程序中覆盖一个或两个模型。你可以为他们提供一个这样的示例

# myapp/models.py
from reusableapp.models import BaseParent
class Parent(BaseParent):
    # custom implementation ...

然后,告诉你的用户更新他们的设置以触发交换。

# myproject/settings.py
REUSABLEAPP_PARENT_MODEL = "myapp.Parent"

目标是使这个过程对你的最终用户来说就像交换auth.User模型一样简单。与auth.User一样,有一些重要的注意事项你可能希望通知你的用户。

最大的问题是,您的用户可能需要在定义myapp的实现之前,先定义交换模型的设置before creating any migrations。由于Django迁移基础设施中的关键假设,很难从一个默认(非交换)模型开始,然后在不进行一些迁移修改的情况下切换到交换实现。这有点尴尬——因为您的用户很可能想在决定自定义之前先尝试您的默认实现。遗憾的是,由于Django核心中当前交换设置的实现方式,没有简单的解决方案。这可能在未来的Django版本中得到解决(请参阅#10Django问题#25313)。

API文档

以下是swapper的完整API,您可能在创建可重用应用代码时发现它很有用。您的库的最终用户通常不需要参考此API。

函数 目的
swappable_setting(app_label, model) 为提供的模型生成交换设置名称(例如"REUSABLEAPP_PARENT_MODEL"
is_swapped(app_label, model) 确定给定的模型是否已交换。如果已交换,则返回模型名称,否则返回False
get_model_name(app_label, model) 获取交换模型已交换的模型名称(或未交换时原始模型的名称。)
get_model_names(app_label, models) 将模型名称列表与其交换版本进行匹配。所有模型应来自同一应用(尽管它们的交换版本不需要是。)
load_model(app_label, model, required=True) 为交换模型加载交换模型类(或未交换时原始模型)。如果您的代码可以在没有指定模型的情况下运行,请将required = False
dependency(app_label, model, version=None) 为迁移生成依赖项元组。仅在依赖项的目标迁移的第一个迁移不起作用时(例如,当需要依赖于特定迁移时)使用version,我们建议避免使用version='__latest__',因为它在依赖模块中添加新迁移时可能会有严重的缺点
set_app_prefix(app_label, prefix) 为交换设置设置自定义前缀(默认为app_label的大写形式)。如果应用具有长名称或属于更大的框架,这很有用。应在models.py的顶部设置此设置。
join(app_label, model)split(model) 用于拆分和连接"app.Model"字符串和("app", "Model")元组的实用程序。

项目详情


下载文件

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

源代码发行版

swapper-1.4.0.tar.gz (12.7 kB 查看哈希

上传于 源代码

构建分发版

swapper-1.4.0-py2.py3-none-any.whl (7.1 kB 查看哈希值)

上传于 Python 2 Python 3

由以下支持