diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 137fb381afb68daedc80ff91318b00c249ef8a82..0e31444c03f5950e534f9e291ae735258c967755 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,8 @@ stages:
 test:
   stage: test
   script:
-    - echo 'Make your tests here !'
+    - pip install .[dev]
+    - python -m unittest djangoldp.tests.runner
   except:
     - master
   tags:
diff --git a/README.md b/README.md
index a565c8354947546fef8580a46dc421bde133241c..07ac1cf2a49059c4e42967fae86544e5decf6264 100644
--- a/README.md
+++ b/README.md
@@ -27,11 +27,14 @@ django-admin startproject myldpserver
 ```
 
 3. Create your django model inside a file myldpserver/myldpserver/models.py
+Note that container_path will be use to resolve instance iri and container iri
+In the future it could also be used to auto configure django router (e.g. urls.py)
 
 ```
-from django.db import models
+from djangoldp.models import Model
 
-class Todo(models.Model):
+class Todo(Model):
+    container_path = "/my-path/"
     name = models.CharField(max_length=255)
     deadline = models.DateTimeField()
 
diff --git a/djangoldp/models.py b/djangoldp/models.py
index 02503fb212486c911a20f89112fa6a62700f11e1..6e02705c724270e6299eb2010c8d93e383f76e23 100644
--- a/djangoldp/models.py
+++ b/djangoldp/models.py
@@ -1,16 +1,86 @@
 from django.conf import settings
 from django.db import models
+from django.urls import get_resolver
 from rest_framework import fields
 
+
+class Model(models.Model):
+    container_path = None
+
+    def get_container_path(self):
+        return self.container_path
+
+    def get_absolute_url(self):
+        return Model.resource_id(self)
+
+    def get_container_id(self):
+        return Model.container_id(self)
+
+    @classmethod
+    def resource_id(cls, instance):
+        view_name = '{}-detail'.format(instance._meta.object_name.lower())
+        slug_field = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][1][0])
+        if slug_field.startswith('/'):
+            slug_field = slug_field[1:]
+        return "{}{}".format(cls.container_id(instance), getattr(instance, slug_field))
+
+    @classmethod
+    def container_id(cls, instance):
+        if isinstance(instance, cls):
+            path = instance.container_path
+            if path is None:
+                path = "{}s".format(instance._meta.object_name.lower())
+        else:
+            view_name = '{}-list'.format(instance._meta.object_name.lower())
+            path = get_resolver().reverse(view_name)
+
+        path = cls.__clean_path(path)
+
+        return path
+
+    class Meta:
+        abstract = True
+
+    @classmethod
+    def resolve_id(cls, id):
+        id = cls.__clean_path(id)
+        view, args, kwargs = get_resolver().resolve(id)
+        return view.initkwargs['model'].objects.get(**kwargs)
+
+    @classmethod
+    def resolve_container(cls, path):
+        path = cls.__clean_path(path)
+        view, args, kwargs = get_resolver().resolve(path)
+        return view.initkwargs['model']
+
+    @classmethod
+    def resolve(cls, path):
+        container = cls.resolve_container(path)
+        try:
+            resolve_id = cls.resolve_id(path)
+        except:
+            resolve_id = None
+        return container, resolve_id
+
+    @classmethod
+    def __clean_path(cls, path):
+        if not path.startswith("/"):
+            path = "/{}".format(path)
+        if not path.endswith("/"):
+            path = "{}/".format(path)
+        return path
+
+
 class LDPUrlField (fields.URLField):
     def to_representation(self, value):
         str = super(LDPUrlField, self).to_representation(value)
         return {'@id': str}
 
+
 class LDPSource(models.Model):
     container = models.URLField()
     federation = models.CharField(max_length=255)
-    
+
     class Meta:
         rdf_type = 'sib:source'
         ordering = ('federation',)
@@ -18,7 +88,7 @@ class LDPSource(models.Model):
             ('view_source', 'acl:Read'),
             ('control_source', 'acl:Control'),
         )
-    
+
     def __str__(self):
         return "{}: {}".format(self.federation, self.container)
 
@@ -30,6 +100,7 @@ class LDNotification(models.Model):
     type = models.CharField(max_length=255)
     summary = models.TextField()
     date = models.DateTimeField(auto_now_add=True)
