diff --git a/README.md b/README.md
index 404d022e5f0b79fbcb214b88ca00380694cc6409..60727dd88b68126e1975b6ca870b918dfb1bf804 100644
--- a/README.md
+++ b/README.md
@@ -146,6 +146,28 @@ To start the server, `cd` to the root of your Django project and run :
 $ python3 manage.py runserver
 ```
 
+## Using DjangoLDP
+
+### Models
+
+To use DjangoLDP in your models you just need to extend djangoldp.Model
+
+If you define a Meta for your Model, you will [need to explicitly inherit Model.Meta](https://docs.djangoproject.com/fr/2.2/topics/db/models/#meta-inheritance) in order to inherit the default settings, e.g. `default_permissions`
+
+```python
+from djangoldp.models import Model, LDPMetaMixin
+
+class Todo(Model):
+    name = models.CharField(max_length=255)
+
+    class Meta(Model.Meta):
+```
+
+
+See "Custom Meta options" below to see some helpful ways you can tweak the behaviour of DjangoLDP
+
+Your model will be automatically detected and registered with an LDPViewSet and corresponding URLs, as well as being registered with the Django admin panel. If you register your model with the admin panel manually, make sure to extend the GuardedModelAdmin so that the model is registered with [Django-Guardian object permissions](https://django-guardian.readthedocs.io/en/stable/userguide/admin-integration.html)
+
 ## Custom Parameters to LDPViewSet
 
 ### lookup_field
@@ -184,6 +206,10 @@ class MyModel(models.Model):
 
 Now when an instance of `MyModel` is saved, its `author_user` property will be set to the current user. 
 
+## permissions
+
+Django-Guardian is used by default to support object-level permissions. Custom permissions can be added to your model using this attribute. See the [Django-Guardian documentation](https://django-guardian.readthedocs.io/en/stable/userguide/assign.html) for more information
+
 ## permissions_classes
 
 This allows you to add permissions for anonymous, logged in user, author ... in the url:
@@ -303,6 +329,15 @@ MIDDLEWARE = [
 
 Notice tht it'll redirect only HTTP 200 Code.
 
+## Extending DjangoLDP
+
+### Testing
+
+Packaged with DjangoLDP is a tests module, containing unit tests
+
+You can extend these tests and add your own test cases by following the examples in the code. You can then run your tests with:
+`python -m unittest tests.runner`
+
 ## License
 
 Licence MIT
diff --git a/djangoldp/admin.py b/djangoldp/admin.py
index 2627925705f5bb2cfb378a73eb099ff4779dc565..90c266b308fa65f0dee5e7b0af4de001e356cfae 100644
--- a/djangoldp/admin.py
+++ b/djangoldp/admin.py
@@ -2,6 +2,7 @@ from importlib import import_module
 
 from django.conf import settings
 from django.contrib import admin
+from guardian.admin import GuardedModelAdmin
 from .models import LDPSource, Model
 
 # automatically import selected DjangoLDP packages from settings
@@ -20,9 +21,10 @@ for package in settings.DJANGOLDP_PACKAGES:
 model_classes = {cls.__name__: cls for cls in Model.__subclasses__()}
 
 # automatically register models with the admin panel (which have not been added manually)
+# NOTE: by default the models are registered with Django Guardian activated
 for class_name in model_classes:
     model_class = model_classes[class_name]
     if not admin.site.is_registered(model_class):
-        admin.site.register(model_class)
+        admin.site.register(model_class, GuardedModelAdmin)
 
 # admin.site.register(LDPSource)
diff --git a/djangoldp/migrations/0004_auto_20200221_1118.py b/djangoldp/migrations/0004_auto_20200221_1118.py
new file mode 100644
index 0000000000000000000000000000000000000000..50ffde0323b8d38b9332efb4fc8ca027755a0c49
--- /dev/null
+++ b/djangoldp/migrations/0004_auto_20200221_1118.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2020-02-21 11:18
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('djangoldp', '0003_auto_20190911_0931'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='ldpsource',
+            options={'ordering': ('federation',)},
+        ),
+    ]
diff --git a/djangoldp/migrations/0005_auto_20200221_1127.py b/djangoldp/migrations/0005_auto_20200221_1127.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5e3bec893bdb4581a8ff70f32561ad8508aae08
--- /dev/null
+++ b/djangoldp/migrations/0005_auto_20200221_1127.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2020-02-21 11:27
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('djangoldp', '0004_auto_20200221_1118'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='ldpsource',
+            options={'default_permissions': ('add', 'change', 'delete', 'view', 'control'), 'ordering': ('federation',)},
+        ),
+    ]
diff --git a/djangoldp/models.py b/djangoldp/models.py
index 4aab52042f927839a6cfd3ec1a94595e90c5bbd8..c61278ba3841360858098e870917bf00e970b66b 100644
--- a/djangoldp/models.py
+++ b/djangoldp/models.py
@@ -21,6 +21,7 @@ class Model(models.Model):
 
     @classmethod
     def get_view_set(cls):
+        '''returns the view_set defined in the model Meta or the LDPViewSet class'''
         view_set = getattr(cls._meta, 'view_set', getattr(cls.Meta, 'view_set', None))
         if view_set is None:
             from djangoldp.views import LDPViewSet
@@ -29,6 +30,7 @@ class Model(models.Model):
 
     @classmethod
     def get_container_path(cls):
+        '''returns the url path which is used to access actions on this model (e.g. /users/)'''
         path = getattr(cls._meta, 'container_path', getattr(cls.Meta, 'container_path', None))
         if path is None:
             path = "{}s".format(cls._meta.object_name.lower())
@@ -123,6 +125,7 @@ class Model(models.Model):
 
     @classonlymethod
     def __clean_path(cls, path):
+        '''ensures path is Django-friendly'''
         if not path.startswith("/"):
             path = "/{}".format(path)
         if not path.endswith("/"):
@@ -131,10 +134,12 @@ class Model(models.Model):
 
     @classonlymethod
     def get_permission_classes(cls, related_model, default_permissions_classes):
+        '''returns the permission_classes set in the models Meta class'''
         return cls.get_meta(related_model, 'permission_classes', default_permissions_classes)
 
     @classonlymethod
     def get_meta(cls, model_class, meta_name, default=None):
+        '''returns the models Meta class'''
         if hasattr(model_class, 'Meta'):
             meta = getattr(model_class.Meta, meta_name, default)
         else:
@@ -150,6 +155,7 @@ class Model(models.Model):
 
     @classmethod
     def is_external(cls, value):
+        '''returns True if the urlid of the value passed is from an external source'''
         try:
             return value.urlid is not None and not value.urlid.startswith(settings.SITE_URL)
         except:
@@ -159,15 +165,11 @@ class Model(models.Model):
 class LDPSource(Model):
     federation = models.CharField(max_length=255)
 
-    class Meta:
+    class Meta(Model.Meta):
         rdf_type = 'ldp:Container'
         ordering = ('federation',)
         container_path = 'sources'
         lookup_field = 'federation'
-        permissions = (
-            ('view_source', 'acl:Read'),
-            ('control_source', 'acl:Control'),
-        )
 
     def __str__(self):
         return "{}: {}".format(self.federation, self.urlid)
@@ -189,6 +191,7 @@ if 'djangoldp_account' not in settings.DJANGOLDP_PACKAGES:
         # hack : We user webid as username for external user (since it's an uniq identifier too)
         if validators.url(self.username):
             webid = self.username
+        # unable to use username, so use user-detail URL with primary key
         else:
             webid = '{0}{1}'.format(settings.BASE_URL, reverse_lazy('user-detail', kwargs={'pk': self.pk}))
         return webid
diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py
index b3c61c8ffbc1b9ac49d70e27c93a1bf22c53d27d..30c2a340e94e47d778dbee9f453b7faad2a2eed8 100644
--- a/djangoldp/permissions.py
+++ b/djangoldp/permissions.py
@@ -1,14 +1,15 @@
 from django.core.exceptions import PermissionDenied
 from django.db.models.base import ModelBase
-from rest_framework.permissions import BasePermission
+from rest_framework.permissions import DjangoObjectPermissions
+from guardian.shortcuts import get_perms
 
 
-class LDPPermissions(BasePermission):
+class LDPPermissions(DjangoObjectPermissions):
     """
         Default permissions
         Anon: None
-        Auth: None but herit from Anon
-        Ownr: None but herit from Auth
+        Auth: None but inherit from Anon
+        Owner: None but inherit from Auth
     """
     anonymous_perms = ['view']
     authenticated_perms = ['inherit']
@@ -19,7 +20,7 @@ class LDPPermissions(BasePermission):
             Filter user permissions for a model class
         """
 
-        # sorted out param mess
+        # this may be a permission for the model class, or an instance
         if isinstance(obj_or_model, ModelBase):
             model = obj_or_model
         else:
@@ -41,23 +42,39 @@ class LDPPermissions(BasePermission):
         if 'inherit' in owner_perms:
             owner_perms = owner_perms + list(set(authenticated_perms) - set(owner_perms))
 
-        if user.is_anonymous():
-            return anonymous_perms
+        # return permissions
+        # apply Django-Guardian (object-level) permissions
+        perms = []
+
+        if obj is not None and not user.is_anonymous:
+            guardian_perms = get_perms(user, obj)
+            model_name = model._meta.model_name
+
+            # remove model name from the permissions
+            forbidden_string = "_" + model_name
+            perms = [p.replace(forbidden_string, '') for p in guardian_perms]
+
+        # apply anon, owner and auth permissions
+        if user.is_anonymous:
+            perms = perms + anonymous_perms
 
         else:
             if obj and hasattr(model._meta, 'owner_field') and (
                     getattr(obj, getattr(model._meta, 'owner_field')) == user
-                    or getattr(obj, getattr(model._meta, 'owner_field')) == user.urlid
+                    or (hasattr(user, 'urlid') and getattr(obj, getattr(model._meta, 'owner_field')) == user.urlid)
                     or getattr(obj, getattr(model._meta, 'owner_field')) == user.id):
-                return owner_perms
+                perms = perms + owner_perms
 
             else:
-                return authenticated_perms
+                perms = perms + authenticated_perms
+
+        return perms
 
     def filter_user_perms(self, user, obj_or_model, permissions):
         # Only used on Model.get_permissions to translate permissions to LDP
         return [perm for perm in permissions if perm in self.user_permissions(user, obj_or_model)]
 
+    # perms_map defines the permissions required for different methods
     perms_map = {
         'GET': ['%(app_label)s.view_%(model_name)s'],
         'OPTIONS': [],
@@ -100,8 +117,10 @@ class LDPPermissions(BasePermission):
             obj = Model.resolve_id(request._request.path)
             model = view.model
 
+        # get permissions required
         perms = self.get_permissions(request.method, model)
 
+        # compare them with the permissions I have
         for perm in perms:
             if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
                 return False
@@ -119,9 +138,11 @@ class LDPPermissions(BasePermission):
             User have permission on request: Continue
             User does not have permission:   403
         """
+        # get permissions required
         perms = self.get_permissions(request.method, obj)
         model = obj
 
+        # compare them with the permissions I have
         for perm in perms:
             if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
                 return False
diff --git a/djangoldp/tests/djangoldp_urls.py b/djangoldp/tests/djangoldp_urls.py
index 6b0d4a0e8636ff35a8a8e14da65d4ae4e8b7e0b2..c13aad61342fbd5484f0feaf6c5103fc6158123e 100644
--- a/djangoldp/tests/djangoldp_urls.py
+++ b/djangoldp/tests/djangoldp_urls.py
@@ -1,13 +1,15 @@
 from django.conf import settings
 from django.conf.urls import url, include
 
-from djangoldp.tests.models import Skill, JobOffer, Message, Conversation, Dummy, Task
+from djangoldp.permissions import LDPPermissions
+from djangoldp.tests.models import Skill, JobOffer, Message, Conversation, Dummy, PermissionlessDummy, Task
 from djangoldp.views import LDPViewSet
 
 urlpatterns = [
-    url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[], fields=["@id", "text", "conversation"], nested_fields=['conversation'])),
-    url(r'^conversations/', LDPViewSet.urls(model=Conversation, nested_fields=["message_set"], permission_classes=())),
-    url(r'^tasks/', LDPViewSet.urls(model=Task, permission_classes=())),
-    url(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[], lookup_field='slug',)),
+    url(r'^messages/', LDPViewSet.urls(model=Message, permission_classes=[LDPPermissions], fields=["@id", "text", "conversation"], nested_fields=['conversation'])),
+    url(r'^conversations/', LDPViewSet.urls(model=Conversation, nested_fields=["message_set"], permission_classes=[LDPPermissions])),
+    url(r'^tasks/', LDPViewSet.urls(model=Task, permission_classes=[LDPPermissions])),
+    url(r'^dummys/', LDPViewSet.urls(model=Dummy, permission_classes=[LDPPermissions], lookup_field='slug',)),
+    url(r'^permissionless-dummys/', LDPViewSet.urls(model=PermissionlessDummy, permission_classes=[LDPPermissions], lookup_field='slug',)),
 ]
 
diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py
index ede3bec7a3b5b42e6a87045b2655fea196a92977..01f74515ed9bcbbfa35ff1962d969ed4caff5f7f 100644
--- a/djangoldp/tests/models.py
+++ b/djangoldp/tests/models.py
@@ -15,7 +15,7 @@ class Skill(Model):
     def recent_jobs(self):
         return self.joboffer_set.filter(date__gte=date.today())
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
         owner_perms = ['inherit', 'change', 'delete', 'control']
@@ -35,7 +35,7 @@ class JobOffer(Model):
     def some_skill(self):
         return self.skills.all().first()
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'change', 'add']
         owner_perms = ['inherit', 'delete', 'control']
@@ -50,18 +50,19 @@ class Conversation(models.Model):
     author_user = models.ForeignKey(settings.AUTH_USER_MODEL)
     peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_conv")
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
         owner_perms = ['inherit', 'change', 'delete', 'control']
+        owner_field = 'author_user'
 
 
 class Resource(Model):
     joboffers = models.ManyToManyField(JobOffer, blank=True, related_name='resources')
     description = models.CharField(max_length=255)
 
-    class Meta:
-        anonymous_perms = ['view', 'add', 'delete', 'add', 'change', 'control']
+    class Meta(Model.Meta):
+        anonymous_perms = ['view', 'add', 'delete', 'change', 'control']
         authenticated_perms = ['inherit']
         owner_perms = ['inherit']
         serializer_fields = ["@id", "joboffers"]
@@ -73,7 +74,7 @@ class UserProfile(Model):
     description = models.CharField(max_length=255, blank=True, null=True)
     user = models.OneToOneField(settings.AUTH_USER_MODEL)
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit']
         owner_perms = ['inherit', 'change', 'control']
@@ -85,7 +86,7 @@ class Message(models.Model):
     conversation = models.ForeignKey(Conversation, on_delete=models.DO_NOTHING)
     author_user = models.ForeignKey(settings.AUTH_USER_MODEL)
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
         owner_perms = ['inherit', 'change', 'delete', 'control']
@@ -95,7 +96,7 @@ class Dummy(models.Model):
     some = models.CharField(max_length=255, blank=True, null=True)
     slug = models.SlugField(blank=True, null=True, unique=True)
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
         owner_perms = ['inherit', 'change', 'delete', 'control']
@@ -104,17 +105,31 @@ class Dummy(models.Model):
 class LDPDummy(Model):
     some = models.CharField(max_length=255, blank=True, null=True)
 
-    class Meta:
+    class Meta(Model.Meta):
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
         owner_perms = ['inherit', 'change', 'delete', 'control']
 
 
+# model used in django-guardian permission tests (no anonymous etc permissions set)
+class PermissionlessDummy(Model):
+    some = models.CharField(max_length=255, blank=True, null=True)
+    slug = models.SlugField(blank=True, null=True, unique=True)
+
+    class Meta(Model.Meta):
+        anonymous_perms = []
+        authenticated_perms = []
+        owner_perms = []
+        permissions = (
+            ('custom_permission_permissionlessdummy', 'Custom Permission'),
+        )
+
+
 class Invoice(Model):
     title = models.CharField(max_length=255, blank=True, null=True)
     date = models.DateField(blank=True, null=True)
 
-    class Meta:
+    class Meta(Model.Meta):
         depth = 2
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
@@ -126,7 +141,7 @@ class Batch(Model):
     invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='batches')
     title = models.CharField(max_length=255, blank=True, null=True)
 
-    class Meta:
+    class Meta(Model.Meta):
         serializer_fields = ['@id', 'title', 'invoice', 'tasks']
         anonymous_perms = ['view', 'add']
         authenticated_perms = ['inherit', 'add']
@@ -139,7 +154,7 @@ class Task(models.Model):
     batch = models.ForeignKey(Batch, on_delete=models.CASCADE, related_name='tasks')
     title = models.CharField(max_length=255)
 
-    class Meta:
+    class Meta(Model.Meta):
         serializer_fields = ['@id', 'title', 'batch']
         anonymous_perms = ['view']
         authenticated_perms = ['inherit', 'add']
@@ -151,7 +166,7 @@ class Post(Model):
     author = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)
     peer_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="peers_post")
 
-    class Meta:
+    class Meta(Model.Meta):
         auto_author = 'author'
         anonymous_perms = ['view', 'add', 'delete', 'add', 'change', 'control']
         authenticated_perms = ['inherit']
@@ -162,7 +177,7 @@ class Circle(Model):
     description = models.CharField(max_length=255)
     team = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
 
-    class Meta:
+    class Meta(Model.Meta):
         nested_fields = ["team"]
         anonymous_perms = ['view', 'add', 'delete', 'add', 'change', 'control']
         authenticated_perms = ["inherit"]
diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py
index f0fb6b439daeedd5d4d86ef00f815fa6138324a8..7416c711b8f00eea72777b7263f5c478316c760d 100644
--- a/djangoldp/tests/runner.py
+++ b/djangoldp/tests/runner.py
@@ -65,6 +65,7 @@ failures = test_runner.run_tests([
     'djangoldp.tests.tests_ldp_model',
     'djangoldp.tests.tests_save',
     'djangoldp.tests.tests_user_permissions',
+    'djangoldp.tests.tests_guardian',
     'djangoldp.tests.tests_anonymous_permissions',
     'djangoldp.tests.tests_update',
     'djangoldp.tests.tests_auto_author',
@@ -73,7 +74,6 @@ failures = test_runner.run_tests([
     'djangoldp.tests.tests_sources',
     'djangoldp.tests.tests_pagination',
     # 'djangoldp.tests.tests_temp'
-
 ])
 if failures:
     sys.exit(failures)
diff --git a/djangoldp/tests/tests_guardian.py b/djangoldp/tests/tests_guardian.py
new file mode 100644
index 0000000000000000000000000000000000000000..04bc2bb0dea81ef187194ba337d605131ae4e995
--- /dev/null
+++ b/djangoldp/tests/tests_guardian.py
@@ -0,0 +1,92 @@
+import json
+from django.contrib.auth import get_user_model
+from rest_framework.test import APIClient, APITestCase
+from guardian.shortcuts import assign_perm
+
+from .models import PermissionlessDummy
+from djangoldp.permissions import LDPPermissions
+
+
+class TestsGuardian(APITestCase):
+
+    def setUp(self):
+        self.client = APIClient(enforce_csrf_checks=True)
+
+    def setUpLoggedInUser(self):
+        self.user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com',
+                                                         password='glass onion')
+        self.client.force_authenticate(user=self.user)
+
+    # optional setup for testing PermissionlessDummy model with parameterised perms
+    def setUpGuardianDummyWithPerms(self, perms=[]):
+        self.dummy = PermissionlessDummy.objects.create(some='test', slug='test')
+        model_name = PermissionlessDummy._meta.model_name
+
+        for perm in perms:
+            assign_perm(perm + '_' + model_name, self.user, self.dummy)
+
+    # test that dummy with no permissions set returns no results
+    def test_get_dummy_no_permissions(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms()
+        response = self.client.get('/permissionless-dummys/{}/'.format(self.dummy.slug))
+        self.assertEqual(response.status_code, 403)
+
+    # test with anonymous user
+    def test_get_dummy_anonymous_user(self):
+        self.setUpGuardianDummyWithPerms()
+        response = self.client.get('/permissionless-dummys/')
+        self.assertEqual(response.status_code, 403)
+
+    # tests that dummy with permissions set enforces these permissions
+    def test_list_dummy_permission_granted(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['view'])
+        response = self.client.get('/permissionless-dummys/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_get_dummy_permission_granted(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['view'])
+        response = self.client.get('/permissionless-dummys/{}/'.format(self.dummy.slug))
+        self.assertEqual(response.status_code, 200)
+
+    def test_get_dummy_permission_rejected(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['view'])
+        dummy_without = PermissionlessDummy.objects.create(some='test2', slug='test2')
+        response = self.client.get('/permissionless-dummys/{}/'.format(dummy_without.slug))
+        self.assertEqual(response.status_code, 403)
+
+    def test_post_dummy_permission_granted(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['add'])
+        post = {'some': "some_new", "slug": 'slug1'}
+        response = self.client.post('/permissionless-dummys/', data=json.dumps(post), content_type='application/ld+json')
+        self.assertEqual(response.status_code, 201)
+
+    def test_patch_dummy_permission_granted(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['change'])
+        body = {'some': "some_new"}
+        response = self.client.patch('/permissionless-dummys/{}/'.format(self.dummy.slug), data=json.dumps(body),
+                                   content_type='application/ld+json')
+        self.assertEqual(response.status_code, 200)
+
+    def test_patch_dummy_permission_rejected(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['change'])
+        dummy_without = PermissionlessDummy.objects.create(some='test2', slug='test2')
+        body = {'some': "some_new"}
+        response = self.client.patch('/permissionless-dummys/{}/'.format(dummy_without.slug), data=json.dumps(body),
+                                   content_type='application/ld+json')
+        self.assertEqual(response.status_code, 403)
+
+    # test that custom permissions are returned on a model
+    def test_custom_permissions(self):
+        self.setUpLoggedInUser()
+        self.setUpGuardianDummyWithPerms(['custom_permission'])
+
+        permissions = LDPPermissions()
+        result = permissions.user_permissions(self.user, self.dummy)
+        self.assertIn('custom_permission', result)
diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py
index ebb914a3a9577246474d025e6d7885c43150e3de..ae35ebe2235cf4ce0f2e32a88d86d905d39a20ee 100644
--- a/djangoldp/tests/tests_update.py
+++ b/djangoldp/tests/tests_update.py
@@ -13,6 +13,9 @@ class Update(TestCase):
     def setUp(self):
         self.factory = APIRequestFactory()
         self.client = APIClient()
+        self.user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com',
+                                                         password='glass onion')
+        self.client.force_authenticate(user=self.user)
 
     def tearDown(self):
         pass
@@ -340,9 +343,8 @@ class Update(TestCase):
         self.assertIn('conversation_set', response.data)
 
     def test_missing_field_should_not_be_removed_with_fk_relation(self):
-        user = get_user_model().objects.create(username="alex", password="test")
         peer = get_user_model().objects.create(username="sylvain", password="test2")
-        conversation = Conversation.objects.create(author_user=user, peer_user=peer,
+        conversation = Conversation.objects.create(author_user=self.user, peer_user=peer,
                                                    description="conversation description")
         body = [
             {
@@ -356,9 +358,8 @@ class Update(TestCase):
         self.assertIn('peer_user', response.data)
 
     def test_empty_field_should_be_removed_with_fk_relation(self):
-        user = get_user_model().objects.create(username="alex", password="test")
         peer = get_user_model().objects.create(username="sylvain", password="test2")
-        conversation = Conversation.objects.create(author_user=user, peer_user=peer,
+        conversation = Conversation.objects.create(author_user=self.user, peer_user=peer,
                                                    description="conversation description")
         body = [
             {
@@ -485,15 +486,14 @@ class Update(TestCase):
         self.assertEqual(response.data['joboffers']['ldp:contains'][0]['title'], "first title")
 
     def test_update_with_new_fk_relation(self):
-        user = get_user_model().objects.create(username="alex", password="test")
-        conversation = Conversation.objects.create(author_user=user,
+        conversation = Conversation.objects.create(author_user=self.user,
                                                    description="conversation description")
         body = [
             {
                 '@id': "/conversations/{}/".format(conversation.pk),
                 'http://happy-dev.fr/owl/#description': "conversation update",
                 'http://happy-dev.fr/owl/#peer_user': {
-                    '@id': 'http://happy-dev.fr/users/{}'.format(user.pk),
+                    '@id': 'http://happy-dev.fr/users/{}'.format(self.user.pk),
                 }
             }
         ]
@@ -505,7 +505,7 @@ class Update(TestCase):
         conversation = Conversation.objects.get(pk=conversation.pk)
         self.assertIsNotNone(conversation.peer_user)
 
-        user = get_user_model().objects.get(pk=user.pk)
+        user = get_user_model().objects.get(pk=self.user.pk)
         self.assertEqual(user.peers_conv.count(), 1)
 
     def test_m2m_user_link_federated(self):
diff --git a/djangoldp/tests/tests_user_permissions.py b/djangoldp/tests/tests_user_permissions.py
index 96ae9673c256db19f0d672ab1439716887320579..f7c17f164f1ae2a20e614fbd7f2a7d2a6542e426 100644
--- a/djangoldp/tests/tests_user_permissions.py
+++ b/djangoldp/tests/tests_user_permissions.py
@@ -1,8 +1,9 @@
 from django.contrib.auth import get_user_model
 from rest_framework.test import APIClient, APITestCase
+from guardian.shortcuts import assign_perm
 
 from djangoldp.permissions import LDPPermissions
-from .models import JobOffer
+from .models import JobOffer, PermissionlessDummy
 from djangoldp.views import LDPViewSet
 
 import json
@@ -10,9 +11,9 @@ import json
 class TestUserPermissions(APITestCase):
 
     def setUp(self):
-        user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion')
+        self.user = get_user_model().objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion')
         self.client = APIClient(enforce_csrf_checks=True)
-        self.client.force_authenticate(user=user)
+        self.client.force_authenticate(user=self.user)
         self.job = JobOffer.objects.create(title="job", slug="slug1")
 
     def test_get_for_authenticated_user(self):
diff --git a/djangoldp/urls.py b/djangoldp/urls.py
index 39ff5152ae0b1e753007b35dfa6612864e17945e..4a9fb6d4c5d7d24c5ea31ff4f0ff9529b2de695b 100644
--- a/djangoldp/urls.py
+++ b/djangoldp/urls.py
@@ -10,6 +10,7 @@ from djangoldp.views import LDPViewSet
 
 
 def __clean_path(path):
+    '''ensures path is Django-friendly'''
     if path.startswith("/"):
         path = path[1:]
     if not path.endswith("/"):
@@ -29,11 +30,15 @@ for package in settings.DJANGOLDP_PACKAGES:
     except ModuleNotFoundError:
         pass
 
+# fetch a list of all models which subclass DjangoLDP Model
 model_classes = {cls.__name__: cls for cls in Model.__subclasses__()}
 
+# append urls for all DjangoLDP Model subclasses
 for class_name in model_classes:
     model_class = model_classes[class_name]
+    # the path is the url for this model
     path = __clean_path(model_class.get_container_path())
+    # urls_fct will be a method which generates urls for a ViewSet (defined in LDPViewSet)
     urls_fct = model_class.get_view_set().urls
     urlpatterns.append(url(r'^' + path, include(
         urls_fct(model=model_class,