Django 1.8 ArrayField的添加和扩展

24

Django 1.8 将推出新的高级字段类型,包括ArrayField,这些依赖于PostgreSQL,并在数据库级别实现。

PostgreSQL的数组字段实现了一个附加方法

然而,我找不到任何关于向ArrayField添加项目的文档。这显然非常有用,因为它允许更新字段而不必将其所有内容从数据库传输回来再传回去。

这是可能的吗?如果不行,将来会可能吗?如果有我错过的文档,请多指教。

为了澄清我的问题,以下是示例代码:

注意:这只是虚构的代码,我认为它不起作用(我没有尝试过)

# on model:
class Post(models.Model):
    tags = ArrayField(models.CharField(max_length=200))

# somewhere else:
p = Post.objects.create(tags=[str(i) for i in range(10000)])
p.tags.append('hello')

目前是否有不使用原始 SQL 的方法来完成这个操作?

6个回答

31
注意:OP代码肯定会起作用。我们只需要保存模型(因为这只是一个模型字段,而不是关系)。让我们看看:
>>> p = Post.objects.create(tags=[str(i) for i in range(10000)])
>>> p.tags.append("working!")
>>> p.save()
>>> working_post = Post.objects.get(tags__contains=["working!"])
<Post: Post object>
>>> working_post.tags[-2:]
[u'9999', u'working!']

更深入地了解

Django将ArrayField作为Python列表

代码参考

你可以用列表做的一切都可以用ArrayField来实现。甚至可以进行排序

Django将ArrayField保存为Python列表

代码参考

这意味着它保存了Python列表的结构和元素。


4
它实际上并没有在数据库层面上追加。它会检索数组中的所有元素,执行列表追加操作,然后将整个列表发送回数据库。 - fkoksal

11

我认为您正在寻找的功能目前尚未实现(并且可能没有计划实现)。许多Postgres contrib功能是基于这个kickstarter项目创立的。

我发现最有用的新功能文档来自源代码本身。它包括许多这些功能的原始拉取请求的链接。

在涉及到数组函数时,需要注意的重要说明是它们是函数,并且可以说是超出了典型ORM的范围

我希望这些信息对您有用并且能够找到一个好的解决方案。


谢谢,这正是我所预料/担心的。我会保留这个问题,以防有人提供一个优雅的代码解决方案。 - SColvin
1
这些特性已经实现了。请查看我的答案。 - Daniil Ryzhkov
1
@Daniil 你说的一切都是完全准确的。我可能误解了问题,但我认为SColvin想要使用Postgres函数而不是从Python执行UPDATE并将整个数组发送回Postgres。 - erik-e
1
@erik-e,我的错,抱歉! - Daniil Ryzhkov

11

这个有效:

from django.db.models import F
from django.db.models.expressions import CombinedExpression, Value

post = Post.objects.get(id=1000)
post.tags = CombinedExpression(F('tags'), '||', Value(['hello']))
post.save()

或在更新语句中:

Post.objects.filter(created_on__lt=now() - timespan(days=30))\
    .update(tags=CombinedExpression(F('tags'), '||', Value(['old'])))

1
这对我不起作用。我在Postgres的一侧收到了类型错误:它将要附加的值(例如“old”)视为文本类型[],将DB中已有的值视为varying[]类型,然后抱怨无法操作这些类型。有什么解决方法吗?我尝试为Value指定ArrayField(CharField(..))的output_field,但行为相同。 - astrognocci

5

另一种解决方案是使用自定义表达式。我在Django 1.11和Python 3.6(f-strings)中测试了以下代码。

from django.db.models.expressions import Func

class ArrayAppend(Func):

    function = 'array_append'
    template = "%(function)s(%(expressions)s, %(element)s)"
    arity = 1

    def __init__(self, expression: str, element, **extra):
        if not isinstance(element, (str, int)):
            raise TypeError(
                f'Type of "{element}" must be int or str, '
                f'not "{type(element).__name__}".'
            )

        super().__init__(
            expression,
            element=isinstance(element, int) and element or f"'{element}'",
            **extra,
        )

表达式可以在update()中使用:
Post.objects \
    .filter(pk=1) \
    .update(tags=ArrayAppend('tags', 'new tag'))

2
您可以使用django_postgres_extensions,它支持许多函数,如追加、前置、删除和连接等。但是,如果您像我一样使用Django 1.8,则应仅使用此包中所需的类。这样,您就不必更改数据库后端。我已经在这里粘贴了所需的类。按照第一个链接中的说明使用它们即可。

2
这绝对有效。
from django.db.models import F
from django.db.models.expressions import CombinedExpression, Value

Post.objects.filter(created_on__lt=now() - timespan(days=30)).update(tags=CombinedExpression(F('tags'), '||', Value('{hello}')))

请注意我和Yotam Ofek的变体之间的区别:

Value(["hello"]) should be changed to Value("{hello}")

如果您需要排除某些特定标签的条目,请使用以下方法:

.exclude(tags__contains="{hello}")

再稍等一下,"tags"字段应该有默认值为list。

tags = ArrayField(models.CharField(max_length=200), default=list)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接