标签归档:django-models

Django:为什么某些模型字段会相互冲突?

问题:Django:为什么某些模型字段会相互冲突?

我想创建一个包含2个指向用户的链接的对象。例如:

class GameClaim(models.Model):
    target = models.ForeignKey(User)
    claimer = models.ForeignKey(User)
    isAccepted = models.BooleanField()

但是运行服务器时出现以下错误:

  • 字段“目标”的访问器与相关字段“ User.gameclaim_set”冲突。在’target’的定义中添加related_name参数。

  • 字段“ claimer”的访问器与相关字段“ User.gameclaim_set”冲突。在“ claimer”的定义中添加一个related_name参数。

您能否解释为什么我会收到错误以及如何解决这些错误?

I want to create an object that contains 2 links to Users. For example:

class GameClaim(models.Model):
    target = models.ForeignKey(User)
    claimer = models.ForeignKey(User)
    isAccepted = models.BooleanField()

but I am getting the following errors when running the server:

  • Accessor for field ‘target’ clashes with related field ‘User.gameclaim_set’. Add a related_name argument to the definition for ‘target’.

  • Accessor for field ‘claimer’ clashes with related field ‘User.gameclaim_set’. Add a related_name argument to the definition for ‘claimer’.

Can you please explain why I am getting the errors and how to fix them?


回答 0

您有两个用户外键。Django自动创建一个从User到GameClaim的反向关系,通常是gameclaim_set。但是,由于您有两个FK,因此将具有两个gameclaim_set属性,这显然是不可能的。因此,您需要告诉Django用于反向关系的名称。

使用related_nameFK定义中的属性。例如

class GameClaim(models.Model):
    target = models.ForeignKey(User, related_name='gameclaim_targets')
    claimer = models.ForeignKey(User, related_name='gameclaim_users')
    isAccepted = models.BooleanField()

You have two foreign keys to User. Django automatically creates a reverse relation from User back to GameClaim, which is usually gameclaim_set. However, because you have two FKs, you would have two gameclaim_set attributes, which is obviously impossible. So you need to tell Django what name to use for the reverse relation.

Use the related_name attribute in the FK definition. e.g.

class GameClaim(models.Model):
    target = models.ForeignKey(User, related_name='gameclaim_targets')
    claimer = models.ForeignKey(User, related_name='gameclaim_users')
    isAccepted = models.BooleanField()

回答 1

User模型试图创建具有相同名称的两个领域,一个是GameClaims说有User作为target,而另一个用于GameClaims该有User作为claimer。这是上的文档related_name,这是Django允许您设置属性名称的方法,以便自动生成的属性不会冲突。

The User model is trying to create two fields with the same name, one for the GameClaims that have that User as the target, and another for the GameClaims that have that User as the claimer. Here’s the docs on related_name, which is Django’s way of letting you set the names of the attributes so the autogenerated ones don’t conflict.


回答 2

OP没有使用抽象基类…但是,如果您使用的是,您会发现在FK中硬编码related_name(例如…,related_name =“ myname”)会导致许多此类冲突错误-从基类继承的每个类一个。下面提供的链接包含解决方法,这很简单,但绝对不明显。

从Django文档…

如果在ForeignKey或ManyToManyField上使用related_name属性,则必须始终为该字段指定唯一的反向名称。这通常会在抽象基类中引起问题,因为此类的字段包含在每个子类中,并且每次属性(包括related_name)的值都完全相同。

更多信息在这里

The OP isn’t using a abstract base class… but if you are, you will find that hard coding the related_name in the FK (e.g. …, related_name=”myname”) will result in a number of these conflict errors – one for each inherited class from the base class. The link provided below contains the workaround, which is simple, but definitely not obvious.

From the django docs…

If you are using the related_name attribute on a ForeignKey or ManyToManyField, you must always specify a unique reverse name for the field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including related_name) each time.

More info here.


回答 3

有时,您related_name 实际上必须在使用继承的任何时候使用额外的格式设置。

class Value(models.Model):
    value = models.DecimalField(decimal_places=2, max_digits=5)
    animal = models.ForeignKey(
        Animal, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class Height(Value):
    pass

class Weigth(Value):
    pass

class Length(Value):
    pass

这里没有冲突,但是related_name定义一次,Django将小心创建唯一的关系名称。

然后在Value类的子级中,您可以访问:

herdboard_height_related
herdboard_lenght_related
herdboard_weight_related

Sometimes you have to use extra formatting in related_name – actually, any time when inheritance is used.

class Value(models.Model):
    value = models.DecimalField(decimal_places=2, max_digits=5)
    animal = models.ForeignKey(
        Animal, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class Height(Value):
    pass

class Weigth(Value):
    pass

class Length(Value):
    pass

No clash here, but related_name is defined once and Django will take care for creating unique relation names.

then in children of Value class, you’ll have access to:

herdboard_height_related
herdboard_lenght_related
herdboard_weight_related

回答 4

当我将子模块作为应用程序添加到Django项目时,似乎偶尔会遇到这种情况,例如,给定以下结构:

myapp/
myapp/module/
myapp/module/models.py

如果我将以下内容添加到INSTALLED_APPS:

'myapp',
'myapp.module',

Django似乎两次处理了myapp.mymodule models.py文件,并抛出了以上错误。可以通过在INSTALLED_APPS列表中不包括主模块来解决此问题:

'myapp.module',

包含myapp代替myapp.module会导致所有数据库表都使用不正确的名称创建,因此这似乎是正确的方法。

我在寻找此问题的解决方案时遇到了这篇文章,所以我想把它放在这里:)

I seem to come across this occasionally when I add a submodule as an application to a django project, for example given the following structure:

myapp/
myapp/module/
myapp/module/models.py

If I add the following to INSTALLED_APPS:

'myapp',
'myapp.module',

Django seems to process the myapp.mymodule models.py file twice and throws the above error. This can be resolved by not including the main module in the INSTALLED_APPS list:

'myapp.module',

Including the myapp instead of myapp.module causes all the database tables to be created with incorrect names, so this seems to be the correct way to do it.

I came across this post while looking for a solution to this problem so figured I’d put this here :)


回答 5

只是添加约旦的答案(感谢约旦的提示),如果您在应用程序上方导入级别,然后导入应用程序,则也可能发生这种情况,例如

myproject/ apps/ foo_app/ bar_app/

因此,如果要导入应用程序foo_app和bar_app,则可能会遇到此问题。我的应用程序foo_app和bar_app都列在设置中。INSTALLED_APPS

而且您还是想避免导入应用程序,因为那样您会将相同的应用程序安装在2个不同的命名空间中

apps.foo_appfoo_app

Just adding to Jordan’s answer (thanks for the tip Jordan) it can also happen if you import the level above the apps and then import the apps e.g.

myproject/ apps/ foo_app/ bar_app/

So if you are importing apps, foo_app and bar_app then you could get this issue. I had apps, foo_app and bar_app all listed in settings.INSTALLED_APPS

And you want to avoid importing apps anyway, because then you have the same app installed in 2 different namespaces

apps.foo_app and foo_app


如何为具有多对多字段的Django模型创建对象?

问题:如何为具有多对多字段的Django模型创建对象?

我的模特:

class Sample(models.Model):
    users = models.ManyToManyField(User)

我想同时保存user1并保存user2在该模型中:

user1 = User.objects.get(pk=1)
user2 = User.objects.get(pk=2)
sample_object = Sample(users=user1, users=user2)
sample_object.save()

我知道这是错误的,但是我敢肯定,您会明白我的意思。你会怎么做?

My model:

class Sample(models.Model):
    users = models.ManyToManyField(User)

I want to save both user1 and user2 in that model:

user1 = User.objects.get(pk=1)
user2 = User.objects.get(pk=2)
sample_object = Sample(users=user1, users=user2)
sample_object.save()

I know that’s wrong, but I’m sure you get what I want to do. How would you do it ?


回答 0

您不能从未保存的对象创建m2m关系。如果有pk,请尝试以下操作:

sample_object = Sample()
sample_object.save()
sample_object.users.add(1,2)

更新:阅读了saverio的答案后,我决定对这个问题进行更深入的研究。这是我的发现。

这是我最初的建议。它可以工作,但不是最佳选择。(注意:我使用Bar的是s和a Foo而不是Users和a Sample,但是您知道了。)

bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars.add(bar1)
foo.bars.add(bar2)

它总共产生7个查询:

SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

我相信我们可以做得更好。您可以将多个对象传递给该add()方法:

bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars.add(bar1, bar2)

如我们所见,传递多个对象可以节省一个SELECT

SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

我不知道您还可以分配对象列表:

bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars = [bar1, bar2]

不幸的是,这又增加了一个SELECT

SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

让我们尝试分配一个pks 列表,如saverio建议的那样:

foo = Foo()
foo.save()
foo.bars = [1,2]

由于不获取两个Bars,因此保存了两个SELECT语句,总共有5个:

INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

最终获胜者是:

foo = Foo()
foo.save()
foo.bars.add(1,2)

路过pks到add()让我们一共有4个查询:

INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

You cannot create m2m relations from unsaved objects. If you have the pks, try this:

sample_object = Sample()
sample_object.save()
sample_object.users.add(1,2)

Update: After reading the saverio’s answer, I decided to investigate the issue a bit more in depth. Here are my findings.

This was my original suggestion. It works, but isn’t optimal. (Note: I’m using Bars and a Foo instead of Users and a Sample, but you get the idea).

bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars.add(bar1)
foo.bars.add(bar2)

It generates a whopping total of 7 queries:

SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

I’m sure we can do better. You can pass multiple objects to the add() method:

bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars.add(bar1, bar2)

As we can see, passing multiple objects saves one SELECT:

SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

I wasn’t aware that you can also assign a list of objects:

bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars = [bar1, bar2]

Unfortunately, that creates one additional SELECT:

SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

Let’s try to assign a list of pks, as saverio suggested:

foo = Foo()
foo.save()
foo.bars = [1,2]

As we don’t fetch the two Bars, we save two SELECT statements, resulting in a total of 5:

INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

And the winner is:

foo = Foo()
foo.save()
foo.bars.add(1,2)

Passing pks to add() gives us a total of 4 queries:

INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)

回答 1

对于将来的访问者,您可以使用django 1.4中新的bulk_create2个查询中创建一个对象及其所有m2m对象。请注意,仅当您不需要对带有save()方法或信号的数据进行任何预处理或后处理时,此方法才可用。您插入的正是数据库中的内容

您无需在字段上指定“直通”模型即可执行此操作。为了完整起见,下面的示例创建了一个空白的Users模型来模仿原始海报的要求。

from django.db import models

class Users(models.Model):
    pass

class Sample(models.Model):
    users = models.ManyToManyField(Users)

现在,在Shell或其他代码中,创建2个用户,创建一个示例对象,然后将用户批量添加到该示例对象中。

Users().save()
Users().save()

# Access the through model directly
ThroughModel = Sample.users.through

users = Users.objects.filter(pk__in=[1,2])

sample_object = Sample()
sample_object.save()

ThroughModel.objects.bulk_create([
    ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
    ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])

For future visitors, you can create an object and all of its m2m objects in 2 queries using the new bulk_create in django 1.4. Note that this is only usable if you don’t require any pre or post-processing on the data with save() methods or signals. What you insert is exactly what will be in the DB

You can do this without specifying a “through” model on the field. For completeness, the example below creates a blank Users model to mimic what the original poster was asking.

from django.db import models

class Users(models.Model):
    pass

class Sample(models.Model):
    users = models.ManyToManyField(Users)

Now, in a shell or other code, create 2 users, create a sample object, and bulk add the users to that sample object.

Users().save()
Users().save()

# Access the through model directly
ThroughModel = Sample.users.through

users = Users.objects.filter(pk__in=[1,2])

sample_object = Sample()
sample_object.save()

ThroughModel.objects.bulk_create([
    ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
    ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])

回答 2

Django 1.9
一个简单的例子:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)

Django 1.9
A quick example:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)

回答 3

RelatedObjectManagers与Model中的字段是不同的“属性”。实现您想要的最简单的方法是

sample_object = Sample.objects.create()
sample_object.users = [1, 2]

这与分配用户列表相同,而没有其他查询和模型构建。

如果查询的数量让您感到困扰(而不是简单),那么最佳解决方案将需要三个查询:

sample_object = Sample.objects.create()
sample_id = sample_object.id
sample_object.users.through.objects.create(user_id=1, sample_id=sample_id)
sample_object.users.through.objects.create(user_id=2, sample_id=sample_id)

这将起作用,因为我们已经知道“用户”列表为空,因此我们可以轻松创建。

RelatedObjectManagers are different “attributes” than fields in a Model. The simplest way to achieve what you are looking for is

sample_object = Sample.objects.create()
sample_object.users = [1, 2]

That’s the same as assigning a User list, without the additional queries and the model building.

If the number of queries is what bothers you (instead of simplicity), then the optimal solution requires three queries:

sample_object = Sample.objects.create()
sample_id = sample_object.id
sample_object.users.through.objects.create(user_id=1, sample_id=sample_id)
sample_object.users.through.objects.create(user_id=2, sample_id=sample_id)

This will work because we already know that the ‘users’ list is empty, so we can create mindlessly.


回答 4

您可以通过以下方式替换相关对象集(Django 1.9中的新增功能):

new_list = [user1, user2, user3]
sample_object.related_set.set(new_list)

You could replace the set of related objects in this way (new in Django 1.9):

new_list = [user1, user2, user3]
sample_object.related_set.set(new_list)

回答 5

如果有人想做David Marbles,请回答自我引用ManyToMany字段。直通模型的ID称为:“ to_’model_name_id”和“ from_’model_name’_id”。

如果这样不起作用,您可以检查Django连接。

If someone is looking to do David Marbles answer on a self referring ManyToMany field. The ids of the through model are called: “to_’model_name_id” and “from_’model_name’_id”.

If that doesn’t work you can check the django connection.


在运行时确定带有upload_to的Django FileField

问题:在运行时确定带有upload_to的Django FileField

我正在尝试设置我的上传文件,以便如果用户joe上传文件,则文件将转到MEDIA_ROOT / joe,而不是让每个人的文件都转到MEDIA_ROOT。问题是我不知道如何在模型中定义它。这是当前的外观:

class Content(models.Model):
    name = models.CharField(max_length=200)
    user = models.ForeignKey(User)
    file = models.FileField(upload_to='.')

所以我想要的不是“。” 作为upload_to,将其作为用户名。

我知道从Django 1.0开始,您可以定义自己的函数来处理upload_to,但是该函数也不知道谁将成为谁,所以我有点迷失了。

谢谢您的帮助!

I’m trying to set up my uploads so that if user joe uploads a file it goes to MEDIA_ROOT/joe as opposed to having everyone’s files go to MEDIA_ROOT. The problem is I don’t know how to define this in the model. Here is how it currently looks:

class Content(models.Model):
    name = models.CharField(max_length=200)
    user = models.ForeignKey(User)
    file = models.FileField(upload_to='.')

So what I want is instead of ‘.’ as the upload_to, have it be the user’s name.

I understand that as of Django 1.0 you can define your own function to handle the upload_to but that function has no idea of who the user will be either so I’m a bit lost.

Thanks for the help!


回答 0

您可能已经阅读了文档,所以这里有一个简单的示例可以使之有意义:

def content_file_name(instance, filename):
    return '/'.join(['content', instance.user.username, filename])

class Content(models.Model):
    name = models.CharField(max_length=200)
    user = models.ForeignKey(User)
    file = models.FileField(upload_to=content_file_name)

如您所见,您甚至不需要使用给定的文件名-如果愿意,您也可以覆盖您可调用的upload_to中的文件名。

You’ve probably read the documentation, so here’s an easy example to make it make sense:

def content_file_name(instance, filename):
    return '/'.join(['content', instance.user.username, filename])

class Content(models.Model):
    name = models.CharField(max_length=200)
    user = models.ForeignKey(User)
    file = models.FileField(upload_to=content_file_name)

As you can see, you don’t even need to use the filename given – you could override that in your upload_to callable too if you liked.


回答 1

这确实有帮助。为了简洁起见,决定在我的情况下使用lambda:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)

This really helped. For a bit more brevity’s sake, decided to use lambda in my case:

file = models.FileField(
    upload_to=lambda instance, filename: '/'.join(['mymodel', str(instance.pk), filename]),
)

回答 2

关于使用“实例”对象的pk值的注释。根据文档:

在大多数情况下,此对象尚未保存到数据库,因此,如果使用默认的AutoField,则它的主键字段可能尚未具有值。

因此,使用pk的有效性取决于特定模型的定义。

A note on using the ‘instance’ object’s pk value. According to the documentation:

In most cases, this object will not have been saved to the database yet, so if it uses the default AutoField, it might not yet have a value for its primary key field.

Therefore the validity of using pk depends on how your particular model is defined.


回答 3

如果您在迁移时遇到问题,则可能应该使用@deconstructible装饰器。

import datetime
import os
import unicodedata