+
     class Meta:
         permissions = (
             ('view_todo', 'Read'),
diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py
index 41bb34d43eb2b69c119e009e70ab1f7ca870cb1f..8f68daec47f465da5e069173e9d976f2dfb8592a 100644
--- a/djangoldp/serializers.py
+++ b/djangoldp/serializers.py
@@ -1,9 +1,23 @@
+from collections import OrderedDict, Mapping
+from urllib import parse
+
 from django.core.exceptions import ImproperlyConfigured
-from django.core.urlresolvers import get_resolver
+from django.core.exceptions import ValidationError as DjangoValidationError
+from django.core.urlresolvers import get_resolver, resolve, get_script_prefix, Resolver404
 from django.utils.datastructures import MultiValueDictKeyError
+from django.utils.encoding import uri_to_iri
 from guardian.shortcuts import get_perms
-from rest_framework.fields import empty
+from rest_framework.exceptions import ValidationError
+from rest_framework.fields import SkipField, empty
+from rest_framework.fields import get_error_detail, set_value
 from rest_framework.relations import HyperlinkedRelatedField, ManyRelatedField, MANY_RELATION_KWARGS
+from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer
+from rest_framework.settings import api_settings
+from rest_framework.utils.field_mapping import get_nested_relation_kwargs
+from rest_framework.utils.serializer_helpers import ReturnDict
+
+from djangoldp.models import Model
+
 from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer
 from rest_framework.utils.field_mapping import get_nested_relation_kwargs
 from rest_framework.utils.serializer_helpers import ReturnDict
@@ -13,17 +27,19 @@ from djangoldp import models
 
 class LDListMixin:
     def to_internal_value(self, data):
-        # data = json.loads(data)
         try:
             data = data['ldp:contains']
-        except TypeError:
+        except (TypeError, KeyError):
             pass
         if isinstance(data, dict):
             data = [data]
+        if isinstance(data, str) and str.startswith("http"):
+            data = [{'@id': data}]
         return [self.child.to_internal_value(item) for item in data]
 
     def to_representation(self, value):
         return {'@id': self.id, '@type': 'ldp:Container', 'ldp:contains': super().to_representation(value)}
+
     def get_attribute(self, instance):
         parent_id_field = self.parent.fields[self.parent.url_field_name]
         context = self.parent.context
@@ -31,6 +47,41 @@ class LDListMixin:
         self.id = parent_id + self.field_name + "/"
         return super().get_attribute(instance)
 
+    def get_value(self, dictionary):
+        try:
+            object_list = dictionary["@graph"]
+            container_id = Model.container_id(self.parent.instance)
+            obj = next(filter(lambda o: container_id in o['@id'], object_list))
+            list = super().get_value(obj)
+            try:
+                list = next(filter(lambda o: list['@id'] == o['@id'], object_list))
+            except (KeyError, TypeError):
+                pass
+
+            try:
+                list = list['ldp:contains']
+            except (KeyError, TypeError):
+                pass
+
+            if isinstance(list, dict):
+                list = [list]
+
+            ret = []
+            for item in list:
+                full_item = None
+                try:
+                    full_item = next(filter(lambda o: item['@id'] == o['@id'], object_list))
+                except StopIteration:
+                    pass
+                if full_item is None:
+                    ret.append(item)
+                else:
+                    ret.append(full_item)
+
+            return ret
+        except KeyError:
+            return super().get_value(dictionary)
+
 
 class ContainerSerializer(LDListMixin, ListSerializer):
     id = ''
@@ -45,7 +96,7 @@ class ContainerSerializer(LDListMixin, ListSerializer):
     def to_internal_value(self, data):
         try:
             return super().to_internal_value(data['@id'])
-        except:
+        except (KeyError, TypeError):
             return super().to_internal_value(data)
 
 
@@ -66,6 +117,12 @@ class JsonLdField(HyperlinkedRelatedField):
         except MultiValueDictKeyError:
             pass
 
+    def to_internal_value(self, data):
+        return super().to_internal_value(data)
+
+    def get_value(self, dictionary):
+        return super().get_value(dictionary)
+
 
 class JsonLdRelatedField(JsonLdField):
     def to_representation(self, value):
@@ -77,7 +134,7 @@ class JsonLdRelatedField(JsonLdField):
     def to_internal_value(self, data):
         try:
             return super().to_internal_value(data['@id'])
-        except:
+        except (KeyError, TypeError):
             return super().to_internal_value(data)
 
     @classmethod
@@ -101,17 +158,24 @@ class JsonLdIdentityField(JsonLdField):
     def to_internal_value(self, data):
         try:
             return super().to_internal_value(data['@id'])
-        except:
+        except KeyError:
             return super().to_internal_value(data)
 
+    def get_value(self, dictionary):
+        return super().get_value(dictionary)
+
 
 class LDPSerializer(HyperlinkedModelSerializer):
     url_field_name = "@id"
     serializer_related_field = JsonLdRelatedField
     serializer_url_field = JsonLdIdentityField
-
     ModelSerializer.serializer_field_mapping [django_models.URLField] = models.LDPUrlField
 
+
+    @property
+    def data(self):
+        return super().data
+
     def get_default_field_names(self, declared_fields, model_info):
         try:
             fields = list(self.Meta.model._meta.serializer_fields)
@@ -134,6 +198,27 @@ class LDPSerializer(HyperlinkedModelSerializer):
                                get_perms(self.context['request'].user, obj)]
         return data
 
+    def build_standard_field(self, field_name, model_field):
+        class JSonLDStandardField:
+            parent_view_name = None
+
+            def __init__(self, **kwargs):
+                self.parent_view_name = kwargs.pop('parent_view_name')
+                super().__init__(**kwargs)
+
+            def get_value(self, dictionary):
+                try:
+                    object_list = dictionary["@graph"]
+                    resource_id = Model.resource_id(self.parent.instance)
+                    obj = next(filter(lambda o: resource_id in o['@id'], object_list))
+                    return super().get_value(obj)
+                except KeyError:
+                    return super().get_value(dictionary)
+
+        field_class, field_kwargs = super().build_standard_field(field_name, model_field)
+        field_kwargs['parent_view_name'] = '{}-list'.format(model_field.model._meta.object_name.lower())
+        return type(field_class.__name__ + 'Valued', (JSonLDStandardField, field_class), {}), field_kwargs
+
     def build_nested_field(self, field_name, relation_info, nested_depth):
         class NestedLDPSerializer(self.__class__):
 
