django + sweetpie:我如何发布以替换数据而不得到“重复键值违反唯一约束”

Posted

技术标签:

【中文标题】django + sweetpie:我如何发布以替换数据而不得到“重复键值违反唯一约束”【英文标题】:django + tastypie: How do I POST to replace data without getting "duplicate key value violates unique constraint" 【发布时间】:2015-09-11 23:22:27 【问题描述】:

我正在尝试使用tastepie 为我的Django 项目编写一个REST API。我可以用它来发布一些数据:

curl --dump-header - -H "Content-Type: application/json" -X POST --data '"name": "environment1", "last_active": "2015-06-18T15:56:37"' http://localhost:8000/api/v1/report_status/

它在放入数据库时​​起作用,但是当我来发送具有环境名称的第二组数据(即重新发送相同的请求)时,为了替换发送的第一组数据,我得到以下错误(略):

"error_message": "duplicate key value violates unique constraint \"<project_name>_environment_name_key\"\nDETAIL:  Key (name)=(production) already exists.\n", "traceback": "Traceback ... django.db.utils.IntegrityError: duplicate key value violates unique constraint \"oilserver_environment_name_key\"\nDETAIL:  Key (name)=(production) already exists.

我了解我已将环境名称设置为唯一,但我正在尝试替换数据,而不是上传另一个具有相同名称的环境。问题似乎是 id 是自动递增的。我不希望每次都提供一个 id - 我希望最终用户简单地提供一个环境名称并让它替换已经在数据库中的数据。谁能告诉我这通常是怎么做的?

下面是代码的相关部分。我有一个外键,我不确定它是否会使事情复杂化,或者这是否完全是另一回事。

models.py:

from django.db import models


class EnvironmentState(models.Model):
    name = models.CharField(
        max_length=255,
        default="Unknown",
        help_text="Current state of the environment.")
    description = models.TextField(
        default=None,
        blank=True,
        null=True,
        help_text="Optional description for state.")

    def __str__(self):
        return self.name


class Environment(models.Model):
    name = models.CharField(
        max_length=255,
        unique=True,
        help_text="Name of environment")
    last_active = models.DateTimeField(
        default=None,
        blank=True,
        null=True,
        help_text="DateTime when environment message was last received.")
    current_situation = models.TextField(
        help_text="Statement(s) giving background to the current env status.")
    status = models.ForeignKey(EnvironmentState)

    def __str__(self):
        return self.name

resources.py:

from tastypie import fields
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from oilserver.models import Environment, EnvironmentState
from oilserver.status_checker import StatusChecker


class EnvironmentStateResource(ModelResource):
    class Meta:
        queryset = EnvironmentState.objects.all()
        resource_name = 'environment_state'
        authorization = Authorization()


class ReportStatusResource(ModelResource):
    status = fields.ForeignKey(EnvironmentStateResource, 'status', 
                               null=True, full=True)

    class Meta:
        queryset = Environment.objects.all()
        resource_name = 'report_status'
        authorization = Authorization()

    def hydrate(self, bundle):
        name = bundle.data.get('name')
        last_active = bundle.data.get('last_active')

        status_checker = StatusChecker(last_active)
        # StatusChecker is just a class that takes in some data and 
        # generates a 'state' (up, down) and a 'situation' string explaining 
        # to the user what is going on.

        bundle.data['current_situation'] = status_checker.situation
        env_state = EnvironmentState.objects.get(name=status_checker.state)
        bundle.data['status'] = "pk": env_state.pk

        return bundle

那么,我哪里错了?

谢谢

【问题讨论】:

【参考方案1】:

好的,所以我最终包含了一个名称检查,然后是 bundle.data['id'] = env.id 如果它已经存在:

class ReportStatusResource(ModelResource):
    status = fields.ForeignKey(EnvironmentStateResource, 'status', 
                               null=True, full=True)

    class Meta:
        queryset = Environment.objects.all()
        resource_name = 'report_status'
        authorization = Authorization()

    def hydrate(self, bundle):
        name = bundle.data.get('name')

        for env in Environment.objects.all():
            if env.name == name:
                bundle.data['id'] = env.id

        last_active = bundle.data.get('last_active')

        status_checker = StatusChecker(last_active)
        # StatusChecker is just a class that takes in some data and 
        # generates a 'state' (up, down) and a 'situation' string explaining 
        # to the user what is going on.

        bundle.data['current_situation'] = status_checker.situation
        env_state = EnvironmentState.objects.get(name=status_checker.state)
        bundle.data['status'] = "pk": env_state.pk

我对其他解决方案持开放态度,但如果其他人有更好的想法......

【讨论】:

【参考方案2】:

您必须针对单个资源,例如:

http://localhost:8000/api/v1/report_status/<IDENTIFIER OF THE RESOURCE YOU WANT TO UPDATE>

我认为你需要一个“PUT”请求而不是“POST”

所以在你创建环境之后,你得到它的 id,你发送一个“PUT”请求到“http://localhost:8000/api/v1/report_status/ 然后它应该可以工作了。

【讨论】:

是的,你是对的。我需要把 id 放在最后。谢谢【参考方案3】:

您可以将环境名称公开为端点,这样您的资源 url 将变为 http://localhost:8000/api/v1/report_status/[environment_name],然后使用 PUT 调用,这将根据给定环境名称的资源是否更新或创建新资源存在与否。

Tastypie docs about exposing non PK endpoints

Tastypie source code for put_details

【讨论】:

以上是关于django + sweetpie:我如何发布以替换数据而不得到“重复键值违反唯一约束”的主要内容,如果未能解决你的问题,请参考以下文章

在 Django 项目中使用 sweetpie 将多个对象保存在同一模型中

使用其他视图的 sweetpie api

如何分析 Django 的扩展瓶颈?

Angularjs路由与django的网址

如何检查该用户是不是已通过来自美味派的身份验证?

美味派api没有在json结果中显示ForeignKey