from django.core.files.storage import default_storage
from django.utils.deconstruct import deconstructible
from django.utils.encoding import force_text, force_str


@deconstructible
class UploadToPath(object):
    def __init__(self, upload_to):
        self.upload_to = upload_to

    def __call__(self, instance, filename):
        return self.generate_filename(filename)

    def get_directory_name(self):
        return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(self.upload_to))))

    def get_filename(self, filename):
        filename = default_storage.get_valid_name(os.path.basename(filename))
        filename = force_text(filename)
        filename = unicodedata.normalize('NFKD', filename).encode('ascii', 'ignore').decode('ascii')
        return os.path.normpath(filename)

    def generate_filename(self, filename):
        return os.path.join(self.get_directory_name(), self.get_filename(filename))

用法:

class MyModel(models.Model):
    file = models.FileField(upload_to=UploadToPath('files/%Y/%m/%d'), max_length=255)

If you have problems with migrations you probably should be using @deconstructible decorator.

import datetime
import os
import unicodedata

from django.core.files.storage import default_storage
from django.utils.deconstruct import deconstructible
from django.utils.encoding import force_text, force_str


@deconstructible
class UploadToPath(object):
    def __init__(self, upload_to):
        self.upload_to = upload_to

    def __call__(self, instance, filename):
        return self.generate_filename(filename)

    def get_directory_name(self):
        return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(self.upload_to))))

    def get_filename(self, filename):
        filename = default_storage.get_valid_name(os.path.basename(filename))
        filename = force_text(filename)
        filename = unicodedata.normalize('NFKD', filename).encode('ascii', 'ignore').decode('ascii')
        return os.path.normpath(filename)

    def generate_filename(self, filename):
        return os.path.join(self.get_directory_name(), self.get_filename(filename))

Usage:

class MyModel(models.Model):
    file = models.FileField(upload_to=UploadToPath('files/%Y/%m/%d'), max_length=255)

如何在Django中过滤用于计数注释的对象?

问题:如何在Django中过滤用于计数注释的对象?

考虑简单的Django模型EventParticipant

class Event(models.Model):
    title = models.CharField(max_length=100)

class Participant(models.Model):
    event = models.ForeignKey(Event, db_index=True)
    is_paid = models.BooleanField(default=False, db_index=True)

使用参与者总数来注释事件查询很容易:

events = Event.objects.all().annotate(participants=models.Count('participant'))

如何用筛选的参与者计数进行注释is_paid=True

我需要查询所有事件,而与参与者人数无关,例如,我不需要按带注释的结果进行过滤。如果有0参与者,那没关系,我只需要带有0注释的值即可。

文档中的示例在这里不起作用,因为它从查询中排除了对象,而不是使用注释了对象0

更新。Django 1.8具有新的条件表达式功能,因此我们现在可以这样做:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0,
        output_field=models.IntegerField()
    )))

更新 2。Django 2.0具有新的条件聚合功能,请参阅下面的可接受答案

Consider simple Django models Event and Participant:

class Event(models.Model):
    title = models.CharField(max_length=100)

class Participant(models.Model):
    event = models.ForeignKey(Event, db_index=True)
    is_paid = models.BooleanField(default=False, db_index=True)

It’s easy to annotate events query with total number of participants:

events = Event.objects.all().annotate(participants=models.Count('participant'))

How to annotate with count of participants filtered by is_paid=True?

I need to query all events regardless of number of participants, e.g. I don’t need to filter by annotated result. If there are 0 participants, that’s ok, I just need 0 in annotated value.

The example from documentation doesn’t work here, because it excludes objects from query instead of annotating them with 0.

Update. Django 1.8 has new conditional expressions feature, so now we can do like this:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0,
        output_field=models.IntegerField()
    )))

Update 2. Django 2.0 has new Conditional aggregation feature, see the accepted answer below.


回答 0

Django 2.0中的条件聚合可让您进一步减少过去的流量。这也将使用Postgres的filter逻辑,该逻辑比求和的情况要快一些(我见过像20-30%这样的数字被打乱)。

无论如何,就您的情况而言,我们正在研究以下简单内容:

from django.db.models import Q, Count
events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)

在文档中有一个单独的部分,关于对注释进行过滤。它和条件聚合是一样的东西,但是更像上面的例子。无论哪种方式,这都比我以前做的粗糙子查询要健康得多。

Conditional aggregation in Django 2.0 allows you to further reduce the amount of faff this has been in the past. This will also use Postgres’ filter logic, which is somewhat faster than a sum-case (I’ve seen numbers like 20-30% bandied around).

Anyway, in your case, we’re looking at something as simple as:

from django.db.models import Q, Count
events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)

There’s a separate section in the docs about filtering on annotations. It’s the same stuff as conditional aggregation but more like my example above. Either which way, this is a lot healthier than the gnarly subqueries I was doing before.


回答 1

刚刚发现Django 1.8具有新的条件表达式功能,因此现在我们可以这样做:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0, output_field=models.IntegerField()
    )))

Just discovered that Django 1.8 has new conditional expressions feature, so now we can do like this:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0, output_field=models.IntegerField()
    )))

回答 2

更新

Django 1.11现在通过subquery-expressions支持了我提到的子查询方法。

Event.objects.annotate(
    num_paid_participants=Subquery(
        Participant.objects.filter(
            is_paid=True,
            event=OuterRef('pk')
        ).values('event')
        .annotate(cnt=Count('pk'))
        .values('cnt'),
        output_field=models.IntegerField()
    )
)

我更喜欢这种方法而不是聚合(sum + case),因为它应该更快,更容易被优化(使用适当的索引)

对于较旧的版本,可以使用 .extra

Event.objects.extra(select={'num_paid_participants': "\
    SELECT COUNT(*) \
    FROM `myapp_participant` \
    WHERE `myapp_participant`.`is_paid` = 1 AND \
            `myapp_participant`.`event_id` = `myapp_event`.`id`"
})

UPDATE

The sub-query approach which I mention is now supported in Django 1.11 via subquery-expressions.

Event.objects.annotate(
    num_paid_participants=Subquery(
        Participant.objects.filter(
            is_paid=True,
            event=OuterRef('pk')
        ).values('event')
        .annotate(cnt=Count('pk'))
        .values('cnt'),
        output_field=models.IntegerField()
    )
)

I prefer this over aggregation (sum+case), because it should be faster and easier to be optimized (with proper indexing).

For older version, the same can be achieved using .extra

Event.objects.extra(select={'num_paid_participants': "\
    SELECT COUNT(*) \
    FROM `myapp_participant` \
    WHERE `myapp_participant`.`is_paid` = 1 AND \
            `myapp_participant`.`event_id` = `myapp_event`.`id`"
})

回答 3

我建议改用.values您的Participantqueryset 方法。

简而言之,您想要做的是:

Participant.objects\
    .filter(is_paid=True)\
    .values('event')\
    .distinct()\
    .annotate(models.Count('id'))

完整的示例如下:

  1. 创建2 Event秒:

    event1 = Event.objects.create(title='event1')
    event2 = Event.objects.create(title='event2')
    
  2. Participants 添加到他们:

    part1l = [Participant.objects.create(event=event1, is_paid=((_%2) == 0))\
              for _ in range(10)]
    part2l = [Participant.objects.create(event=event2, is_paid=((_%2) == 0))\
              for _ in range(50)]
    
  3. 将所有Participants按其event字段分组:

    Participant.objects.values('event')
    > <QuerySet [{'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, '...(remaining elements truncated)...']>
    

    这里需要与众不同:

    Participant.objects.values('event').distinct()
    > <QuerySet [{'event': 1}, {'event': 2}]>
    

    什么.values.distinct正在做的事情是,他们正在创造的两个水桶Participant用元的分组小号event。请注意,这些存储桶包含Participant

  4. 然后,您可以注释这些存储桶,因为它们包含原始集Participant。在这里,我们要计算的数量Participant,只需通过计算id这些存储区中的元素的s即可(因为它们是Participant):

    Participant.objects\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
    
  5. 最后,您只Participant需要一个is_paidbeing True,您可以只在前一个表达式的前面添加一个过滤器,这将产生上面显示的表达式:

    Participant.objects\
        .filter(is_paid=True)\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 5}, {'event': 2, 'id__count': 25}]>
    

唯一的缺点是Event您只能id从上面的方法中获取,因此您必须检索之后的内容。

I would suggest to use the .values method of your Participant queryset instead.

For short, what you want to do is given by:

Participant.objects\
    .filter(is_paid=True)\
    .values('event')\
    .distinct()\
    .annotate(models.Count('id'))