@@ -146,10 +231,56 @@ class LDPSerializer(HyperlinkedModelSerializer):
                     fields = '__all__'
 
             def to_internal_value(self, data):
-                model = self.Meta.model
-                return self.serializer_related_field(
-                    view_name='{}-detail'.format(model._meta.object_name.lower()),
-                    queryset=model.objects.all()).to_internal_value(data)
+                if self.url_field_name in data:
+                    if not isinstance(data, Mapping):
+                        message = self.error_messages['invalid'].format(
+                            datatype=type(data).__name__
+                        )
+                        raise ValidationError({
+                            api_settings.NON_FIELD_ERRORS_KEY: [message]
+                        }, code='invalid')
+
+                    ret = OrderedDict()
+                    errors = OrderedDict()
+                    fields = list(filter(lambda x: x.field_name in data, self._writable_fields))
+
+                    for field in fields:
+                        validate_method = getattr(self, 'validate_' + field.field_name, None)
+                        primitive_value = field.get_value(data)
+                        try:
+                            validated_value = field.run_validation(primitive_value)
+                            if validate_method is not None:
+                                validated_value = validate_method(validated_value)
+                        except ValidationError as exc:
+                            errors[field.field_name] = exc.detail
+                        except DjangoValidationError as exc:
+                            errors[field.field_name] = get_error_detail(exc)
+                        except SkipField:
+                            pass
+                        else:
+                            set_value(ret, field.source_attrs, validated_value)
+
+                    if errors:
+                        raise ValidationError(errors)
+
+                    uri = data[self.url_field_name]
+                    http_prefix = uri.startswith(('http:', 'https:'))
+
+                    if http_prefix:
+                        uri = parse.urlparse(uri).path
+                        prefix = get_script_prefix()
+                        if uri.startswith(prefix):
+                            uri = '/' + uri[len(prefix):]
+
+                    try:
+                        match = resolve(uri_to_iri(uri))
+                        ret['pk'] = match.kwargs['pk']
+                    except Resolver404:
+                        pass
+
+                    return ret
+                else:
+                    return super().to_internal_value(data)
 
         kwargs = get_nested_relation_kwargs(relation_info)
         kwargs['read_only'] = False
@@ -158,19 +289,96 @@ class LDPSerializer(HyperlinkedModelSerializer):
 
     @classmethod
     def many_init(cls, *args, **kwargs):
-        kwargs['child'] = cls()
+        kwargs['child'] = cls(**kwargs)
+        try:
+            cls.Meta.depth = kwargs['context']['view'].many_depth
+        except KeyError:
+            pass
         return ContainerSerializer(*args, **kwargs)
 
+    def get_value(self, dictionary):
+        try:
+            object_list = dictionary["@graph"]
+            container_id = Model.container_path(self.parent.instance)
+            obj = next(filter(lambda o: container_id in o[self.url_field_name], object_list))
+            item = super().get_value(obj)
+            full_item = None
+            if item is empty:
+                return empty
+            try:
+                full_item = next(filter(lambda o: item['@id'] == o['@id'], object_list))
+            except StopIteration:
+                pass
+            if full_item is None:
+                return item
+            else:
+                return full_item
+
+        except KeyError:
+            return super().get_value(dictionary)
+
     def create(self, validated_data):
+        return self.internal_create(validated_data, model=self.Meta.model)
+
+    def internal_create(self, validated_data, model):
         nested_fields = []
         nested_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data))
         for field_name in nested_fields_name:
             nested_fields.append((field_name, validated_data.pop(field_name)))
 
-        obj = self.Meta.model.objects.create(**validated_data)
+        instance = model.objects.create(**validated_data)
+
+        self.save_or_update_nested_list(instance, nested_fields)
+
+        return instance
 
+    def update(self, instance, validated_data):
+        nested_fields = []
+        nested_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data))
+        for field_name in nested_fields_name:
+            nested_fields.append((field_name, validated_data.pop(field_name)))
+
+        for attr, value in validated_data.items():
+            if isinstance(value, dict):
+                manager = getattr(instance, attr)
+                if 'pk' in value:
+                    oldObj = manager._meta.model.objects.get(pk=value['pk'])
+                    value = self.update(instance=oldObj, validated_data=value)
+                else:
+                    value = self.internal_create(validated_data=value, model=manager._meta.model)
+            setattr(instance, attr, value)
+
+        instance.save()
+
+        self.save_or_update_nested_list(instance, nested_fields)
+
+        return instance
+
+    def save_or_update_nested_list(self, instance, nested_fields):
         for (field_name, data) in nested_fields:
-            for item in data:
-                getattr(obj, field_name).add(item)
+            manager = getattr(instance, field_name)
 
