From 94147f3d301da2c99978e7bd11e8b00468633daa Mon Sep 17 00:00:00 2001
From: Jean-Baptiste <bleme@pm.me>
Date: Thu, 11 Apr 2019 12:43:59 +0200
Subject: [PATCH] update: manage simple nested fielf (with foreign key)

---
 djangoldp/models.py           | 10 +++++--
 djangoldp/serializers.py      | 16 ++++++++--
 djangoldp/tests/models.py     |  2 +-
 djangoldp/tests/runner.py     | 16 +++++-----
 djangoldp/tests/tests_save.py | 56 ++++++++++++++++++++++++-----------
 djangoldp/tests/tests_temp.py | 11 +++++++
 djangoldp/tests/urls.py       |  2 +-
 setup.cfg                     |  1 +
 8 files changed, 80 insertions(+), 34 deletions(-)
 create mode 100644 djangoldp/tests/tests_temp.py

diff --git a/djangoldp/models.py b/djangoldp/models.py
index e66c1170..ecb4027a 100644
--- a/djangoldp/models.py
+++ b/djangoldp/models.py
@@ -1,6 +1,7 @@
 from django.conf import settings
 from django.contrib.auth.models import AnonymousUser
 from django.db import models
+from django.db.models.base import ModelBase
 from django.urls import get_resolver
 from django.utils.decorators import classonlymethod
 from guardian.shortcuts import get_perms
@@ -36,8 +37,12 @@ class Model(models.Model):
         return cls.__clean_path(r_id)
 
     @classonlymethod
-    def slug_field(cls, instance):
-        view_name = '{}-detail'.format(instance._meta.object_name.lower())
+    def slug_field(cls, instance_or_model):
+        if isinstance(instance_or_model, ModelBase):
+            object_name = instance_or_model.__name__.lower()
+        else:
+            object_name = instance_or_model._meta.object_name.lower()
+        view_name = '{}-detail'.format(object_name)
         slug_field = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][1][0])
         if slug_field.startswith('/'):
             slug_field = slug_field[1:]
@@ -113,7 +118,6 @@ class Model(models.Model):
         return [{'mode': {'@type': name.split('_')[0]}} for name in permissions]
 
 
-
 class LDPSource(models.Model):
     container = models.URLField()
     federation = models.CharField(max_length=255)
diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py
index 3b0f101f..bf364e6e 100644
--- a/djangoldp/serializers.py
+++ b/djangoldp/serializers.py
@@ -358,7 +358,7 @@ class LDPSerializer(HyperlinkedModelSerializer):
             if item is empty:
                 return empty
             try:
-                full_item = next(filter(lambda o: item[self.url_field_name] == o[self.url_field_name], object_list))
+                full_item = next(filter(lambda o: self.url_field_name in o and (item[self.url_field_name] == o[self.url_field_name]), object_list))
             except StopIteration:
                 pass
             if full_item is None:
@@ -390,10 +390,20 @@ class LDPSerializer(HyperlinkedModelSerializer):
                     getattr(instance, field_name).add(related)
 
     def internal_create(self, validated_data, model):
-        nested_fields = []
+
         nested_fk_fields_name = list(filter(lambda key: isinstance(validated_data[key], dict), validated_data))
-        # TODO replace fk_fields_name by the instance in validated_data
+        for field_name in nested_fk_fields_name:
+            field_dict = validated_data[field_name]
+            field_model = getattr(model, field_name).field.rel.model
+            slug_field = Model.slug_field(field_model)
+            if slug_field in field_dict:
+                kwargs = {slug_field: field_dict[slug_field]}
+                sub_inst = field_model.objects.get(**kwargs)
+            else:
+                sub_inst = self.internal_create(field_dict, field_model )
+            validated_data[field_name] = sub_inst
 
+        nested_fields = []
         nested_list_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data))
         for field_name in nested_list_fields_name:
             nested_fields.append((field_name, validated_data.pop(field_name)))
diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py
index 9f828667..d515cc05 100644
--- a/djangoldp/tests/models.py
+++ b/djangoldp/tests/models.py
@@ -61,7 +61,7 @@ class Batch(Model):
 
     class Meta:
         serializer_fields = ['@id', 'title', 'invoice', 'tasks']
-        nested_fields = ["tasks"]
+        nested_fields = ["tasks", 'invoice']
 
 
 class Task(models.Model):
diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py
index dcb50a99..52ea8488 100644
--- a/djangoldp/tests/runner.py
+++ b/djangoldp/tests/runner.py
@@ -53,14 +53,14 @@ from django.test.runner import DiscoverRunner
 test_runner = DiscoverRunner(verbosity=1)
 
 failures = test_runner.run_tests([
-    # 'djangoldp.tests.tests_ldp_model',
-    # 'djangoldp.tests.tests_save',
-    # 'djangoldp.tests.tests_user_permissions',
-    # 'djangoldp.tests.tests_anonymous_permissions',
-    # 'djangoldp.tests.tests_update',
-    # 'djangoldp.tests.tests_auto_author',
-    'djangoldp.tests.tests_temp'
-    # 'djangoldp.tests.tests_get'
+    'djangoldp.tests.tests_ldp_model',
+    'djangoldp.tests.tests_save',
+    'djangoldp.tests.tests_user_permissions',
+    'djangoldp.tests.tests_anonymous_permissions',
+    'djangoldp.tests.tests_update',
+    'djangoldp.tests.tests_auto_author',
+    # 'djangoldp.tests.tests_temp'
+    'djangoldp.tests.tests_get'
 ])
 if failures:
     sys.exit(failures)
diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py
index a0926a2c..3b63d44a 100644
--- a/djangoldp/tests/tests_save.py
+++ b/djangoldp/tests/tests_save.py
@@ -1,5 +1,7 @@
 from django.test import TestCase
+from rest_framework.utils import json
 
+from djangoldp.models import Model
 from djangoldp.serializers import LDPSerializer
 from djangoldp.tests.models import Skill, JobOffer, Invoice, Message
 
@@ -146,24 +148,42 @@ class Save(TestCase):
         self.assertIs(result.joboffer_set.get().skills.count(), 1)
 
     def test_save_fk_graph_with_nested(self):
-        skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
-        skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
-
-        message = {"@graph": [
-            {"text": "message test",
-             "thread": {"@id": "_.123"}
-             },
-            {"@id": "_.123", "description": "thread"},
-        ]}
+        post = {
+            '@graph': [
+                {
+                    'http://happy-dev.fr/owl/#title': "title",
+                    'http://happy-dev.fr/owl/#invoice': {
+                        '@id': "_.123"
+                    }
+                },
+                {
+                    '@id': "_.123",
+                    'http://happy-dev.fr/owl/#title': "title 2"
+                }
+            ]
+        }
 
-        meta_args = {'model': Message, 'depth': 2, 'fields': ("@id", "text", "thread")}
+        response = self.client.post('/batchs/', data=json.dumps(post), content_type='application/ld+json')
+        self.assertEqual(response.status_code, 201)
+        self.assertNotIn('author', response.data)
+        self.assertEquals(response.data['title'], "title")
+        self.assertEquals(response.data['invoice']['title'], "title 2")
 
-        meta_class = type('Meta', (), meta_args)
-        serializer_class = type(LDPSerializer)('MessageSerializer', (LDPSerializer,), {'Meta': meta_class})
-        serializer = serializer_class(data=message)
-        serializer.is_valid()
-        result = serializer.save()
+    def test_save_fk_graph_with_existing_nested(self):
+        invoice = Invoice.objects.create(title="title 3")
+        post = {
+            '@graph': [
+                {
+                    'http://happy-dev.fr/owl/#title': "title",
+                    'http://happy-dev.fr/owl/#invoice': {
+                        '@id': "https://happy-dev.fr{}{}/".format(Model.container_id(invoice), invoice.id)
+                    }
+                }
+            ]
+        }
 
-        self.assertEquals(result.text, "message test")
-        self.assertIsNotNone(result.thread)
-        self.assertEquals(result.thread.description, "thread")
+        response = self.client.post('/batchs/', data=json.dumps(post), content_type='application/ld+json')
+        self.assertEqual(response.status_code, 201)
+        self.assertNotIn('author', response.data)
+        self.assertEquals(response.data['title'], "title")
+        self.assertEquals(response.data['invoice']['title'], "title 3")
diff --git a/djangoldp/tests/tests_temp.py b/djangoldp/tests/tests_temp.py
new file mode 100644
index 00000000..309931fd
--- /dev/null
+++ b/djangoldp/tests/tests_temp.py
@@ -0,0 +1,11 @@
+import json
+
+from rest_framework.test import APITestCase
+
+from djangoldp.models import Model
+from djangoldp.tests.models import Invoice
+
+
+class TestTemp(APITestCase):
+    pass
+
diff --git a/djangoldp/tests/urls.py b/djangoldp/tests/urls.py
index 7ac1c38f..a686bd1b 100644
--- a/djangoldp/tests/urls.py
+++ b/djangoldp/tests/urls.py
@@ -5,7 +5,7 @@ from djangoldp.tests.models import Skill, JobOffer, Message, Thread, Dummy
 from djangoldp.views import LDPViewSet
 
 urlpatterns = [
-    url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[], fields=["@id", "text"], nested_fields=[])),
+    url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[], fields=["@id", "text", "thread"], nested_fields=['thread'])),
     url(r'^threads/', LDPViewSet.urls(model=Thread, nested_fields=["message_set"], permission_classes=())),
     url(r'^users/', LDPViewSet.urls(model=settings.AUTH_USER_MODEL, permission_classes=[])),
     url(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[], lookup_field='slug',)),
diff --git a/setup.cfg b/setup.cfg
index 70a69cd1..bc9625e0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,6 +14,7 @@ setup_requires =
 install_requires =
     django~=1.11
     django_rest_framework
+    requests
     pyld
     django-guardian
 
-- 
GitLab