A complete example is as follow:

  1. Create 2 Events:

    event1 = Event.objects.create(title='event1')
    event2 = Event.objects.create(title='event2')
    
  2. Add Participants to them:

    part1l = [Participant.objects.create(event=event1, is_paid=((_%2) == 0))\
              for _ in range(10)]
    part2l = [Participant.objects.create(event=event2, is_paid=((_%2) == 0))\
              for _ in range(50)]
    
  3. Group all Participants by their event field:

    Participant.objects.values('event')
    > <QuerySet [{'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, '...(remaining elements truncated)...']>
    

    Here distinct is needed:

    Participant.objects.values('event').distinct()
    > <QuerySet [{'event': 1}, {'event': 2}]>
    

    What .values and .distinct are doing here is that they are creating two buckets of Participants grouped by their element event. Note that those buckets contain Participant.

  4. You can then annotate those buckets as they contain the set of original Participant. Here we want to count the number of Participant, this is simply done by counting the ids of the elements in those buckets (since those are Participant):

    Participant.objects\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
    
  5. Finally you want only Participant with a is_paid being True, you may just add a filter in front of the previous expression, and this yield the expression shown above:

    Participant.objects\
        .filter(is_paid=True)\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 5}, {'event': 2, 'id__count': 25}]>
    

The only drawback is that you have to retrieve the Event afterwards as you only have the id from the method above.


回答 4

我正在寻找什么结果:

  • 将任务添加到报告中的人员(受让人)。-唯一身份人员总数
  • 将任务添加到报告中但仅针对计费性大于0的任务的人员。

通常,我将不得不使用两个不同的查询:

Task.objects.filter(billable_efforts__gt=0)
Task.objects.all()

但我想在一个查询中两者。因此:

Task.objects.values('report__title').annotate(withMoreThanZero=Count('assignee', distinct=True, filter=Q(billable_efforts__gt=0))).annotate(totalUniqueAssignee=Count('assignee', distinct=True))

结果:

<QuerySet [{'report__title': 'TestReport', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}, {'report__title': 'Utilization_Report_April_2019', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}]>

What result I am looking for:

  • People (assignee) who have tasks added to a report. – Total Unique count of People
  • People who have tasks added to a report but, for task whose billability is more than 0 only.

In general, I would have to use two different queries:

Task.objects.filter(billable_efforts__gt=0)
Task.objects.all()

But I want both in one query. Hence:

Task.objects.values('report__title').annotate(withMoreThanZero=Count('assignee', distinct=True, filter=Q(billable_efforts__gt=0))).annotate(totalUniqueAssignee=Count('assignee', distinct=True))

Result:

<QuerySet [{'report__title': 'TestReport', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}, {'report__title': 'Utilization_Report_April_2019', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}]>

创建Django模型或更新(如果存在)

问题:创建Django模型或更新(如果存在)

我想创建一个模型对象,例如Person,如果不存在person的id,或者我将得到该person对象。

创建新人员的代码如下:

class Person(models.Model):
    identifier = models.CharField(max_length = 10)
    name = models.CharField(max_length = 20)
    objects = PersonManager()

class PersonManager(models.Manager):
    def create_person(self, identifier):
        person = self.create(identifier = identifier)
        return person

但是我不知道在哪里检查并获取现有的人对象。

I want to create a model object, like Person, if person’s id doesn’t not exist, or I will get that person object.

The code to create a new person as following:

class Person(models.Model):
    identifier = models.CharField(max_length = 10)
    name = models.CharField(max_length = 20)
    objects = PersonManager()

class PersonManager(models.Manager):
    def create_person(self, identifier):
        person = self.create(identifier = identifier)
        return person

But I don’t know where to check and get the existing person object.


回答 0

如果您正在寻找“如果存在则更新,否则会创建”用例,请参阅@Zags绝佳答案


Django中已经有一个get_or_createhttps://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

对你来说可能是:

id = 'some identifier'
person, created = Person.objects.get_or_create(identifier=id)

if created:
   # means you have created a new person
else:
   # person just refers to the existing one

If you’re looking for “update if exists else create” use case, please refer to @Zags excellent answer


Django already has a get_or_create, https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

For you it could be :

id = 'some identifier'
person, created = Person.objects.get_or_create(identifier=id)

if created:
   # means you have created a new person
else:
   # person just refers to the existing one

回答 1

目前尚不清楚您的问题是否要求使用get_or_create方法(至少可从Django 1.3获得)或update_or_create方法(Django 1.7中的新增功能)。这取决于您要如何更新用户对象。

用法示例如下:

# In both cases, the call will get a person object with matching
# identifier or create one if none exists; if a person is created,
# it will be created with name equal to the value in `name`.

# In this case, if the Person already exists, its existing name is preserved
person, created = Person.objects.get_or_create(
        identifier=identifier, defaults={"name": name}
)

# In this case, if the Person already exists, its name is updated
person, created = Person.objects.update_or_create(
        identifier=identifier, defaults={"name": name}
)

It’s unclear whether your question is asking for the get_or_create method (available from at least Django 1.3) or the update_or_create method (new in Django 1.7). It depends on how you want to update the user object.

Sample use is as follows:

# In both cases, the call will get a person object with matching
# identifier or create one if none exists; if a person is created,
# it will be created with name equal to the value in `name`.

# In this case, if the Person already exists, its existing name is preserved
person, created = Person.objects.get_or_create(
        identifier=identifier, defaults={"name": name}
)

# In this case, if the Person already exists, its name is updated
person, created = Person.objects.update_or_create(
        identifier=identifier, defaults={"name": name}
)

回答 2

Django支持此功能,请检查get_or_create

person, created = Person.objects.get_or_create(name='abc')
if created:
    # A new person object created
else:
    # person object already exists

Django has support for this, check get_or_create

person, created = Person.objects.get_or_create(name='abc')
if created:
    # A new person object created
else:
    # person object already exists

回答 3

对于仅少量的对象,update_or_create可以很好地工作,但是如果您要处理的是大型集合,则扩展性就不好。update_or_create始终首先运行SELECT,然后再运行UPDATE。

for the_bar in bars:
    updated_rows = SomeModel.objects.filter(bar=the_bar).update(foo=100)
        if not updated_rows:
            # if not exists, create new
            SomeModel.objects.create(bar=the_bar, foo=100)

这充其量仅会运行第一个更新查询,并且仅当它匹配零行时才运行另一个INSERT查询。如果您希望大多数行实际存在,那将大大提高您的性能。

但这全都取决于您的用例。如果您期望大部分插入内容,则可以选择bulk_create()命令。

For only a small amount of objects the update_or_create works well, but if you’re doing over a large collection it won’t scale well. update_or_create always first runs a SELECT and thereafter an UPDATE.

for the_bar in bars:
    updated_rows = SomeModel.objects.filter(bar=the_bar).update(foo=100)
        if not updated_rows:
            # if not exists, create new
            SomeModel.objects.create(bar=the_bar, foo=100)

This will at best only run the first update-query, and only if it matched zero rows run another INSERT-query. Which will greatly increase your performance if you expect most of the rows to actually be existing.

It all comes down to your use case though. If you are expecting mostly inserts then perhaps the bulk_create() command could be an option.


回答 4

我想我要添加一个答案,因为您的问题标题看起来像是在询问如何创建或更新,而不是按照问题正文中的描述进行获取或创建。

如果您确实想创建或更新对象,则默认情况下,.save()方法已经具有以下行为,来自docs

Django提取了使用INSERT或UPDATE SQL语句的需求。具体来说,当您调用save()时,Django遵循以下算法:

如果对象的主键属性设置为计算结果为True的值(即,无或空字符串以外的值),则Django将执行UPDATE。如果未设置对象的主键属性,或者UPDATE未更新任何内容,则Django执行INSERT。

值得注意的是,当他们说“如果UPDATE不更新任何内容”时,它们实际上是指您为对象提供的ID在数据库中不存在的情况。

Thought I’d add an answer since your question title looks like it is asking how to create or update, rather than get or create as described in the question body.

If you did want to create or update an object, the .save() method already has this behaviour by default, from the docs:

Django abstracts the need to use INSERT or UPDATE SQL statements. Specifically, when you call save(), Django follows this algorithm:

If the object’s primary key attribute is set to a value that evaluates to True (i.e., a value other than None or the empty string), Django executes an UPDATE. If the object’s primary key attribute is not set or if the UPDATE didn’t update anything, Django executes an INSERT.

It’s worth noting that when they say ‘if the UPDATE didn’t update anything’ they are essentially referring to the case where the id you gave the object doesn’t already exist in the database.


回答 5