-        return obj
+            item_pk_to_keep = list(map(lambda e: int(e['pk']), filter(lambda x: 'pk' in x, data)))
+            for item in list(manager.all()):
+                if not item.pk in item_pk_to_keep:
+                    if getattr(manager, 'through', None) is None:
+                        item.delete()
+                    else:
+                        manager.remove(item)
+
+            for item in data:
+                if 'pk' in item:
+                    oldObj = manager.model.objects.get(pk=item['pk'])
+                    savedItem = self.update(instance=oldObj, validated_data=item)
+                else:
+                    rel = getattr(instance._meta.model, field_name).rel
+                    try:
+                        if rel.related_model == manager.model:
+                            reverse_id = rel.remote_field.attname
+                            item[reverse_id] = instance.pk
+                    except AttributeError:
+                        pass
+                    savedItem = self.internal_create(validated_data=item, model=manager.model)
+
+                if getattr(manager, 'through', None) is not None and manager.through._meta.auto_created:
+                    manager.add(savedItem)
diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py
index d8e0e76be8cc145c7dcc45635a4a342037dcc9a9..a68a803c1f26baa1bdb47ddd2c22706666b4c07f 100644
--- a/djangoldp/tests/models.py
+++ b/djangoldp/tests/models.py
@@ -1,12 +1,34 @@
 from django.conf import settings
 from django.db import models
 
+from djangoldp.models import Model
+
 
 class Skill(models.Model):
     title = models.CharField(max_length=255, blank=True, null=True)
+    obligatoire = models.CharField(max_length=255)
 
 
 class JobOffer(models.Model):
     title = models.CharField(max_length=255, blank=True, null=True)
     skills = models.ManyToManyField(Skill, blank=True)
 
+
+class Thread(models.Model):
+    description = models.CharField(max_length=255, blank=True, null=True)
+    author_user = models.ForeignKey(settings.AUTH_USER_MODEL)
+
+
+class Message(models.Model):
+    text = models.CharField(max_length=255, blank=True, null=True)
+    thread = models.ForeignKey(Thread, on_delete=models.DO_NOTHING)
+    author_user = models.ForeignKey(settings.AUTH_USER_MODEL)
+
+
+class Dummy(models.Model):
+    some = models.CharField(max_length=255, blank=True, null=True)
+
+
+class LDPDummy(Model):
+    some = models.CharField(max_length=255, blank=True, null=True)
+
diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py
index 8e04314473e2e7b8898ed9f3fa004daa303dc121..70074c5b66755ae9ffe7715611f1e2c3335c0945 100644
--- a/djangoldp/tests/runner.py
+++ b/djangoldp/tests/runner.py
@@ -24,6 +24,12 @@ from django.test.runner import DiscoverRunner
 
 test_runner = DiscoverRunner(verbosity=1)
 
-failures = test_runner.run_tests(['djangoldp.tests.tests'])
+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'])
 if failures:
     sys.exit(failures)
