django + tastypie:我如何 POST 在不获取 "duplicate key value violates unique constraint" 的情况下替换数据

django + tastypie: How do I POST to replace data without getting "duplicate key value violates unique constraint"

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

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

那么,我哪里错了?

谢谢

好的,所以我最后包括了一个名称检查,然后是 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}

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

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

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 > 然后它应该工作。

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