如果创建时的输入之一是主键,那么这就足够了:

Person.objects.get_or_create(id=1)

如果存在,它将自动更新,因为不允许两个具有相同主键的数据。

If one of the input when you create is a primary key, this will be enough:

Person.objects.get_or_create(id=1)

It will automatically update if exist since two data with the same primary key is not allowed.


字典可以在创建时传递给Django模型吗?

问题:字典可以在创建时传递给Django模型吗?

是否有可能做类似这样的一个东西listdictionary还是其他什么东西?

data_dict = {
    'title' : 'awesome title',
    'body' : 'great body of text',
}

Model.objects.create(data_dict)

如果我可以扩展它,那就更好了:

Model.objects.create(data_dict, extra='hello', extra2='world')

Is it possible to do something similar to this with a list, dictionary or something else?

data_dict = {
    'title' : 'awesome title',
    'body' : 'great body of text',
}

Model.objects.create(data_dict)

Even better if I can extend it:

Model.objects.create(data_dict, extra='hello', extra2='world')

回答 0

如果titlebody是模型中的字段,则可以使用**运算符将关键字参数传递到字典中

假设您的模型称为MyModel

# create instance of model
m = MyModel(**data_dict)
# don't forget to save to database!
m.save()

至于第二个问题,字典必须是最后一个参数。同样,extra并且extra2应该是模型中的字段。

m2 =MyModel(extra='hello', extra2='world', **data_dict)
m2.save()

If title and body are fields in your model, then you can deliver the keyword arguments in your dictionary using the ** operator.

Assuming your model is called MyModel:

# create instance of model
m = MyModel(**data_dict)
# don't forget to save to database!
m.save()

As for your second question, the dictionary has to be the final argument. Again, extra and extra2 should be fields in the model.

m2 =MyModel(extra='hello', extra2='world', **data_dict)
m2.save()

回答 1

并不是直接回答问题,但是我发现这段代码帮助我创建了可以很好地保存为正确答案的字典。如果此数据将导出到json,则需要进行类型转换。

我希望这有帮助:

  #mod is a django database model instance
def toDict( mod ):
  import datetime
  from decimal import Decimal
  import re

    #Go through the object, load in the objects we want
  obj = {}
  for key in mod.__dict__:
    if re.search('^_', key):
      continue

      #Copy my data
    if isinstance( mod.__dict__[key], datetime.datetime ):
      obj[key] = int(calendar.timegm( ts.utctimetuple(mod.__dict__[key])))
    elif isinstance( mod.__dict__[key], Decimal ):
      obj[key] = float( mod.__dict__[key] )
    else:
      obj[key] = mod.__dict__[key]

  return obj 

def toCsv( mod, fields, delim=',' ):
  import datetime
  from decimal import Decimal

    #Dump the items
  raw = []
  for key in fields:
    if key not in mod.__dict__:
      continue

      #Copy my data
    if isinstance( mod.__dict__[key], datetime.datetime ):
      raw.append( str(calendar.timegm( ts.utctimetuple(mod.__dict__[key]))) )
    elif isinstance( mod.__dict__[key], Decimal ):
      raw.append( str(float( mod.__dict__[key] )))
    else:
      raw.append( str(mod.__dict__[key]) )

  return delim.join( raw )

Not directly an answer to the question, but I find this code helped me create the dicts that save nicely into the correct answer. The type conversions made are required if this data will be exported to json.

I hope this helps:

  #mod is a django database model instance
def toDict( mod ):
  import datetime
  from decimal import Decimal
  import re

    #Go through the object, load in the objects we want
  obj = {}
  for key in mod.__dict__:
    if re.search('^_', key):
      continue

      #Copy my data
    if isinstance( mod.__dict__[key], datetime.datetime ):
      obj[key] = int(calendar.timegm( ts.utctimetuple(mod.__dict__[key])))
    elif isinstance( mod.__dict__[key], Decimal ):
      obj[key] = float( mod.__dict__[key] )
    else:
      obj[key] = mod.__dict__[key]

  return obj 

def toCsv( mod, fields, delim=',' ):
  import datetime
  from decimal import Decimal

    #Dump the items
  raw = []
  for key in fields:
    if key not in mod.__dict__:
      continue

      #Copy my data
    if isinstance( mod.__dict__[key], datetime.datetime ):
      raw.append( str(calendar.timegm( ts.utctimetuple(mod.__dict__[key]))) )
    elif isinstance( mod.__dict__[key], Decimal ):
      raw.append( str(float( mod.__dict__[key] )))
    else:
      raw.append( str(mod.__dict__[key]) )

  return delim.join( raw )

排序查询集的好方法?-Django

问题:排序查询集的好方法?-Django

