TandoorRecipes / recipes

Application for managing recipes, planning meals, building shopping lists and much much more!
https://docs.tandoor.dev
Other
5.55k stars 589 forks source link

Create Meal Plan entry via API fails #2614

Closed tomtjes closed 1 year ago

tomtjes commented 1 year ago

Tandoor Version

commit d42d784aeb0595bbc0ed47eb8bbc27ba61b0787d

Setup

Docker / Docker-Compose

Reverse Proxy

SWAG

Other

No response

Bug description

I'm trying to create meal plan entries via API. I'm sending a POST request to .../api/meal-plan/ with the following JSON:

{
    "title": "test recipe",
    "recipe": {
        "name": "test recipe",
        "description": "",
        "keywords": [{}],
        "working_time": 0,
        "waiting_time": 0,
        "internal": true,
        "servings": 6,
        "servings_text": "",
        "rating": null,
        "last_cooked": null
    },
    "servings": "6",
    "note": "",
    "date": "2023-09-04",
    "meal_type": {
        "name": "any",
        "order": 0,
        "icon": "",
        "color": "",
        "default": false
    },
    "shared": []
}

The recipe "test recipe" and meal type "any" exist.

Relevant logs


Environment:

Request Method: POST
Request URL: https://recipes.tld/api/meal-plan/

Django Version: 4.1.10
Python Version: 3.10.13
Installed Applications:
['dal',
 'dal_select2',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.sites',
 'django.contrib.staticfiles',
 'django.contrib.postgres',
 'oauth2_provider',
 'django_prometheus',
 'django_tables2',
 'corsheaders',
 'crispy_forms',
 'rest_framework',
 'rest_framework.authtoken',
 'django_cleanup.apps.CleanupConfig',
 'webpack_loader',
 'django_js_reverse',
 'hcaptcha',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'cookbook.apps.CookbookConfig',
 'treebeard',
 'allauth.socialaccount.providers.nextcloud',
 'debug_toolbar']
Installed Middleware:
['corsheaders.middleware.CorsMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'whitenoise.middleware.WhiteNoiseMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'cookbook.helper.scope_middleware.ScopeMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware']

Traceback (most recent call last):
  File "/opt/recipes/venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner
    response = get_response(request)
  File "/opt/recipes/venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/recipes/venv/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 55, in wrapped_view
    return view_func(*args, **kwargs)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/mixins.py", line 19, in create
    self.perform_create(serializer)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/mixins.py", line 24, in perform_create
    serializer.save()
  File "/opt/recipes/venv/lib/python3.10/site-packages/drf_writable_nested/mixins.py", line 233, in save
    return super(BaseNestedModelSerializer, self).save(**kwargs)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "/opt/recipes/cookbook/serializer.py", line 988, in create
    mealplan = super().create(validated_data)
  File "/opt/recipes/cookbook/serializer.py", line 333, in create
    return super().create(validated_data)
  File "/opt/recipes/venv/lib/python3.10/site-packages/drf_writable_nested/mixins.py", line 253, in create
    self.update_or_create_direct_relations(
  File "/opt/recipes/venv/lib/python3.10/site-packages/drf_writable_nested/mixins.py", line 224, in update_or_create_direct_relations
    attrs[field_source] = serializer.save(
  File "/opt/recipes/venv/lib/python3.10/site-packages/drf_writable_nested/mixins.py", line 233, in save
    return super(BaseNestedModelSerializer, self).save(**kwargs)
  File "/opt/recipes/venv/lib/python3.10/site-packages/rest_framework/serializers.py", line 213, in save
    assert self.instance is not None, (

Exception Type: AssertionError at /api/meal-plan/
Exception Value: `create()` did not return an object instance.
smilerz commented 1 year ago

Existing objects will require an ID. (also, I believe keywords:[{}] is invalid - either omit it entirely or just drop the internal {})

You can probably get away with just an ID, or maybe ID and name.

tomtjes commented 1 year ago

Thanks. Just tried that, but adding id fields doesn't help. Also played around with different versions of keyword arrays. The template json from the API browser doesn't include id fields and according to the API doc, the name fields are required.

{
    "title": "",
    "recipe": {
        "name": "",
        "description": "",
        "keywords": [],
        "working_time": null,
        "waiting_time": null,
        "internal": false,
        "servings": null,
        "servings_text": "",
        "rating": null,
        "last_cooked": null
    },
    "servings": null,
    "note": "",
    "date": null,
    "meal_type": {
        "name": "",
        "order": null,
        "icon": "",
        "color": "",
        "default": false
    },
    "shared": []
}
smilerz commented 1 year ago

The following minimal API worked

curl -X POST 127.0.0.1:8000/api/meal-plan/ -H 'Content-Type: application/json' -H 'Authorization: Bearer tda_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx' -d '{"recipe": {"id": 100, "name": "recipe", "keywords": []}, "meal_type": {"id": 1, "name": "test type"}, "date": "2023-09-06", "servings": 1, "title": "Test Meal Plan", "shared": []}'

tomtjes commented 1 year ago

This works for me as well. Thank you so much for checking!

Not sure if we should close this issue or if the API documentation and API browser template json need fixing, since there's no mention of the id field there.

smilerz commented 1 year ago

This works for me as well. Thank you so much for checking!

Not sure if we should close this issue or if the API documentation and API browser template json need fixing, since there's no mention of the id field there.

The documentation is automatically generated - for better or worse the 'create' assumes creation of the nested objects as well.