0

I took the Django drf-writable-nested package and made it a complete implementation called drf-writable-example by adding a bit of REST code (see below). (See drf-writable-nested page for the models and serializers.)

If I POST their JSON example (see below) twice, it creates every object in the JSON twice. So, two Site objects with url "http://google.com", two Avatar objects with image "image-1.png", etc.

How do I instead modify that code to create-or-update? That is, if the "http://google.com" site already exists, then just use it, and if the avatar object with "image-1.png" already exists, just use it, etc.?

Edit: It looks like this issue is about this question.

Edit 2: I thought POST/PUT might come up. Yes, I've read a number of blog posts and stackoverflow answers about POST and PUT. But I want update_or_create (also here and here), also called "upsert." That may not fit neatly into REST, but it's easier for the client, so that's what I want. POST is closer, because there may not be an object, so there's no id.

REST code:

from rest_framework import routers, viewsets

from .serializers import UserSerializer
from .models import User


class UserModelViewSet(viewsets.ModelViewSet):
    queryset = ExampleUser.objects.all()
    serializer_class = UserSerializer

router = routers.DefaultRouter()
router.register(r'users', UserModelViewSet)

and registering that router to a URL.

Now I can POST the example from their docs (user-example.json) using httpie:

http POST http://localhost:8000/api/users/ \
    < examples/user-example.json > my.log

If I POST that twice, I get two complete sets of stuff:

GET /api/users/
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "pk": 1,
        "profile": {
            "pk": 1,
            "sites": [
                {
                    "pk": 1,
                    "url": "http://google.com"
                },
                {
                    "pk": 2,
                    "url": "http://yahoo.com"
                }
            ],
            "avatars": [
                {
                    "pk": 1,
                    "image": "image-1.png"
                },
                {
                    "pk": 2,
                    "image": "image-2.png"
                }
            ],
            "access_key": {
                "pk": 1,
                "key": "key"
            }
        },
        "username": "test"
    },
    {
        "pk": 2,
        "profile": {
            "pk": 2,
            "sites": [
                {
                    "pk": 3,
                    "url": "http://google.com"
                },
                {
                    "pk": 4,
                    "url": "http://yahoo.com"
                }
            ],
            "avatars": [
                {
                    "pk": 3,
                    "image": "image-1.png"
                },
                {
                    "pk": 4,
                    "image": "image-2.png"
                }
            ],
            "access_key": {
                "pk": 2,
                "key": "key"
            }
        },
        "username": "test"
    }
]
dfrankow
  • 20,191
  • 41
  • 152
  • 214
  • 1
    Normally you would need to send a PUT request to /api/users/user_id/ to update a user, a POST request triggers the create method on the view set. You want to change this behavior? – Ozgur Akcali Apr 18 '19 at 22:31
  • 1
    In REST Architecture POST is for creating while PUT and PATCH are for updating. POST works on the list route (as the object should not yet exist) while update methods work on detail route as the id of the object to be updated should already be known. So if you want to update an existing user object, send PUT or PATCH to `/api/users//` – Ken4scholars Apr 19 '19 at 00:06
  • @OzgurAkcali I want update_or_create (a.k.a. upsert), see "Edit 2" above. I don't think that quite fits into REST, but it's what I want, and closer to POST. – dfrankow Apr 19 '19 at 01:57
  • @Ken4scholars I want update_or_create (a.k.a. upsert), see "Edit 2" above. I don't think that quite fits into REST, but it's what I want, and closer to POST. – dfrankow Apr 19 '19 at 01:57
  • @OzgurAkcali if you did read the issue you sent clearly then they answered that if you want to update, then you should add the PK. `Now object updates if PK is specified. PK can be any custom field #10.` Else it wouldn't know that you want to update or even the object you want to update – Ken4scholars Apr 19 '19 at 02:54

1 Answers1

0

As is echoed by several of the commenters, under the REST semantics:

  • Technically, the RFC says that a POST should handle the request "according to the resource's own specific semantics", but this is conventionally a create. Multiple POSTs are multiple requests to create and should be handled that way.
  • In your case, there should be a uniqueness constraint on the username. If this was in-place, the second request would error. I believe the proper error code for the second POST is 409 Conflict although many tools (including DRF) will return a 400. The 409 is a better fit (theoretically) because the error was not in the request, but an incompatibility between the request and the database state (i.e. the key collision).
  • A valid alternative would be to PUT the request to a URL like /users/<username>/. This ensures you never need to know the user_id (as you would in a comment above). The first request would create and the second would update. If you use a strategy like this, you really should implement a concurrency check -- e.g. this DRF-compatible library.
Community
  • 1
  • 1
claytond
  • 1,061
  • 9
  • 22