我想做的是这样的:

  • 获得得分最高的30位作者(Author.objects.order_by('-score')[:30]

  • 命令作者 last_name


有什么建议?

what I’m trying to do is this:

  • get the 30 Authors with highest score ( Author.objects.order_by('-score')[:30] )

  • order the authors by last_name


Any suggestions?


回答 0

关于什么

import operator

auths = Author.objects.order_by('-score')[:30]
ordered = sorted(auths, key=operator.attrgetter('last_name'))

在Django 1.4及更高版本中,您可以通过提供多个字段进行订购。
参考:https : //docs.djangoproject.com/en/dev/ref/models/querysets/#order-by

order_by(*字段)

默认情况下,a返回的结果由模型的Meta中QuerySetordering选项给出的排序元组排序。您可以使用order_by方法基于每个QuerySet重写此方法。

例:

ordered_authors = Author.objects.order_by('-score', 'last_name')[:30]

上面的结果将按score降序排列,然后按last_name升序排列。前面的负号"-score"表示降序。隐含升序。

What about

import operator

auths = Author.objects.order_by('-score')[:30]
ordered = sorted(auths, key=operator.attrgetter('last_name'))

In Django 1.4 and newer you can order by providing multiple fields.
Reference: https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by

order_by(*fields)

By default, results returned by a QuerySet are ordered by the ordering tuple given by the ordering option in the model’s Meta. You can override this on a per-QuerySet basis by using the order_by method.

Example:

ordered_authors = Author.objects.order_by('-score', 'last_name')[:30]

The result above will be ordered by score descending, then by last_name ascending. The negative sign in front of "-score" indicates descending order. Ascending order is implied.


回答 1

我只是想说明一下内置解决方案(仅适用于SQL)并不总是最好的。最初,我认为因为Django的QuerySet.objects.order_by方法接受多个参数,所以您可以轻松地将它们链接起来:

ordered_authors = Author.objects.order_by('-score', 'last_name')[:30]

但是,它没有按您期望的那样工作。举例来说,首先是按得分排序的总统列表(选择前5名以便于阅读):

>>> auths = Author.objects.order_by('-score')[:5]
>>> for x in auths: print x
... 
James Monroe (487)
Ulysses Simpson (474)
Harry Truman (471)
Benjamin Harrison (467)
Gerald Rudolph (464)

使用Alex Martelli的解决方案,该解决方案可准确提供排名前5位的人员last_name

>>> for x in sorted(auths, key=operator.attrgetter('last_name')): print x
... 
Benjamin Harrison (467)
James Monroe (487)
Gerald Rudolph (464)
Ulysses Simpson (474)
Harry Truman (471)

现在组合order_by调用:

>>> myauths = Author.objects.order_by('-score', 'last_name')[:5]
>>> for x in myauths: print x
... 
James Monroe (487)
Ulysses Simpson (474)
Harry Truman (471)
Benjamin Harrison (467)
Gerald Rudolph (464)

如您所见,它与第一个结果相同,这意味着它无法按预期工作。

I just wanted to illustrate that the built-in solutions (SQL-only) are not always the best ones. At first I thought that because Django’s QuerySet.objects.order_by method accepts multiple arguments, you could easily chain them:

ordered_authors = Author.objects.order_by('-score', 'last_name')[:30]

But, it does not work as you would expect. Case in point, first is a list of presidents sorted by score (selecting top 5 for easier reading):

>>> auths = Author.objects.order_by('-score')[:5]
>>> for x in auths: print x
... 
James Monroe (487)
Ulysses Simpson (474)
Harry Truman (471)
Benjamin Harrison (467)
Gerald Rudolph (464)

Using Alex Martelli’s solution which accurately provides the top 5 people sorted by last_name:

>>> for x in sorted(auths, key=operator.attrgetter('last_name')): print x
... 
Benjamin Harrison (467)
James Monroe (487)
Gerald Rudolph (464)
Ulysses Simpson (474)
Harry Truman (471)

And now the combined order_by call:

>>> myauths = Author.objects.order_by('-score', 'last_name')[:5]
>>> for x in myauths: print x
... 
James Monroe (487)
Ulysses Simpson (474)
Harry Truman (471)
Benjamin Harrison (467)
Gerald Rudolph (464)

As you can see it is the same result as the first one, meaning it doesn’t work as you would expect.


回答 2

这是一种允许得分临界值的方法。

author_count = Author.objects.count()
cut_off_score = Author.objects.order_by('-score').values_list('score')[min(30, author_count)]
top_authors = Author.objects.filter(score__gte=cut_off_score).order_by('last_name')

这样一来,您在top_authors中可能会获得30多位作者,如果您的作者少于30位,则可以在此处找到min(30,author_count)

Here’s a way that allows for ties for the cut-off score.

author_count = Author.objects.count()
cut_off_score = Author.objects.order_by('-score').values_list('score')[min(30, author_count)]
top_authors = Author.objects.filter(score__gte=cut_off_score).order_by('last_name')

You may get more than 30 authors in top_authors this way and the min(30,author_count) is there incase you have fewer than 30 authors.


从一个应用程序到Django中另一个应用程序的外键

问题:从一个应用程序到Django中另一个应用程序的外键

我想知道是否有可能在Django的models.py文件中定义外键,该外键引用了另一个应用程序中的表?

换句话说,我有两个应用程序,分别称为cf和profile,在cf / models.py中,我拥有(以及其他功能):

class Movie(models.Model):
    title = models.CharField(max_length=255)

并希望在profile / models.py中拥有:

class MovieProperty(models.Model):
    movie = models.ForeignKey(Movie)

但是我无法正常工作。我试过了:

    movie = models.ForeignKey(cf.Movie)

并且我曾尝试在models.py的开头导入cf.Movie,但我总是会遇到错误,例如:

NameError: name 'User' is not defined

我是通过尝试以这种方式将两个应用程序绑定在一起来违反规则,还是只是语法错误?

I’m wondering if it’s possible to define a foreign key in a models.py file in Django that is a reference to a table in another app?

In other words, I have two apps, called cf and profiles, and in cf/models.py I have (amongst other things):

class Movie(models.Model):
    title = models.CharField(max_length=255)

and in profiles/models.py I want to have:

class MovieProperty(models.Model):
    movie = models.ForeignKey(Movie)

But I can’t get it to work. I’ve tried:

    movie = models.ForeignKey(cf.Movie)

and I’ve tried importing cf.Movie at the beginning of models.py, but I always get errors, such as:

NameError: name 'User' is not defined

Am I breaking the rules by trying to tie two apps together in this way, or have I just got the syntax wrong?


回答 0

根据文档,您的第二次尝试应该可行:

要引用在另一个应用程序中定义的模型,必须改为显式指定应用程序标签。例如,如果上述制造商模型是在另一个称为生产的应用程序中定义的,则需要使用:

class Car(models.Model):
    manufacturer = models.ForeignKey('production.Manufacturer')

您是否尝试过将其用引号引起来?

According to the docs, your second attempt should work:

To refer to models defined in another application, you must instead explicitly specify the application label. For example, if the Manufacturer model above is defined in another application called production, you’d need to use:

class Car(models.Model):
    manufacturer = models.ForeignKey('production.Manufacturer')

Have you tried putting it into quotes?


回答 1

也可以通过类本身:

from django.db import models
from production import models as production_models

class Car(models.Model):
    manufacturer = models.ForeignKey(production_models.Manufacturer)

It is also possible to pass the class itself:

from django.db import models
from production import models as production_models

class Car(models.Model):
    manufacturer = models.ForeignKey(production_models.Manufacturer)

回答 2

好的-我知道了。您可以做到,只需使用正确的import语法即可。正确的语法是:

from prototype.cf.models import Movie

我的错误是没有指定该.models行的一部分。天哪!

OK – I’ve figured it out. You can do it, you just have to use the right import syntax. The correct syntax is:

from prototype.cf.models import Movie

My mistake was not specifying the .models part of that line. D’oh!


Django-如何创建文件并将其保存到模型的FileField中?

问题:Django-如何创建文件并将其保存到模型的FileField中?

这是我的模特。我想要做的是生成一个新文件,并在保存模型实例时覆盖现有文件:

class Kitten(models.Model):
    claw_size = ...
    license_file = models.FileField(blank=True, upload_to='license')

    def save(self, *args, **kwargs):
        #Generate a new license file overwriting any previous version
        #and update file path
        self.license_file = ???
        super(Request,self).save(*args, **kwargs)

我看到很多有关如何上传文件的文档。但是,如何生成文件,将其分配给模型字段并将Django存储在正确的位置呢?

Here’s my model. What I want to do is generate a new file and overwrite the existing one whenever a model instance is saved:

class Kitten(models.Model):
    claw_size = ...
    license_file = models.FileField(blank=True, upload_to='license')

    def save(self, *args, **kwargs):
        #Generate a new license file overwriting any previous version
        #and update file path
        self.license_file = ???
        super(Request,self).save(*args, **kwargs)

I see lots of documentation about how to upload a file. But how do I generate a file, assign it to a model field and have Django store it in the right place?


回答 0

您想看看Django文档中的FileField和FieldFile,尤其是FieldFile.save()

基本上,FileField在访问时声明为的字段为您提供class的实例FieldFile,该实例为您提供了几种与基础文件进行交互的方法。因此,您需要做的是:

self.license_file.save(new_name, new_contents)

new_name您要分配的文件名在哪里,并且new_contents是文件的内容。请注意,new_contents该实例必须是django.core.files.File或的一个实例django.core.files.base.ContentFile(有关详细信息,请参见给定的手册链接)。这两个选择可以归结为:

# Using File
f = open('/path/to/file')
self.license_file.save(new_name, File(f))
# Using ContentFile
self.license_file.save(new_name, ContentFile('A string with the file content'))

You want to have a look at FileField and FieldFile in the Django docs, and especially FieldFile.save().

Basically, a field declared as a FileField, when accessed, gives you an instance of class FieldFile, which gives you several methods to interact with the underlying file. So, what you need to do is:

self.license_file.save(new_name, new_contents)

where new_name is the filename you wish assigned and new_contents is the content of the file. Note that new_contents must be an instance of either django.core.files.File or django.core.files.base.ContentFile (see given links to manual for the details). The two choices boil down to:

# Using File
f = open('/path/to/file')
self.license_file.save(new_name, File(f))
# Using ContentFile
self.license_file.save(new_name, ContentFile('A string with the file content'))

回答 1

接受的答案当然是一个很好的解决方案,但是这就是我生成CSV并从视图中提供它的方法。

我认为这是值得的,因为我花了点时间来摆弄所有想要的行为(覆盖现有文件,存储到正确的位置,不创建重复的文件等)。

的Django 1.4.1

Python 2.7.3

#Model
class MonthEnd(models.Model):
    report = models.FileField(db_index=True, upload_to='not_used')

import csv
from os.path import join

#build and store the file
def write_csv():
    path = join(settings.MEDIA_ROOT, 'files', 'month_end', 'report.csv')
    f = open(path, "w+b")

    #wipe the existing content
    f.truncate()

    csv_writer = csv.writer(f)
    csv_writer.writerow(('col1'))

    for num in range(3):
        csv_writer.writerow((num, ))

    month_end_file = MonthEnd()
    month_end_file.report.name = path
    month_end_file.save()

from my_app.models import MonthEnd

#serve it up as a download
def get_report(request):
    month_end = MonthEnd.objects.get(file_criteria=criteria)

    response = HttpResponse(month_end.report, content_type='text/plain')
    response['Content-Disposition'] = 'attachment; filename=report.csv'

    return response

Accepted answer is certainly a good solution, but here is the way I went about generating a CSV and serving it from a view.

Thought it was worth while putting this here as it took me a little bit of fiddling to get all the desirable behaviour (overwrite existing file, storing to the right spot, not creating duplicate files etc).

Django 1.4.1

Python 2.7.3

#Model
class MonthEnd(models.Model):
    report = models.FileField(db_index=True, upload_to='not_used')

import csv
from os.path import join

#build and store the file
def write_csv():
    path = join(settings.MEDIA_ROOT, 'files', 'month_end', 'report.csv')
    f = open(path, "w+b")

    #wipe the existing content
    f.truncate()

    csv_writer = csv.writer(f)
    csv_writer.writerow(('col1'))

    for num in range(3):
        csv_writer.writerow((num, ))

    month_end_file = MonthEnd()
    month_end_file.report.name = path
    month_end_file.save()

from my_app.models import MonthEnd

#serve it up as a download
def get_report(request):
    month_end = MonthEnd.objects.get(file_criteria=criteria)

    response = HttpResponse(month_end.report, content_type='text/plain')
    response['Content-Disposition'] = 'attachment; filename=report.csv'

    return response

回答 2

close()在文件保存过程中,如果有异常情况,最好使用上下文管理器或进行调用。如果您的存储后端关闭等情况,可能会发生。

任何覆盖行为都应在存储后端中配置。例如S3Boto3Storage有一个设置AWS_S3_FILE_OVERWRITE。如果您正在使用FileSystemStorage,则可以编写自定义mixin

如果您希望发生任何自定义的副作用(例如最近更新的时间戳记),则可能还需要调用模型的save方法而不是FileField的save方法。如果是这种情况,您还可以将文件的name属性设置为文件的名称-相对于MEDIA_ROOT。它默认为文件的完整路径,如果不设置它,可能会引起问题-请参见File .__ init __()File.name

这是一个示例实例,其中self是模型实例,其中my_file是FileField / ImageFile,它调用save()整个模型实例,而不仅仅是FileField:

import os
from django.core.files import File

with open(filepath, 'rb') as fi:
    self.my_file = File(fi, name=os.path.basename(fi.name))
    self.save()

It’s good practice to use a context manager or call close() in case of exceptions during the file saving process. Could happen if your storage backend is down, etc.

Any overwrite behavior should be configured in your storage backend. For example S3Boto3Storage has a setting AWS_S3_FILE_OVERWRITE. If you’re using FileSystemStorage you can write a custom mixin.

You might also want to call the model’s save method instead of the FileField’s save method if you want any custom side-effects to happen, like last-updated timestamps. If that’s the case, you can also set the name attribute of the file to the name of the file – which is relative to MEDIA_ROOT. It defaults to the full path of the file which can cause problems if you don’t set it – see File.__init__() and File.name.

Here’s an example where self is the model instance where my_file is the FileField / ImageFile, calling save() on the whole model instance instead of just FileField:

import os
from django.core.files import File

with open(filepath, 'rb') as fi:
    self.my_file = File(fi, name=os.path.basename(fi.name))
    self.save()

通过choices =…设置Django IntegerField

问题:通过choices =…设置Django IntegerField

当您拥有一个带有选项选项的模型字段时,您倾向于具有一些与人类可读名称相关的魔术值。Django中是否有一种方便的方法来通过人类可读的名称而不是值来设置这些字段?

考虑以下模型:

class Thing(models.Model):
  PRIORITIES = (
    (0, 'Low'),
    (1, 'Normal'),
    (2, 'High'),
  )

  priority = models.IntegerField(default=0, choices=PRIORITIES)

在某个时候,我们有一个Thing实例,我们想设置它的优先级。显然你可以做,

thing.priority = 1

但这迫使您记住优先级的值-名称映射。这不起作用:

thing.priority = 'Normal' # Throws ValueError on .save()

目前,我有这个愚蠢的解决方法:

thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']

但这很笨重。考虑到这种情况有多普遍,我想知道是否有人有更好的解决方案。是否有一些我完全忽略的通过选择名称设置字段的字段方法?

When you have a model field with a choices option you tend to have some magic values associated with human readable names. Is there in Django a convenient way to set these fields by the human readable name instead of the value?

Consider this model:

class Thing(models.Model):
  PRIORITIES = (
    (0, 'Low'),
    (1, 'Normal'),
    (2, 'High'),
  )

  priority = models.IntegerField(default=0, choices=PRIORITIES)

At some point we have a Thing instance and we want to set its priority. Obviously you could do,

thing.priority = 1

But that forces you to memorize the Value-Name mapping of PRIORITIES. This doesn’t work:

thing.priority = 'Normal' # Throws ValueError on .save()

Currently I have this silly workaround:

thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']

but that’s clunky. Given how common this scenario could be I was wondering if anyone had a better solution. Is there some field method for setting fields by choice name which I totally overlooked?


回答 0

如做在这里看到。然后,您可以使用代表适当整数的单词。

像这样:

LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
    (LOW, 'Low'),
    (NORMAL, 'Normal'),
    (HIGH, 'High'),
)