+
diff --git a/djangoldp/tests/tests.py b/djangoldp/tests/tests.py
deleted file mode 100644
index fe5de882868459b37beb4d3de5d5b0d0e8a15f6d..0000000000000000000000000000000000000000
--- a/djangoldp/tests/tests.py
+++ /dev/null
@@ -1,122 +0,0 @@
-from django.test import TestCase
-
-from djangoldp.serializers import LDPSerializer
-from djangoldp.tests.models import Skill, JobOffer
-
-
-class Serializer(TestCase):
-
-    def test_container_serializer_save(self):
-        skill1 = Skill.objects.create(title="skill1")
-        skill2 = Skill.objects.create(title="skill2")
-        job = {"title": "job test",
-               "skills": {
-                   "ldp:contains": [
-                       {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)},
-                       {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk)},
-                   ]}
-               }
-
-        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
-
-        meta_class = type('Meta', (), meta_args)
-        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
-        serializer = serializer_class(data=job)
-        serializer.is_valid()
-        result = serializer.save()
-
-        self.assertEquals(result.title, "job test")
-        self.assertIs(result.skills.count(), 2)
-
-    def test_save_without_nested_fields(self):
-        skill1 = Skill.objects.create(title="skill1")
-        skill2 = Skill.objects.create(title="skill2")
-        job = {"title": "job test"}
-
-        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
-
-        meta_class = type('Meta', (), meta_args)
-        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
-        serializer = serializer_class(data=job)
-        serializer.is_valid()
-        result = serializer.save()
-
-        self.assertEquals(result.title, "job test")
-        self.assertIs(result.skills.count(), 0)
-
-from django.test import TestCase, Client, RequestFactory
-from djangoldp.views import LDPViewSet
-from djangoldp.permissions import AnonymousReadOnly
-
-from django.contrib.auth.models import AnonymousUser, User
-from djangoldp_joboffer.models import JobOffer
-
-
-class TestUserPermissions (TestCase):
-    def setUp(self):
-        self.factory = RequestFactory()
-#        self.c = Client()
-        self.user = User.objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion')
-
-    def tearDown(self):
-        self.user.delete()
-
-    def test_get_with_user(self):
-        request = self.factory.get('/job-offers/')
-        request.user = self.user
-        my_view = LDPViewSet.as_view({'get': 'list'}, model=JobOffer, nested_fields=["skills"], permission_classes=[AnonymousReadOnly])
-        response = my_view(request)
-        self.assertEqual(response.status_code, 200)
-
-    def test_request_options_create_with_user(self):
-        request = self.factory.options('/job-offers/')
-        request.user = self.user
-        my_view = LDPViewSet.as_view({'options': 'create'}, model=JobOffer, nested_fields=["skills"], permission_classes=[AnonymousReadOnly])
-        response = my_view(request)
-        self.assertEqual(response.status_code, 201)
-
-    def test_request_options_update_with_user(self):
-        request = self.factory.options('/job-offers/')
-        request.user = self.user
-        my_view = LDPViewSet.as_view({'options': 'update'}, model=JobOffer, nested_fields=["skills"], permission_classes=[AnonymousReadOnly])
-        response = my_view(request)
-        self.assertEqual(response.status_code, 201)
-
-
-class TestAnonymousUserPermissions (TestCase):
-    def setUp(self):
-        self.factory = RequestFactory()
-#        self.c = Client()
-        self.user = AnonymousUser
-
-    def test_get_request_with_anonymousUser(self):
-        request = self.factory.get("/job-offers/")
-        request.user = self.user
-        my_view = LDPViewSet.as_view({'get': 'list'},
-                                     model=JobOffer,
-                                     nested_fields=["skills"],
-                                     permission_classes=[AnonymousReadOnly])
-        response = my_view(request)
-        self.assertEqual(response.status_code, 200)
-
-    def test_request_options_create_with_anonymousUser(self):
-        request = self.factory.options("/job-offers/")
-        request.user = self.user
-        my_view = LDPViewSet.as_view({'options': 'create'},
-                                     model=JobOffer,
-                                     nested_fields=["skills"],
-                                     permission_classes=[AnonymousReadOnly])
-        response = my_view(request)
-        self.assertEqual(response.status_code, 403)
-
-    def test_request_options_update_with_anonymousUser(self):
-        request = self.factory.options("/job-offers/")
-        request.user = self.user
-        my_view = LDPViewSet.as_view({'options': 'update'},
-                                     model=JobOffer,
-                                     nested_fields=["skills"],
-                                     permission_classes=[AnonymousReadOnly])
-        response = my_view(request)
-        self.assertEqual(response.status_code, 403)
-
-
diff --git a/djangoldp/tests/tests_anonymous_permissions.py b/djangoldp/tests/tests_anonymous_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8f4ee3f471f3d245c8f92fbd00ecfbd73d08fd8
--- /dev/null
+++ b/djangoldp/tests/tests_anonymous_permissions.py
@@ -0,0 +1,43 @@
+from django.contrib.auth.models import AnonymousUser
+from django.test import TestCase, RequestFactory
+
+from djangoldp.permissions import AnonymousReadOnly
+from djangoldp.tests.models import JobOffer
+from djangoldp.views import LDPViewSet
+
+
+class TestAnonymousUserPermissions(TestCase):
+    def setUp(self):
+        self.factory = RequestFactory()
+        #        self.c = Client()
+        self.user = AnonymousUser
+
+    def test_get_request_with_anonymousUser(self):
+        request = self.factory.get("/job-offers/")
+        request.user = self.user
+        my_view = LDPViewSet.as_view({'get': 'list'},
+                                     model=JobOffer,
+                                     nested_fields=["skills"],
+                                     permission_classes=[AnonymousReadOnly])
+        response = my_view(request)
+        self.assertEqual(response.status_code, 200)
+
+    def test_request_options_create_with_anonymousUser(self):
+        request = self.factory.options("/job-offers/")
+        request.user = self.user
+        my_view = LDPViewSet.as_view({'options': 'create'},
+                                     model=JobOffer,
+                                     nested_fields=["skills"],
+                                     permission_classes=[AnonymousReadOnly])
+        response = my_view(request)
+        self.assertEqual(response.status_code, 403)
+
+    def test_request_options_update_with_anonymousUser(self):
+        request = self.factory.options("/job-offers/")
+        request.user = self.user
+        my_view = LDPViewSet.as_view({'options': 'update'},
+                                     model=JobOffer,
+                                     nested_fields=["skills"],
+                                     permission_classes=[AnonymousReadOnly])
+        response = my_view(request)
+        self.assertEqual(response.status_code, 403)
diff --git a/djangoldp/tests/tests_ldp_model.py b/djangoldp/tests/tests_ldp_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..6400378cee8383cd39bf1fc0ff30d5cc45204630
--- /dev/null
+++ b/djangoldp/tests/tests_ldp_model.py
@@ -0,0 +1,39 @@
+import unittest
+
+from django.test import TestCase
+
+from djangoldp.models import Model
+from djangoldp.tests.models import Dummy, LDPDummy
+
+
+class LDPModelTest(TestCase):
+
+    def test_class_not_inheriting_ldp_model(self):
+        dummy = Dummy.objects.create(some="text")
+        self.assertEquals("/dummys/", Model.container_id(dummy))
+        self.assertEquals("/dummys/{}".format(dummy.pk), Model.resource_id(dummy))
+
+    def test_class_inheriting_ldp_model(self):
+        dummy = LDPDummy.objects.create(some="text")
+        self.assertEquals("/ldpdummys/", dummy.get_container_id())
+        self.assertEquals("/ldpdummys/{}".format(dummy.pk), dummy.get_absolute_url())
+        self.assertEquals("/ldpdummys/", Model.container_id(dummy))
+        self.assertEquals("/ldpdummys/{}".format(dummy.pk), Model.resource_id(dummy))
+
+    def test_from_resolve_id(self):
+        saved_instance = Dummy.objects.create(some="text")
+        result = Model.resolve_id("/dummys/{}".format(saved_instance.pk))
+        self.assertEquals(saved_instance, result)
+
+    def test_resolve_container(self):
+        result = Model.resolve_container("/dummys/")
+        self.assertEquals(Dummy, result)
+
+    @unittest.skip("futur feature: avoid urls.py on apps")
+    def test_auto_url(self):
+        from django.urls import get_resolver
+        dummy = LDPDummy.objects.create(some="text")
+        view_name = '{}-list'.format(dummy._meta.object_name.lower())
+        path = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][0], dummy.pk)
+
+        self.assertEquals(path, dummy.get_absolute_url())
diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e05496ab21ab8aff4e639bd6d80dcc5c8873ae5
--- /dev/null
+++ b/djangoldp/tests/tests_save.py
@@ -0,0 +1,49 @@
+from django.test import TestCase
+
+from djangoldp.serializers import LDPSerializer
+from djangoldp.tests.models import Skill, JobOffer
+
+
+class Save(TestCase):
+
+    def test_save_m2m(self):
+        skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
+        skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
+
+        job = {"title": "job test",
+               "skills": {
+                   "ldp:contains": [
+                       {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)},
+                       {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk), "title": "skill2 UP"},
+                   ]}
+               }
+
+        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=job)
+        serializer.is_valid()
+        result = serializer.save()
+
+        self.assertEquals(result.title, "job test")
+        self.assertIs(result.skills.count(), 2)
+        self.assertEquals(result.skills.all()[0].title, "skill1")     # no change
+        self.assertEquals(result.skills.all()[1].title, "skill2 UP")  # title updated
+
+    def test_save_without_nested_fields(self):
+        skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
+        skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
+        job = {"title": "job test"}
+
+        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=job)
+        serializer.is_valid()
+        result = serializer.save()
+
+        self.assertEquals(result.title, "job test")
+        self.assertIs(result.skills.count(), 0)
+
diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..39083e2038e831663e52137251f5cd9fec1fdfce
--- /dev/null
+++ b/djangoldp/tests/tests_update.py
@@ -0,0 +1,252 @@
+from django.contrib.auth.models import User
+from django.test import TestCase
+
+from djangoldp.serializers import LDPSerializer
+from djangoldp.tests.models import Skill, JobOffer, Thread, Message
+
+
+class Update(TestCase):
+
+    def test_update(self):
+        skill = Skill.objects.create(title="to drop", obligatoire="obligatoire")
+        skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
+        skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
+        job1 = JobOffer.objects.create(title="job test")
+        job1.skills.add(skill)
+
+        job = {"@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk),
+               "title": "job test updated",
+               "skills": {
+                   "ldp:contains": [
+                       {"title": "new skill", "obligatoire": "okay"},
+                       {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)},
+                       {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk), "title": "skill2 UP"},
+                   ]}
+               }
+
+        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=job, instance=job1)
+        serializer.is_valid()
+        result = serializer.save()
+
+        self.assertEquals(result.title, "job test updated")
+        self.assertIs(result.skills.count(), 3)
+        skills = result.skills.all().order_by('title')
+        self.assertEquals(skills[0].title, "new skill")  # new skill
+        self.assertEquals(skills[1].title, "skill1")  # no change
+        self.assertEquals(skills[2].title, "skill2 UP")  # title updated
+
+    def test_update_graph(self):
+        skill = Skill.objects.create(title="to drop", obligatoire="obligatoire")
+        skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
+        skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
+        job1 = JobOffer.objects.create(title="job test")
+        job1.skills.add(skill)
+
+        job = {"@graph":
+            [
+                {
+                    "@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk),
+                    "title": "job test updated",
+                    "skills": {
+                        "ldp:contains": [
+                            {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)},
+                            {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk)},
+                            {"@id": "_.123"},
+                        ]}
+                },
+                {
+                    "@id": "_.123",
+                    "title": "new skill",
+                    "obligatoire": "okay"
+                },
+                {
+                    "@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk),
+                },
+                {
+                    "@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk),
+                    "title": "skill2 UP"
+                }
+            ]
+        }
+
+        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=job, instance=job1)
+        serializer.is_valid()
+        result = serializer.save()
+
+        skills = result.skills.all().order_by('title')
+
+        self.assertEquals(result.title, "job test updated")
+        self.assertIs(result.skills.count(), 3)
+        self.assertEquals(skills[0].title, "new skill")  # new skill
+        self.assertEquals(skills[1].title, "skill1")  # no change
+        self.assertEquals(skills[2].title, "skill2 UP")  # title updated
+
+    def test_update_graph_2(self):
+        skill = Skill.objects.create(title="to drop", obligatoire="obligatoire")
+        skill1 = Skill.objects.create(title="skill1", obligatoire="obligatoire")
+        skill2 = Skill.objects.create(title="skill2", obligatoire="obligatoire")
+        job1 = JobOffer.objects.create(title="job test")
+        job1.skills.add(skill)
+
+        job = {"@graph":
+            [
+                {
+                    "@id": "https://happy-dev.fr/job-offers/{}/".format(job1.pk),
+                    "title": "job test updated",
+                    "skills": {
+                        "@id": "https://happy-dev.fr/job-offers/{}/skills/".format(job1.pk)
+                    }
+                },
+                {
+                    "@id": "_.123",
+                    "title": "new skill",
+                    "obligatoire": "okay"
+                },
+                {
+                    "@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk),
+                },
+                {
+                    "@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk),
+                    "title": "skill2 UP"
+                },
+                {
+                    '@id': "https://happy-dev.fr/job-offers/{}/skills/".format(job1.pk),
+                    "ldp:contains": [
+                        {"@id": "https://happy-dev.fr/skills/{}/".format(skill1.pk)},
+                        {"@id": "https://happy-dev.fr/skills/{}/".format(skill2.pk)},
+                        {"@id": "_.123"},
+                    ]
+                }
+            ]
+        }
+
+        meta_args = {'model': JobOffer, 'depth': 1, 'fields': ("@id", "title", "skills")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('JobOfferSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=job, instance=job1)
+        serializer.is_valid()
+        result = serializer.save()
+
+        skills = result.skills.all().order_by('title')
+
+        self.assertEquals(result.title, "job test updated")
+        self.assertIs(result.skills.count(), 3)
+        self.assertEquals(skills[0].title, "new skill")  # new skill
+        self.assertEquals(skills[1].title, "skill1")  # no change
+        self.assertEquals(skills[2].title, "skill2 UP")  # title updated
+        self.assertEquals(skill, skill._meta.model.objects.get(pk=skill.pk))  # title updated
+
+    def test_update_list_with_reverse_relation(self):
+        user1 = User.objects.create()
+        thread = Thread.objects.create(description="Thread 1", author_user=user1)
+        message1 = Message.objects.create(text="Message 1", thread=thread, author_user=user1)
+        message2 = Message.objects.create(text="Message 2", thread=thread, author_user=user1)
+
+        json = {"@graph": [
+            {
+                "@id": "https://happy-dev.fr/messages/{}/".format(message1.pk),
+                "text": "Message 1 UP"
+            },
+            {
+                "@id": "https://happy-dev.fr/messages/{}/".format(message2.pk),
+                "text": "Message 2 UP"
+            },
+            {
+                '@id': "https://happy-dev.fr/threads/{}/".format(thread.pk),
+                'description': "Thread 1 UP",
+                "message_set": [
+                    {"@id": "https://happy-dev.fr/messages/{}/".format(message1.pk)},
+                    {"@id": "https://happy-dev.fr/messages/{}/".format(message2.pk)},
+                ]
+            }
+        ]
+        }
+
+        meta_args = {'model': Thread, 'depth': 1, 'fields': ("@id", "description", "message_set")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('ThreadSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=json, instance=thread)
+        serializer.is_valid()
+        result = serializer.save()
+
+        messages = result.message_set.all().order_by('text')
+
+        self.assertEquals(result.description, "Thread 1 UP")
+        self.assertIs(result.message_set.count(), 2)
+        self.assertEquals(messages[0].text, "Message 1 UP")
+        self.assertEquals(messages[1].text, "Message 2 UP")
+
+    def test_add_new_element_with_foreign_key_id(self):
+        user1 = User.objects.create()
+        thread = Thread.objects.create(description="Thread 1", author_user=user1)
+        message1 = Message.objects.create(text="Message 1", thread=thread, author_user=user1)
+        message2 = Message.objects.create(text="Message 2", thread=thread, author_user=user1)
+
+        json = {"@graph": [
+            {
+                "@id": "https://happy-dev.fr/messages/{}/".format(message1.pk),
+                "text": "Message 1 UP",
+                "author_user": {
+                    '@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
+                }
+            },
+            {
+                "@id": "https://happy-dev.fr/messages/{}/".format(message2.pk),
+                "text": "Message 2 UP",
+                "author_user": {
+                    '@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
+                }
+            },
+            {
+                "@id": "_:b1",
+                "text": "Message 3 NEW",
+                "author_user": {
+                    '@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
+                }
+            },
+            {
+                '@id': "https://happy-dev.fr/threads/{}".format(thread.pk),
+                "author_user": {
+                    '@id': "https://happy-dev.fr/users/{}/".format(user1.pk)
+                },
+                'description': "Thread 1 UP",
+                'message_set': {
+                    "@id": "https://happy-dev.fr/threads/{}/message_set".format(thread.pk)
+                }
+            },
+            {
+                '@id': "https://happy-dev.fr/threads/{}/message_set".format(thread.pk),
+                "ldp:contains": [
+                    {"@id": "https://happy-dev.fr/messages/{}/".format(message1.pk)},
+                    {"@id": "https://happy-dev.fr/messages/{}/".format(message2.pk)},
+                    {"@id": "_:b1"}
+                ]
+            }
+        ]
+        }
+
+        meta_args = {'model': Thread, 'depth': 1, 'fields': ("@id", "description", "message_set")}
+
+        meta_class = type('Meta', (), meta_args)
+        serializer_class = type(LDPSerializer)('ThreadSerializer', (LDPSerializer,), {'Meta': meta_class})
+        serializer = serializer_class(data=json, instance=thread)
+        serializer.is_valid()
+        result = serializer.save()
+
+        messages = result.message_set.all().order_by('text')
+
+        self.assertEquals(result.description, "Thread 1 UP")
+        self.assertIs(result.message_set.count(), 3)
+        self.assertEquals(messages[0].text, "Message 1 UP")
+        self.assertEquals(messages[1].text, "Message 2 UP")
+        self.assertEquals(messages[2].text, "Message 3 NEW")
diff --git a/djangoldp/tests/tests_user_permissions.py b/djangoldp/tests/tests_user_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ad30ddd3af62424ec42db1e544fe425ef32f9db
--- /dev/null
+++ b/djangoldp/tests/tests_user_permissions.py
@@ -0,0 +1,40 @@
+from django.contrib.auth.models import User
+from django.test import TestCase, RequestFactory
+
+from djangoldp.permissions import AnonymousReadOnly
+from djangoldp.tests.models import JobOffer
+from djangoldp.views import LDPViewSet
+
+
+class TestUserPermissions(TestCase):
+    def setUp(self):
+        self.factory = RequestFactory()
+        #        self.c = Client()
+        self.user = User.objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion')
+
+    def tearDown(self):
+        self.user.delete()
+
+    def test_get_with_user(self):
+        request = self.factory.get('/job-offers/')
+        request.user = self.user
+        my_view = LDPViewSet.as_view({'get': 'list'}, model=JobOffer, nested_fields=["skills"],
+                                     permission_classes=[AnonymousReadOnly])
+        response = my_view(request)
+        self.assertEqual(response.status_code, 200)
+
+    def test_request_options_create_with_user(self):
+        request = self.factory.options('/job-offers/')
+        request.user = self.user
+        my_view = LDPViewSet.as_view({'options': 'create'}, model=JobOffer, nested_fields=["skills"],
+                                     permission_classes=[AnonymousReadOnly])
+        response = my_view(request)
+        self.assertEqual(response.status_code, 201)
+
+    def test_request_options_update_with_user(self):
+        request = self.factory.options('/job-offers/')
+        request.user = self.user
+        my_view = LDPViewSet.as_view({'options': 'update'}, model=JobOffer, nested_fields=["skills"],
+                                     permission_classes=[AnonymousReadOnly])
+        response = my_view(request)
+        self.assertEqual(response.status_code, 201)
diff --git a/djangoldp/tests/urls.py b/djangoldp/tests/urls.py
index 5982784daeaeaf8c7b1f8e59f784632b7ae95cbe..392b0e2b7764a2fe37fa87e1b47f3ea9916f66f7 100644
--- a/djangoldp/tests/urls.py
+++ b/djangoldp/tests/urls.py
@@ -1,4 +1,6 @@
-from djangoldp.tests.models import Skill, JobOffer
+from django.conf import settings
+
+from djangoldp.tests.models import Skill, JobOffer, Message, Thread, Dummy, LDPDummy
 from djangoldp.views import LDPViewSet
 from django.conf.urls import url
 
@@ -6,4 +8,9 @@ from django.conf.urls import url
 urlpatterns = [
     url(r'^skills/', LDPViewSet.urls(model=Skill, permission_classes=[], fields=["@id", "title"], nested_fields=[])),
     url(r'^job-offers/', LDPViewSet.urls(model=JobOffer, nested_fields=["skills"], permission_classes=())),
+    url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[], fields=["@id", "text"], nested_fields=[])),
+    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=[])),
+    url(r'^ldp-dummys/', LDPViewSet.urls(model=LDPDummy, permission_classes=[])),
 ]