然后它们仍然是数据库中的整数。

用法是 thing.priority = Thing.NORMAL

Do as seen here. Then you can use a word that represents the proper integer.

Like so:

LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
    (LOW, 'Low'),
    (NORMAL, 'Normal'),
    (HIGH, 'High'),
)

Then they are still integers in the DB.

Usage would be thing.priority = Thing.NORMAL


回答 1

我可能会一劳永逸地设置反向查询字典,但是如果没有,我只会使用:

thing.priority = next(value for value, name in Thing.PRIORITIES
                      if name=='Normal')

这似乎比立即构建dict再扔掉要简单得多;-)。

I’d probably set up the reverse-lookup dict once and for all, but if I hadn’t I’d just use:

thing.priority = next(value for value, name in Thing.PRIORITIES
                      if name=='Normal')

which seems simpler than building the dict on the fly just to toss it away again;-).


回答 2

这是我几分钟前写的一种字段类型,我认为它满足您的要求。它的构造函数需要一个参数“ choices”,它可以是2元组的元组,格式与IntegerField的choices选项相同,也可以是简单的名称列表(例如ChoiceField((’Low’,’Normal’, ‘高’),默认=’低’)。该类为您处理从字符串到int的映射,您从不会看到int。

  class ChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if not hasattr(choices[0],'__iter__'):
            choices = zip(range(len(choices)), choices)

        self.val2choice = dict(choices)
        self.choice2val = dict((v,k) for k,v in choices)

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, value):
        return self.val2choice[value]

    def get_db_prep_value(self, choice):
        return self.choice2val[choice]

Here’s a field type I wrote a few minutes ago that I think does what you want. Its constructor requires an argument ‘choices’, which may be either a tuple of 2-tuples in the same format as the choices option to IntegerField, or instead a simple list of names (ie ChoiceField((‘Low’, ‘Normal’, ‘High’), default=’Low’) ). The class takes care of the mapping from string to int for you, you never see the int.

  class ChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if not hasattr(choices[0],'__iter__'):
            choices = zip(range(len(choices)), choices)

        self.val2choice = dict(choices)
        self.choice2val = dict((v,k) for k,v in choices)

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, value):
        return self.val2choice[value]

    def get_db_prep_value(self, choice):
        return self.choice2val[choice]

回答 3

我欣赏不断的定义方式,但我相信枚举类型最适合此任务。它们可以同时表示一个项目的整数和字符串,同时保持代码的可读性。

枚举在版本3.4中引入Python。如果您使用的是任何较低版本(例如v2.x),则仍可以通过安装向后移植的软件包来安装它:pip install enum34

# myapp/fields.py
from enum import Enum    


class ChoiceEnum(Enum):

    @classmethod
    def choices(cls):
        choices = list()

        # Loop thru defined enums
        for item in cls:
            choices.append((item.value, item.name))

        # return as tuple
        return tuple(choices)

    def __str__(self):
        return self.name

    def __int__(self):
        return self.value


class Language(ChoiceEnum):
    Python = 1
    Ruby = 2
    Java = 3
    PHP = 4
    Cpp = 5

# Uh oh
Language.Cpp._name_ = 'C++'

这几乎就是全部。您可以继承ChoiceEnum来创建自己的定义,并在模型定义中使用它们,例如:

from django.db import models
from myapp.fields import Language

class MyModel(models.Model):
    language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
    # ...

您可能会猜到查询是锦上添花:

MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)

用字符串表示它们也很容易:

# Get the enum item
lang = Language(some_instance.language)

print(str(lang))
# or if you don't prefer `__str__` method..
print(lang.name)

# Same as get_FOO_display
lang.name == some_instance.get_language_display()

I appreciate the constant defining way but I believe Enum type is far best for this task. They can represent integer and a string for an item in the same time, while keeping your code more readable.

Enums were introduced to Python in version 3.4. If you are using any lower (such as v2.x) you can still have it by installing the backported package: pip install enum34.

# myapp/fields.py
from enum import Enum    


class ChoiceEnum(Enum):

    @classmethod
    def choices(cls):
        choices = list()

        # Loop thru defined enums
        for item in cls:
            choices.append((item.value, item.name))

        # return as tuple
        return tuple(choices)

    def __str__(self):
        return self.name

    def __int__(self):
        return self.value


class Language(ChoiceEnum):
    Python = 1
    Ruby = 2
    Java = 3
    PHP = 4
    Cpp = 5

# Uh oh
Language.Cpp._name_ = 'C++'

This is pretty much all. You can inherit the ChoiceEnum to create your own definitions and use them in a model definition like:

from django.db import models
from myapp.fields import Language

class MyModel(models.Model):
    language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
    # ...

Querying is icing on the cake as you may guess:

MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)

Representing them in string is also made easy:

# Get the enum item
lang = Language(some_instance.language)

print(str(lang))
# or if you don't prefer `__str__` method..
print(lang.name)

# Same as get_FOO_display
lang.name == some_instance.get_language_display()

回答 4

class Sequence(object):
    def __init__(self, func, *opts):
        keys = func(len(opts))
        self.attrs = dict(zip([t[0] for t in opts], keys))
        self.choices = zip(keys, [t[1] for t in opts])
        self.labels = dict(self.choices)
    def __getattr__(self, a):
        return self.attrs[a]
    def __getitem__(self, k):
        return self.labels[k]
    def __len__(self):
        return len(self.choices)
    def __iter__(self):
        return iter(self.choices)
    def __deepcopy__(self, memo):
        return self

class Enum(Sequence):
    def __init__(self, *opts):
        return super(Enum, self).__init__(range, *opts)

class Flags(Sequence):
    def __init__(self, *opts):
        return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts)

像这样使用它:

Priorities = Enum(
    ('LOW', 'Low'),
    ('NORMAL', 'Normal'),
    ('HIGH', 'High')
)

priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
class Sequence(object):
    def __init__(self, func, *opts):
        keys = func(len(opts))
        self.attrs = dict(zip([t[0] for t in opts], keys))
        self.choices = zip(keys, [t[1] for t in opts])
        self.labels = dict(self.choices)
    def __getattr__(self, a):
        return self.attrs[a]
    def __getitem__(self, k):
        return self.labels[k]
    def __len__(self):
        return len(self.choices)
    def __iter__(self):
        return iter(self.choices)
    def __deepcopy__(self, memo):
        return self

class Enum(Sequence):
    def __init__(self, *opts):
        return super(Enum, self).__init__(range, *opts)

class Flags(Sequence):
    def __init__(self, *opts):
        return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts)

Use it like this:

Priorities = Enum(
    ('LOW', 'Low'),
    ('NORMAL', 'Normal'),
    ('HIGH', 'High')
)

priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)

回答 5

从Django 3.0开始,您可以使用:

class ThingPriority(models.IntegerChoices):
    LOW = 0, 'Low'
    NORMAL = 1, 'Normal'
    HIGH = 2, 'High'


class Thing(models.Model):
    priority = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)

# then in your code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH

As of Django 3.0, you can use:

class ThingPriority(models.IntegerChoices):
    LOW = 0, 'Low'
    NORMAL = 1, 'Normal'
    HIGH = 2, 'High'


class Thing(models.Model):
    priority = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)

# then in your code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH

回答 6

只需将数字替换为您想要的可读值即可。因此:

PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)

这使其易于阅读,但是,您必须定义自己的顺序。

Simply replace your numbers with the human readable values you would like. As such:

PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)

This makes it human readable, however, you’d have to define your own ordering.


回答 7

我的回答很晚,对于如今的Django专家来说似乎很明显,但是对于在这里居住的人来说,我最近发现了django-model-utils带来的一种非常优雅的解决方案:https : //django-model-utils.readthedocs.io/ zh / latest / utilities.html#choices

此程序包使您可以定义具有三元组的Choices,其中:

  • 第一项是数据库值
  • 第二项是代码可读值
  • 第三项是人类可读的值

因此,您可以执行以下操作:

from model_utils import Choices

class Thing(models.Model):
    PRIORITIES = Choices(
        (0, 'low', 'Low'),
        (1, 'normal', 'Normal'),
        (2, 'high', 'High'),
      )

    priority = models.IntegerField(default=PRIORITIES.normal, choices=PRIORITIES)

thing.priority = getattr(Thing.PRIORITIES.Normal)

这条路:

  • 您可以使用人类可读的值来实际选择字段的值(对我而言,这非常有用,因为我正在抓取疯狂的内容并将其以规范化的方式存储)
  • 干净值存储在数据库中
  • 您没有非DRY可以做的事;)

请享用 :)

My answer is very late and might seem obvious to nowadays-Django experts, but to whoever lands here, i recently discovered a very elegant solution brought by django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

This package allows you to define Choices with three-tuples where:

  • The first item is the database value
  • The second item is a code-readable value
  • The third item is a human-readable value

So here’s what you can do:

from model_utils import Choices

class Thing(models.Model):
    PRIORITIES = Choices(
        (0, 'low', 'Low'),
        (1, 'normal', 'Normal'),
        (2, 'high', 'High'),
      )

    priority = models.IntegerField(default=PRIORITIES.normal, choices=PRIORITIES)

thing.priority = getattr(Thing.PRIORITIES.Normal)

This way:

  • You can use your human-readable value to actually choose the value of your field (in my case, it’s useful because i’m scraping wild content and storing it in a normalized way)
  • A clean value is stored in your database
  • You have nothing non-DRY to do ;)

Enjoy :)


回答 8

最初,我使用@Allan答案的修改版本:

from enum import IntEnum, EnumMeta

class IntegerChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
            choices = list(zip(range(1, len(choices) + 1), [member.name for member in list(choices)]))

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, value):
        return self.choices(value)

    def get_db_prep_value(self, choice):
        return self.choices[choice]

models.IntegerChoiceField = IntegerChoiceField

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Safe to assume last element is largest value member of an enum?
    #type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
    largest_member = GEAR(max([member.value for member in list(GEAR)]))
    type = models.IntegerChoiceField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for member in GEAR:
            setattr(self, member.name, member.value)

print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))

django-enumfields我现在使用的包简化了:

from enumfields import EnumIntegerField, IntEnum

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Safe to assume last element is largest value member of an enum?
    type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
    #largest_member = GEAR(max([member.value for member in list(GEAR)]))
    #type = EnumIntegerField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for member in GEAR:
            setattr(self, member.name, member.value)

Originally I used a modified version of @Allan’s answer:

from enum import IntEnum, EnumMeta

class IntegerChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
            choices = list(zip(range(1, len(choices) + 1), [member.name for member in list(choices)]))

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, value):
        return self.choices(value)

    def get_db_prep_value(self, choice):
        return self.choices[choice]

models.IntegerChoiceField = IntegerChoiceField

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Safe to assume last element is largest value member of an enum?
    #type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
    largest_member = GEAR(max([member.value for member in list(GEAR)]))
    type = models.IntegerChoiceField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for member in GEAR:
            setattr(self, member.name, member.value)

print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))

Simplified with the django-enumfields package package which I now use:

from enumfields import EnumIntegerField, IntEnum

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Safe to assume last element is largest value member of an enum?
    type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
    #largest_member = GEAR(max([member.value for member in list(GEAR)]))
    #type = EnumIntegerField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for member in GEAR:
            setattr(self, member.name, member.value)

回答 9

模型的choices选项接受一个序列,该序列本身由恰好两个项目(例如[[(A,B),(A,B)…])的可迭代项组成,用作该字段的选择。

另外,Django提供 枚举类型,您可以枚举为子类,以简洁的方式定义选择:

class ThingPriority(models.IntegerChoices):
    LOW = 0, _('Low')
    NORMAL = 1, _('Normal')
    HIGH = 2, _('High')

class Thing(models.Model):
    priority = models.IntegerField(default=ThingPriority.NORMAL, choices=ThingPriority.choices)

Django支持在该元组的末尾添加一个额外的字符串值,以用作人类可读的名称或标签。标签可以是懒惰的可翻译字符串。

   # in your code 
   thing = get_thing() # instance of Thing
   thing.priority = ThingPriority.LOW

注意:您可以使用,使用ThingPriority.HIGHThingPriority.['HIGH']ThingPriority(0)访问或查询枚举成员。

Model’s choices option accepts a sequence consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) …]) to use as choices for this field.

In addition, Django provides enumeration types that you can subclass to define choices in a concise way:

class ThingPriority(models.IntegerChoices):
    LOW = 0, _('Low')
    NORMAL = 1, _('Normal')
    HIGH = 2, _('High')

class Thing(models.Model):
    priority = models.IntegerField(default=ThingPriority.NORMAL, choices=ThingPriority.choices)

Django supports adding an extra string value to the end of this tuple to be used as the human-readable name, or label. The label can be a lazy translatable string.

   # in your code 
   thing = get_thing() # instance of Thing
   thing.priority = ThingPriority.LOW

Note: you can use that using ThingPriority.HIGH, ThingPriority.['HIGH'], or ThingPriority(0) to access or lookup enum members.