\ No newline at end of file
diff --git a/djangoldp/views.py b/djangoldp/views.py
index 9f38b07954063deaab01ec59ac33e37f3c306a17..f4b2fb59f72c56b898174c6329c7b8f8ee606393 100644
--- a/djangoldp/views.py
+++ b/djangoldp/views.py
@@ -89,9 +89,10 @@ class LDPViewSet(LDPViewSetGenerator):
     """An automatically generated viewset that serves models following the Linked Data Platform convention"""
     fields = None
     exclude = None
-    depth = 0
+    depth = 1
+    many_depth = 0
     renderer_classes = (JSONLDRenderer,)
-    parser_classes =(JSONLDParser,)
+    parser_classes = (JSONLDParser,)
     authentication_classes = (NoCSRFAuthentication,)
 
     def __init__(self, **kwargs):
@@ -106,7 +107,9 @@ class LDPViewSet(LDPViewSetGenerator):
     def build_serializer(self):
         model_name = self.model._meta.object_name.lower()
         lookup_field = get_resolver().reverse_dict[model_name + '-detail'][0][0][1][0]
-        meta_args = {'model': self.model, 'extra_kwargs': {'@id': {'lookup_field': lookup_field}}, 'depth': self.depth,
+        meta_args = {'model': self.model, 'extra_kwargs': {
+            '@id': {'lookup_field': lookup_field}},
+                     'depth': self.depth,
                      'extra_fields': self.nested_fields}
         if self.fields:
             meta_args['fields'] = self.fields
@@ -135,12 +138,16 @@ class LDPViewSet(LDPViewSetGenerator):
         response["Access-Control-Allow-Headers"] = "Content-Type, if-match"
         response["Access-Control-Allow-Credentials"] = 'true'
         response["Accept-Post"] = "application/ld+json"
+        if response.status_code == 201 and '@id' in response.data:
+            response["Location"] = response.data['@id']
+        response["Accept-Post"] = "application/ld+json"
         return response
 
     def update(self, request, *args, **kwargs):
         response = super().update(request, *args, **kwargs)
         return response
 
+
 class LDPNestedViewSet(LDPViewSet):
     """A special case of LDPViewSet serving objects of a relation of a given object (e.g. members of a group, or skills of a user)"""
     parent_model = None