From c07319b0655b0fc2888cc1fea5186dbfd330693f Mon Sep 17 00:00:00 2001
From: Benoit Alessandroni <benoit@startinblox.com>
Date: Fri, 22 May 2020 13:02:08 +0000
Subject: [PATCH] bugfix: Reverting backlinks push

This reverts merge request !125
---
 README.md                                     |  31 +-
 djangoldp/activities/__init__.py              |   3 -
 djangoldp/activities/errors.py                |  10 -
 djangoldp/activities/objects.py               | 200 --------
 djangoldp/activities/services.py              | 389 ---------------
 djangoldp/activities/verbs.py                 | 157 ------
 djangoldp/migrations/0006_activity.py         |  31 --
 .../migrations/0007_auto_20200429_1346.py     |  25 -
 .../migrations/0008_auto_20200501_1207.py     |  25 -
 .../migrations/0009_auto_20200505_1733.py     |  25 -
 djangoldp/migrations/0010_follower.py         |  31 --
 djangoldp/models.py                           |  77 ---
 djangoldp/permissions.py                      |   6 +-
 djangoldp/serializers.py                      |  24 +-
 djangoldp/tests/models.py                     |  37 +-
 djangoldp/tests/runner.py                     |   4 +-
 djangoldp/tests/tests_auto_author.py          |   1 +
 djangoldp/tests/tests_inbox.py                | 453 ------------------
 djangoldp/tests/tests_save.py                 |  10 +-
 djangoldp/tests/tests_update.py               |  36 +-
 djangoldp/urls.py                             |   3 +-
 djangoldp/views.py                            | 188 +-------
 22 files changed, 49 insertions(+), 1717 deletions(-)
 delete mode 100644 djangoldp/activities/__init__.py
 delete mode 100644 djangoldp/activities/errors.py
 delete mode 100644 djangoldp/activities/objects.py
 delete mode 100644 djangoldp/activities/services.py
 delete mode 100644 djangoldp/activities/verbs.py
 delete mode 100644 djangoldp/migrations/0006_activity.py
 delete mode 100644 djangoldp/migrations/0007_auto_20200429_1346.py
 delete mode 100644 djangoldp/migrations/0008_auto_20200501_1207.py
 delete mode 100644 djangoldp/migrations/0009_auto_20200505_1733.py
 delete mode 100644 djangoldp/migrations/0010_follower.py
 delete mode 100644 djangoldp/tests/tests_inbox.py

diff --git a/README.md b/README.md
index e458de9e..e3113b5d 100644
--- a/README.md
+++ b/README.md
@@ -171,8 +171,6 @@ $ python3 manage.py runserver
 
 To use DjangoLDP in your models you just need to extend djangoldp.Model
 
-The Model class allows you to use your models in federation, adding a `urlid` field, and some key methods useful in federation
-
 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
@@ -184,30 +182,7 @@ class Todo(Model):
     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)
-
-### Model Federation
-
-Model `urlid`s can be **local** (matching `settings.SITE_URL`), or **external**
-
-To maintain consistency between federated servers, [Activities](https://www.w3.org/TR/activitystreams-vocabulary) such as Create, Update, Delete are sent to external resources referenced in a ForeignKey relation, instructing them on how to manage the reverse-links with the local server
-
-This behaviour can be disabled in settings.py
-```python
-SEND_BACKLINKS = False
-```
-
-It can also be disabled on a model instance
-```python
-instance.allow_create_backlinks = False
-```
-
-When an instance was created as a reverse-link to an external resource, it is marked with `is_backlink`
-```python
-instance.is_backlink = True
-```
+To enable federation, meaning that local users can access objects from another server as if they were on the local server, DjangoLDP creates backlinks, local copies of the object containing the URL-id (@id) of the distant resource. This is a key concept in LDP. To read more, see the [W3C primer on LDP](https://www.w3.org/TR/ldp-primer/), and the [LDP specification](https://www.w3.org/TR/ldp/)
 
 For situations where you don't want to include federated resources in a queryset, DjangoLDP Models override `models.Manager`, allowing you to write `Todo.objects.local()`, for example:
 ```python
@@ -221,6 +196,10 @@ Todo.objects.local() # { Local Todo } only
 For Views, we also define a FilterBackend to achieve the same purpose. See the section on ViewSets for this purpose
 
 
+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)
+
 ## LDPViewSet
 
 DjangoLDP automatically generates ViewSets for your models, and registers these at urls, according to the settings configured in the model Meta (see below for options)
diff --git a/djangoldp/activities/__init__.py b/djangoldp/activities/__init__.py
deleted file mode 100644
index bf3c4fa7..00000000
--- a/djangoldp/activities/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .objects import *
-from .verbs import *
-from .services import *
diff --git a/djangoldp/activities/errors.py b/djangoldp/activities/errors.py
deleted file mode 100644
index 712cf87d..00000000
--- a/djangoldp/activities/errors.py
+++ /dev/null
@@ -1,10 +0,0 @@
-class ActivityStreamDecodeError(Exception):
-    pass
-
-
-class ActivityStreamTypeError(Exception):
-    pass
-
-
-class ActivityStreamValidationError(Exception):
-    pass
diff --git a/djangoldp/activities/objects.py b/djangoldp/activities/objects.py
deleted file mode 100644
index 96bc963e..00000000
--- a/djangoldp/activities/objects.py
+++ /dev/null
@@ -1,200 +0,0 @@
-import json
-import requests
-from djangoldp.activities import errors
-
-
-class Object(object):
-    attributes = ["type", "id", "name", "to"]
-    type = "Object"
-
-    @classmethod
-    def from_json(cls, json):
-        return Object(**json)
-
-    def __init__(self, obj=None, **kwargs):
-        if obj:
-            self.__init__(**obj.to_activitystream())
-        for key in self.attributes:
-            value = kwargs.get(key)
-            if value is None:
-                continue
-
-            if isinstance(value, dict) and value.get("type"):
-                value = as_activitystream(value)
-            self.__setattr__(key, value)
-
-    def __str__(self):
-        content = json.dumps(self, default=encode_activitystream)
-        return "<{type}: {content}>".format(type=self.type, content=content)
-
-    def to_json(self, context=False):
-        '''
-        converts Object to json
-        context parameter indicates whether @context should be added to the object (for JSON-LD parsing)
-        '''
-        values = {}
-        for attribute in self.attributes:
-            value = getattr(self, attribute, None)
-            if value is None:
-                continue
-            if isinstance(value, Object):
-                value = value.to_json()
-            # if getattr(value, "__iter__", None):
-            #     value = [item.to_json() for item in value]
-            values[attribute] = value
-        to = values.get("to")
-        if isinstance(to, str):
-            values["to"] = [to]
-        elif getattr(to, "__iter__", None):
-            values["to"] = []
-            for item in to:
-                if isinstance(item, str):
-                    values["to"].append(item)
-                if isinstance(item, Object):
-                    values["to"].append(item.id)
-
-        if context:
-            values["@context"] = "https://www.w3.org/ns/activitystreams"
-        return values
-
-    def to_activitystream(self):
-        return self
-
-
-class Actor(Object):
-    attributes = Object.attributes + [
-        "target",
-        "publicKey",
-        "preferredUsername",
-        "following",
-        "followers",
-        "outbox",
-        "inbox",
-    ]
-    type = "Actor"
-
-    def send(self, activity):
-        res = requests.post(self.inbox, json=activity.to_json(context=True))
-        if res.status_code != 200:
-            raise Exception
-
-
-class Person(Actor):
-    type = "Person"
-
-
-class Service(Actor):
-    type = "Service"
-
-
-class Group(Actor):
-    type = "Group"
-
-
-class Collection(Object):
-    attributes = Object.attributes + ["items", "totalItems"]
-    type = "Collection"
-
-    def __init__(self, iterable=None, **kwargs):
-        self._items = []
-
-        Object.__init__(self, **kwargs)
-        if iterable is None:
-            return
-
-        self.items = iterable
-
-    @property
-    def items(self):
-        return self._items
-
-    @items.setter
-    def items(self, iterable):
-        for item in iterable:
-            if isinstance(item, Object):
-                self._items.append(item)
-            elif isinstance(item, str):
-                self._items.append(item)
-            elif getattr(item, "to_activitystream", None):
-                item = as_activitystream(item.to_activitystream())
-                self._items.append(item)
-            else:
-                raise Exception("invalid ActivityStream object: {item}".format(item=item))
-
-    def to_json(self, **kwargs):
-        json = Object.to_json(self, **kwargs)
-        items = [item.to_json() if isinstance(item, Object) else item
-                 for item in self.items]
-        json.update({
-            "items": items
-        })
-        return json
-
-
-class OrderedCollection(Collection):
-    attributes = Object.attributes + ["orderedItems", "totalItems"]
-    type = "OrderedCollection"
-
-    @property
-    def totalItems(self):
-        return len(self.items)
-
-    @totalItems.setter
-    def totalItems(self, value):
-        pass
-
-    @property
-    def orderedItems(self):
-        return self.items
-
-    @orderedItems.setter
-    def orderedItems(self, iterable):
-        self.items = iterable
-
-    def to_json(self, **kwargs):
-        json = Collection.to_json(self, **kwargs)
-        json["orderedItems"] = json["items"]
-        del json["items"]
-        return json
-
-
-class Link(Object):
-    type = 'Link'
-    attributes = ['link']
-
-    def to_json(self, **kwargs):
-        return self.link
-
-
-#########
-# Utils #
-#########
-
-ALLOWED_TYPES = {
-    "Object": Object,
-    "Actor": Actor,
-    "Service": Service,
-    "Person": Person,
-    "Link": Link,
-    "Collection": Collection,
-    "OrderedCollection": OrderedCollection
-}
-
-
-def as_activitystream(obj):
-    type = obj.get("type")
-
-    if type:
-        if type in ALLOWED_TYPES:
-            obj = ALLOWED_TYPES[type](**obj)
-            return obj
-
-        raise errors.ActivityStreamDecodeError("Invalid Type {0}".format(type))
-
-    return obj
-
-
-def encode_activitystream(obj):
-    if isinstance(obj, Object):
-        return obj.to_json()
-    raise errors.ActivityStreamTypeError("Unknown ActivityStream Type")
diff --git a/djangoldp/activities/services.py b/djangoldp/activities/services.py
deleted file mode 100644
index 9bc509c9..00000000
--- a/djangoldp/activities/services.py
+++ /dev/null
@@ -1,389 +0,0 @@
-import threading
-from urllib.parse import urlparse
-from django.conf import settings
-from django.contrib.auth import get_user_model
-from django.db.models.signals import post_save, post_delete, m2m_changed
-from django.dispatch import receiver
-from rest_framework.utils import model_meta
-
-from djangoldp.models import Model, Follower
-
-from .objects import *
-from .verbs import *
-import logging
-
-
-logger = logging.getLogger('djangoldp')
-
-
-class ActivityPubService(object):
-    '''A service for sending ActivityPub notifications'''
-
-    @classmethod
-    def build_object_tree(cls, instance):
-        '''builds an object tree from a parameterised instance'''
-        model = type(instance)
-        info = model_meta.get_field_info(model)
-
-        if not hasattr(instance, 'urlid'):
-            return
-
-        obj = {
-            "@type": Model.get_model_rdf_type(model),
-            "@id": instance.urlid
-        }
-        if obj['@type'] is None:
-            logger.warning('[Backlink-Creation] model ' + str(model) + ' has no rdf_type')
-            return
-
-        # append relations
-        for field_name, relation_info in info.relations.items():
-            if not relation_info.to_many:
-                value = getattr(instance, field_name, None)
-                if value is None:
-                    continue
-
-                sub_object = {
-                    "@id": value.urlid,
-                    "@type": Model.get_model_rdf_type(type(value))
-                }
-
-                if sub_object['@type'] is None:
-                    logger.warning('[Backlink-Creation] model ' + str(type(value)) + ' has no rdf_type')
-                    continue
-
-                obj[field_name] = sub_object
-
-        return obj
-
-    @classmethod
-    def _discover_inbox(cls, target_id):
-        url = urlparse(target_id)
-        return target_id.replace(url.path, "/") + "inbox/"
-
-    @classmethod
-    def send_add_activity(cls, actor, object, target):
-        '''
-        Sends an Add activity
-        :param actor: a valid Actor object, or a user instance
-        :param object: a valid ActivityStreams Object
-        :param target: an object representing the target collection
-        '''
-        # bounds checking
-        if isinstance(actor, get_user_model()):
-            actor = {
-                '@type': 'foaf:user',
-                '@id': actor.urlid
-            }
-
-        summary = str(object['@id']) + " was added to " + str(target['@id'])
-
-        activity = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                settings.LDP_RDF_CONTEXT
-            ],
-            "summary": summary,
-            "type": "Add",
-            "actor": actor,
-            "object": object,
-            "target": target
-        }
-
-        logger.debug('[Sender] sending add activity ' + str(activity))
-
-        inbox = ActivityPubService._discover_inbox(target['@id'])
-
-        # send request
-        t = threading.Thread(target=cls.do_post, args=[inbox, activity])
-        t.start()
-
-    @classmethod
-    def send_remove_activity(cls, actor, object, origin):
-        '''
-        Sends a Remove activity
-        :param actor: a valid Actor object, or a user instance
-        :param object: a valid ActivityStreams Object
-        :param origin: the context the object has been removed from
-        '''
-        # bounds checking
-        if isinstance(actor, get_user_model()):
-            actor = {
-                '@type': 'foaf:user',
-                '@id': actor.urlid
-            }
-
-        summary = str(object['@id']) + " was removed from " + str(origin['@id'])
-
-        activity = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                settings.LDP_RDF_CONTEXT
-            ],
-            "summary": summary,
-            "type": "Remove",
-            "actor": actor,
-            "object": object,
-            "origin": origin
-        }
-
-        logger.debug('[Sender] sending remove activity ' + str(activity))
-
-        inbox = ActivityPubService._discover_inbox(origin['@id'])
-
-        # send request
-        t = threading.Thread(target=cls.do_post, args=[inbox, activity])
-        t.start()
-
-    @classmethod
-    def send_create_activity(cls, actor, object, inbox):
-        '''
-        Sends a Create activity
-        :param actor: a valid Actor object, or a user instance
-        :param object: a valid ActivityStreams Object
-        :param inbox: the inbox to send the activity to
-        '''
-        # bounds checking
-        if isinstance(actor, get_user_model()):
-            actor = {
-                '@type': 'foaf:user',
-                '@id': actor.urlid
-            }
-
-        summary = str(object['@id']) + " was created"
-
-        activity = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                settings.LDP_RDF_CONTEXT
-            ],
-            "summary": summary,
-            "type": "Create",
-            "actor": actor,
-            "object": object
-        }
-
-        logger.debug('[Sender] sending create activity ' + str(activity))
-
-        # send request
-        t = threading.Thread(target=cls.do_post, args=[inbox, activity])
-        t.start()
-
-    @classmethod
-    def send_update_activity(cls, actor, object, inbox):
-        '''
-        Sends an Update activity
-        :param actor: a valid Actor object, or a user instance
-        :param object: a valid ActivityStreams Object
-        :param inbox: the inbox to send the activity to
-        '''
-        # bounds checking
-        if isinstance(actor, get_user_model()):
-            actor = {
-                '@type': 'foaf:user',
-                '@id': actor.urlid
-            }
-
-        summary = str(object['@id']) + " was created"
-
-        activity = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                settings.LDP_RDF_CONTEXT
-            ],
-            "summary": summary,
-            "type": "Update",
-            "actor": actor,
-            "object": object
-        }
-
-        logger.debug('[Sender] sending update activity ' + str(activity))
-
-        # send request
-        t = threading.Thread(target=cls.do_post, args=[inbox, activity])
-        t.start()
-
-    @classmethod
-    def send_delete_activity(cls, actor, object, inbox):
-        '''
-        Sends a Remove activity
-        :param actor: a valid Actor object, or a user instance
-        :param object: a valid ActivityStreams Object
-        :param inbox: the inbox to send the activity to
-        '''
-        # bounds checking
-        if isinstance(actor, get_user_model()):
-            actor = {
-                '@type': 'foaf:user',
-                '@id': actor.urlid
-            }
-
-        summary = str(object['@id']) + " was deleted"
-
-        activity = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                settings.LDP_RDF_CONTEXT
-            ],
-            "summary": summary,
-            "type": "Delete",
-            "actor": actor,
-            "object": object
-        }
-
-        logger.debug('[Sender] sending delete activity ' + str(activity))
-
-        # send request
-        t = threading.Thread(target=cls.do_post, args=[inbox, activity])
-        t.start()
-
-    @classmethod
-    def do_post(cls, url, activity, auth=None):
-        '''makes a POST request to passed url'''
-        headers = {'Content-Type': 'application/ld+json'}
-        response = None
-        try:
-            response = requests.post(url, data=json.dumps(activity), headers=headers)
-            logger.debug('[Sender] sent, receiver responded ' + response.text)
-        except:
-            logger.error('Failed to deliver backlink to ' + str(url) +', was attempting ' + str(activity))
-        return response
-
-
-def _check_instance_for_backlinks(sender, instance):
-    '''Auxiliary function returns a dictionary of backlink targets from paramertised instance'''
-    info = model_meta.get_field_info(sender)
-
-    # bounds checking
-    if not hasattr(instance, 'urlid') or Model.get_model_rdf_type(sender) is None:
-        logger.warning('[Create-Backlink] model ' + str(sender) + ' has no rdf_type')
-        return {}
-
-    # check each foreign key for a distant resource
-    targets = {}
-    for field_name, relation_info in info.relations.items():
-        if not relation_info.to_many:
-            value = getattr(instance, field_name, None)
-            logger.debug('[Sender] model has relation ' + str(value))
-            if value is not None and Model.is_external(value):
-                target_type = Model.get_model_rdf_type(type(value))
-
-                if target_type is None:
-                    logger.warning('[Create-Backlink] model ' + str(type(value)) + ' has no rdf_type')
-                    continue
-
-                targets[value.urlid] = ActivityPubService._discover_inbox(value.urlid)
-
-    # append Followers as targets
-    followers = Follower.objects.filter(object=instance.urlid)
-    for follower in followers:
-        targets[follower.inbox] = follower.inbox
-
-    logger.debug('[Sender] built dict of targets: ' + str(targets))
-    return targets
-
-
-@receiver([post_save])
-def check_save_for_backlinks(sender, instance, created, **kwargs):
-    if getattr(settings, 'SEND_BACKLINKS', True) and not getattr(instance, 'is_backlink', False) \
-            and getattr(instance, 'allow_create_backlink', False)\
-            and getattr(instance, 'username', None) != 'hubl-workaround-493':
-        logger.debug("[Sender] Received created non-backlink instance " + str(instance) + "(" + str(sender) + ")")
-        targets = _check_instance_for_backlinks(sender, instance)
-
-        if len(targets.items()) > 0:
-            obj = ActivityPubService.build_object_tree(instance)
-            actor = {
-                "type": "Service",
-                "name": "Backlinks Service"
-            }
-            # Create Activity
-            if created:
-                for key in targets.keys():
-                    ActivityPubService.send_create_activity(actor, obj, targets[key])
-                    Follower.objects.create(object=obj['@id'], inbox=targets[key])
-            # Update Activity
-            else:
-                for key in targets.keys():
-                    ActivityPubService.send_update_activity(actor, obj, targets[key])
-                    if not Follower.objects.filter(object=obj['@id'], inbox=targets[key]).exists():
-                        Follower.objects.create(object=obj['@id'], inbox=targets[key])
-
-
-@receiver([post_delete])
-def check_delete_for_backlinks(sender, instance, **kwargs):
-    if getattr(settings, 'SEND_BACKLINKS', True) and getattr(instance, 'allow_create_backlink', False) \
-            and getattr(instance, 'username', None) != 'hubl-workaround-493':
-        logger.debug("[Sender] Received deleted non-backlink instance " + str(instance) + "(" + str(sender) + ")")
-        targets = _check_instance_for_backlinks(sender, instance)
-
-        if len(targets.items()) > 0:
-            for key in targets.keys():
-                ActivityPubService.send_delete_activity({
-                    "type": "Service",
-                    "name": "Backlinks Service"
-                }, {
-                    "@id": instance.urlid,
-                    "@type": Model.get_model_rdf_type(sender)
-                }, targets[key])
-
-    # remove any Followers on this resource
-    urlid = getattr(instance, 'urlid', None)
-    if urlid is not None:
-        for follower in Follower.objects.filter(object=urlid):
-            follower.delete()
-
-
-@receiver([m2m_changed])
-def check_m2m_for_backlinks(sender, instance, action, *args, **kwargs):
-    if getattr(settings, 'SEND_BACKLINKS', True):
-        member_model = kwargs['model']
-        pk_set = kwargs['pk_set']
-        if pk_set is None:
-            return
-        member_rdf_type = Model.get_model_rdf_type(member_model)
-        container_rdf_type = Model.get_model_rdf_type(type(instance))
-
-        if member_rdf_type is None:
-            logger.warning('[Backlink-Creation] model ' + str(member_model) + ' has no rdf_type')
-            return
-        if container_rdf_type is None:
-            logger.warning('[Backlink-Creation] model ' + str(type(instance)) + ' has no rdf_type')
-            return
-
-        # build list of targets (models affected by the change)
-        query_set = member_model.objects.filter(pk__in=pk_set)
-        targets = []
-
-        for obj in query_set:
-            condition = Model.is_external(obj) and getattr(obj, 'allow_create_backlink', False)
-            if action == "post_add":
-                condition = condition and not getattr(instance, 'is_backlink', False)
-
-            if condition:
-                targets.append({
-                    "@type": member_rdf_type,
-                    "@id": obj.urlid
-                })
-
-        logger.debug('[Sender] checking many2many for backlinks')
-        logger.debug('[Sender] built targets: ' + str(targets))
-
-        if len(targets) > 0:
-            obj = {
-                "@type": container_rdf_type,
-                "@id": instance.urlid
-            }
-            if action == 'post_add':
-                for target in targets:
-                    ActivityPubService.send_add_activity({
-                        "type": "Service",
-                        "name": "Backlinks Service"
-                    }, obj, target)
-
-            elif action == "post_remove":
-                for target in targets:
-                    ActivityPubService.send_remove_activity({
-                        "type": "Service",
-                        "name": "Backlinks Service"
-                    }, obj, target)
diff --git a/djangoldp/activities/verbs.py b/djangoldp/activities/verbs.py
deleted file mode 100644
index 65bfe09f..00000000
--- a/djangoldp/activities/verbs.py
+++ /dev/null
@@ -1,157 +0,0 @@
-from copy import copy
-
-from djangoldp.activities import errors
-from djangoldp.activities.objects import ALLOWED_TYPES, Object, Actor
-
-
-class Activity(Object):
-    attributes = Object.attributes + ["actor", "object"]
-    type = "Activity"
-
-    def get_audience(self):
-        audience = []
-        for attr in ["to", "bto", "cc", "bcc", "audience"]:
-            value = getattr(self, attr, None)
-            if not value:
-                continue
-
-            if isinstance(value, str):
-                value = [value]
-            audience += value
-        return set(audience)
-
-    def strip_audience(self):
-        new = copy(self)
-        if getattr(new, "bto", None):
-            delattr(new, "bto")
-        if getattr(new, "bcc", None):
-            delattr(new, "bcc")
-        return new
-
-    def validate(self):
-        pass
-
-
-class Add(Activity):
-    type = "Add"
-    attributes = Activity.attributes + ["target"]
-
-    def validate(self):
-        msg = None
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not getattr(self, "target", None):
-            msg = "Invalid activity, target is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-        elif not isinstance(self.target, dict):
-            msg = "Invalid target type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
-
-class Remove(Activity):
-    type = "Remove"
-    attributes = Activity.attributes + ["target", "origin"]
-
-    def validate(self):
-        msg = None
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not getattr(self, "target", None) and not getattr(self, "origin", None):
-            msg = "Invalid activity, no target or origin given"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-
-        if getattr(self, "target", None) is not None:
-            if not isinstance(self.target, dict):
-                msg = "Invalid target type, must be a dict"
-        if getattr(self, "origin", None) is not None:
-            if not isinstance(self.origin, dict):
-                msg = "Invalid origin type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
-
-class Create(Activity):
-    type = "Create"
-
-    def validate(self):
-        msg = None
-
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
-
-class Update(Create):
-    type = "Update"
-
-
-class Delete(Activity):
-    type = "Delete"
-    attributes = Activity.attributes + ["origin"]
-
-    def validate(self):
-        msg = None
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
-
-class Follow(Activity):
-    type = "Follow"
-
-    def validate(self):
-        msg = None
-
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif isinstance(self.actor, Actor) and (self.actor.inbox is None and self.actor.id is None):
-            msg = "Must pass inbox or id with the actor to follow"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
-
-ALLOWED_TYPES.update({
-    "Activity": Activity,
-    "Add": Add,
-    "Remove": Remove,
-    "Create": Create,
-    "Update": Update,
-    "Delete": Delete,
-    "Follow": Follow
-})
diff --git a/djangoldp/migrations/0006_activity.py b/djangoldp/migrations/0006_activity.py
deleted file mode 100644
index f56653f9..00000000
--- a/djangoldp/migrations/0006_activity.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2020-04-21 19:43
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import djangoldp.fields
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('djangoldp', '0005_auto_20200221_1127'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='Activity',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('urlid', djangoldp.fields.LDPUrlField(blank=True, null=True, unique=True)),
-                ('aid', djangoldp.fields.LDPUrlField(null=True)),
-                ('local_id', djangoldp.fields.LDPUrlField()),
-                ('payload', models.BinaryField()),
-                ('created_at', models.DateField(auto_now_add=True)),
-            ],
-            options={
-                'abstract': False,
-                'default_permissions': ('add', 'change', 'delete', 'view', 'control'),
-            },
-        ),
-    ]
diff --git a/djangoldp/migrations/0007_auto_20200429_1346.py b/djangoldp/migrations/0007_auto_20200429_1346.py
deleted file mode 100644
index ddec3b61..00000000
--- a/djangoldp/migrations/0007_auto_20200429_1346.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2020-04-29 13:46
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('djangoldp', '0006_activity'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='activity',
-            name='allow_create_backlink',
-            field=models.BooleanField(default=True, help_text='set to False to disable backlink creation after Model save'),
-        ),
-        migrations.AddField(
-            model_name='ldpsource',
-            name='allow_create_backlink',
-            field=models.BooleanField(default=True, help_text='set to False to disable backlink creation after Model save'),
-        ),
-    ]
diff --git a/djangoldp/migrations/0008_auto_20200501_1207.py b/djangoldp/migrations/0008_auto_20200501_1207.py
deleted file mode 100644
index 9d13b8ef..00000000
--- a/djangoldp/migrations/0008_auto_20200501_1207.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2020-05-01 12:07
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('djangoldp', '0007_auto_20200429_1346'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='activity',
-            name='backlink_created',
-            field=models.BooleanField(default=False, help_text='set automatically to indicate the Model is a backlink'),
-        ),
-        migrations.AddField(
-            model_name='ldpsource',
-            name='backlink_created',
-            field=models.BooleanField(default=False, help_text='set automatically to indicate the Model is a backlink'),
-        ),
-    ]
diff --git a/djangoldp/migrations/0009_auto_20200505_1733.py b/djangoldp/migrations/0009_auto_20200505_1733.py
deleted file mode 100644
index a4a6136a..00000000
--- a/djangoldp/migrations/0009_auto_20200505_1733.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2020-05-05 17:33
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('djangoldp', '0008_auto_20200501_1207'),
-    ]
-
-    operations = [
-        migrations.RenameField(
-            model_name='activity',
-            old_name='backlink_created',
-            new_name='is_backlink',
-        ),
-        migrations.RenameField(
-            model_name='ldpsource',
-            old_name='backlink_created',
-            new_name='is_backlink',
-        ),
-    ]
diff --git a/djangoldp/migrations/0010_follower.py b/djangoldp/migrations/0010_follower.py
deleted file mode 100644
index 72ca4ad1..00000000
--- a/djangoldp/migrations/0010_follower.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2020-05-19 10:37
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import djangoldp.fields
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('djangoldp', '0009_auto_20200505_1733'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='Follower',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('urlid', djangoldp.fields.LDPUrlField(blank=True, null=True, unique=True)),
-                ('is_backlink', models.BooleanField(default=False, help_text='set automatically to indicate the Model is a backlink')),
-                ('allow_create_backlink', models.BooleanField(default=True, help_text='set to False to disable backlink creation after Model save')),
-                ('object', models.URLField()),
-                ('inbox', models.URLField()),
-            ],
-            options={
-                'abstract': False,
-                'default_permissions': ('add', 'change', 'delete', 'view', 'control'),
-            },
-        ),
-    ]
diff --git a/djangoldp/models.py b/djangoldp/models.py
index 13a699ed..f76de19f 100644
--- a/djangoldp/models.py
+++ b/djangoldp/models.py
@@ -1,11 +1,7 @@
-import json
-import uuid
 from urllib.parse import urlparse
 from django.conf import settings
-from django.core.exceptions import ObjectDoesNotExist
 from django.contrib.auth import get_user_model
 from django.db import models
-from django.db.models import BinaryField, DateField
 from django.db.models.base import ModelBase
 from django.db.models.signals import post_save
 from django.dispatch import receiver
@@ -15,10 +11,6 @@ from django.utils.decorators import classonlymethod
 
 from djangoldp.fields import LDPUrlField
 from djangoldp.permissions import LDPPermissions
-import logging
-
-
-logger = logging.getLogger('djangoldp')
 
 
 class LDPModelManager(models.Manager):
@@ -31,9 +23,6 @@ class LDPModelManager(models.Manager):
 
 class Model(models.Model):
     urlid = LDPUrlField(blank=True, null=True, unique=True)
-    is_backlink = models.BooleanField(default=False, help_text='set automatically to indicate the Model is a backlink')
-    allow_create_backlink = models.BooleanField(default=True,
-                                                help_text='set to False to disable backlink creation after Model save')
     objects = LDPModelManager()
 
     def __init__(self, *args, **kwargs):
@@ -152,41 +141,6 @@ class Model(models.Model):
             path = "{}/".format(path)
         return path
 
-    @classonlymethod
-    def get_or_create(cls, model, urlid, update=False, **field_tuples):
-        try:
-            logger.debug('[get_or_create] ' + str(model) + ' backlink ' + str(urlid))
-            rval = model.objects.get(urlid=urlid)
-            if update:
-                for field in field_tuples.keys():
-                    setattr(rval, field, field_tuples[field])
-                rval.save()
-            return rval
-        except ObjectDoesNotExist:
-            logger.debug('[get_or_create] creating..')
-            if model is get_user_model():
-                field_tuples['username'] = str(uuid.uuid4())
-            return model.objects.create(urlid=urlid, is_backlink=True, **field_tuples)
-
-    @classonlymethod
-    def get_model_rdf_type(cls, model):
-        if model is get_user_model():
-            return "foaf:user"
-        else:
-            return Model.get_meta(model, "rdf_type")
-
-    @classonlymethod
-    def get_subclass_with_rdf_type(cls, type):
-        '''returns Model subclass with Meta.rdf_type matching parameterised type, or None'''
-        if type == 'foaf:user':
-            return get_user_model()
-
-        for subcls in Model.__subclasses__():
-            if Model.get_meta(subcls, 'rdf_type') == type:
-                return subcls
-
-        return None
-
     @classonlymethod
     def get_permission_classes(cls, related_model, default_permissions_classes):
         '''returns the permission_classes set in the models Meta class'''
@@ -230,37 +184,6 @@ class LDPSource(Model):
         return "{}: {}".format(self.federation, self.urlid)
 
 
-class Activity(Model):
-    '''Models an ActivityStreams Activity'''
-    aid = LDPUrlField(null=True) # activity id
-    local_id = LDPUrlField()  # /inbox or /outbox full url
-    payload = BinaryField()
-    created_at = DateField(auto_now_add=True)
-
-    class Meta(Model.Meta):
-        container_path = "activities"
-        rdf_type = 'as:Activity'
-
-    def to_activitystream(self):
-        payload = self.payload.decode("utf-8")
-        data = json.loads(payload)
-        return data
-
-
-class Follower(Model):
-    '''Models a subscription on a model. When the model is saved, an Update activity will be sent to the inbox'''
-    object = models.URLField()
-    inbox = models.URLField()
-
-    def __str__(self):
-        return 'Inbox ' + str(self.inbox) + ' on ' + str(self.object)
-
-    def save(self, *args, **kwargs):
-        if self.pk is None:
-            logger.debug('[Follower] saving Follower ' + self.__str__())
-        super(Follower, self).save(*args, **kwargs)
-
-
 @receiver([post_save])
 def auto_urlid(sender, instance, **kwargs):
     if isinstance(instance, Model):
diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py
index dffd104a..2e4ed672 100644
--- a/djangoldp/permissions.py
+++ b/djangoldp/permissions.py
@@ -119,11 +119,10 @@ class LDPPermissions(DjangoObjectPermissions):
 
         # get permissions required
         perms = self.get_permissions(request.method, model)
-        user_perms = self.user_permissions(request.user, model, obj)
 
         # compare them with the permissions I have
         for perm in perms:
-            if not perm.split('.')[1].split('_')[0] in user_perms:
+            if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
                 return False
 
         return True
@@ -142,11 +141,10 @@ class LDPPermissions(DjangoObjectPermissions):
         # get permissions required
         perms = self.get_permissions(request.method, obj)
         model = obj
-        user_perms = self.user_permissions(request.user, model, obj)
 
         # compare them with the permissions I have
         for perm in perms:
-            if not perm.split('.')[1].split('_')[0] in user_perms:
+            if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
                 return False
 
         return True
diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py
index 62543396..65bdc175 100644
--- a/djangoldp/serializers.py
+++ b/djangoldp/serializers.py
@@ -47,10 +47,9 @@ class LDListMixin:
 
         return [getattr(self, self.child_attr).to_internal_value(item) for item in data]
 
+    # converts internal representation to primitive data representation
     def to_representation(self, value):
         '''
-        Converts internal representation to primitive data representation
-        Filters objects out which I don't have permission to view
         Permission on container :
          - Can Add if add permission on contained object's type
          - Can view the container is view permission on container model : container obj are filtered by view permission
@@ -272,6 +271,13 @@ class LDPSerializer(HyperlinkedModelSerializer):
             fields = list(self.Meta.model._meta.serializer_fields)
         except AttributeError:
             fields = super().get_default_field_names(declared_fields, model_info)
+        if 'request' in self._context and not (self._context['request']._request.method == 'GET' or self._context['request']._request.method == 'OPTIONS'):
+            try:
+                fields.remove(self.Meta.model._meta.auto_author)
+            except ValueError:
+                pass
+            except AttributeError:
+                pass
         return fields + list(getattr(self.Meta, 'extra_fields', []))
 
     def to_representation(self, obj):
@@ -410,9 +416,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
             def to_internal_value(self, data):
                 if data is '':
                     return ''
-                # workaround for Hubl app - 293
-                if 'username' in data and not self.url_field_name in data:
-                    data[self.url_field_name] = './'
                 if self.url_field_name in data:
                     if not isinstance(data, Mapping):
                         message = self.error_messages['invalid'].format(
@@ -425,7 +428,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
                     ret = OrderedDict()
                     errors = OrderedDict()
 
-                    # validate fields passed in the data
                     fields = list(filter(lambda x: x.field_name in data, self._writable_fields))
 
                     for field in fields:
@@ -447,7 +449,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
                     if errors:
                         raise ValidationError(errors)
 
-                    # resolve path of the resource
                     uri = data[self.url_field_name]
                     http_prefix = uri.startswith(('http:', 'https:'))
 
@@ -550,7 +551,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
     def internal_create(self, validated_data, model):
         validated_data = self.resolve_fk_instances(model, validated_data)
 
-        # build tuples list of nested_field keys and their values
         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:
@@ -563,9 +563,8 @@ class LDPSerializer(HyperlinkedModelSerializer):
                     field_name in validated_data) and not field_name is None:
                 many_to_many.append((field_name, validated_data.pop(field_name)))
         validated_data = self.remove_empty_value(validated_data)
-
         if model is get_user_model() and not 'username' in validated_data:
-            validated_data['username'] = str(uuid.uuid4())
+            validated_data['username'] = uuid.uuid4()
         instance = model.objects.create(**validated_data)
 
         for field_name, value in many_to_many:
@@ -576,7 +575,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
         return instance
 
     def remove_empty_value(self, validated_data):
-        '''sets any empty strings in the validated_data to None'''
         for attr, value in validated_data.items():
             if value is '':
                 validated_data[attr] = None
@@ -605,7 +603,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
         return instance
 
     def resolve_fk_instances(self, model, validated_data):
-        '''iterates over every dict object in validated_data and resolves them into instances (get or create)'''
         nested_fk_fields_name = list(filter(lambda key: isinstance(validated_data[key], dict), validated_data))
         for field_name in nested_fk_fields_name:
             field_dict = validated_data[field_name]
@@ -627,7 +624,8 @@ class LDPSerializer(HyperlinkedModelSerializer):
                         model, sub_inst = Model.resolve(field_dict['urlid'])
                 # remote resource - get backlinked copy
                 elif hasattr(field_model, 'urlid'):
-                    sub_inst = Model.get_or_create(field_model, field_dict['urlid'])
+                    kwargs = {'urlid': field_dict['urlid']}
+                    sub_inst = field_model.objects.get(**kwargs)
             # try slug field, assuming that this is a local resource
             elif slug_field in field_dict:
                 kwargs = {slug_field: field_dict[slug_field]}
diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py
index 30fe6d6d..50c53b75 100644
--- a/djangoldp/tests/models.py
+++ b/djangoldp/tests/models.py
@@ -1,11 +1,8 @@
-import json
 from django.conf import settings
 from django.contrib.auth.models import AbstractUser
 from django.db import models
-from django.db.models import BinaryField, DateField
 from django.utils.datetime_safe import date
 
-from djangoldp.fields import LDPUrlField
 from djangoldp.models import Model
 
 
@@ -13,11 +10,10 @@ class User(AbstractUser, Model):
 
     class Meta(AbstractUser.Meta, Model.Meta):
         serializer_fields = ['@id', 'username', 'first_name', 'last_name', 'email', 'userprofile',
-                             'conversation_set', 'circle_set', 'projects']
+                             'conversation_set', 'circle_set']
         anonymous_perms = ['view', 'add']
         authenticated_perms = ['inherit', 'change']
         owner_perms = ['inherit']
-        nested_fields = ['circles', 'projects']
 
 
 class Skill(Model):
@@ -188,10 +184,8 @@ class Post(Model):
 
 
 class Circle(Model):
-    description = models.CharField(max_length=255, null=True, blank=False)
-    team = models.ManyToManyField(settings.AUTH_USER_MODEL, through="CircleMember", blank=True)
-    owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="owned_circles", on_delete=models.DO_NOTHING,
-                              null=True, blank=True)
+    description = models.CharField(max_length=255)
+    team = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
 
     class Meta(Model.Meta):
         nested_fields = ["team"]
@@ -199,28 +193,3 @@ class Circle(Model):
         authenticated_perms = ["inherit"]
         rdf_type = 'hd:circle'
         depth = 1
-
-
-class CircleMember(Model):
-    circle = models.ForeignKey(Circle, on_delete=models.CASCADE, related_name='members')
-    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="circles")
-    is_admin = models.BooleanField(default=False)
-
-    class Meta(Model.Meta):
-        container_path = "circle-members/"
-        anonymous_perms = ['view', 'add', 'delete', 'add', 'change', 'control']
-        authenticated_perms = ['inherit']
-        unique_together = ['user', 'circle']
-        rdf_type = 'hd:circlemember'
-
-
-class Project(Model):
-    description = models.CharField(max_length=255, null=True, blank=False)
-    team = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='projects')
-
-    class Meta(Model.Meta):
-        nested_fields = ["team"]
-        anonymous_perms = ['view', 'add', 'delete', 'add', 'change', 'control']
-        authenticated_perms = ["inherit"]
-        rdf_type = 'hd:project'
-        depth = 1
diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py
index fbcb6378..87acd84f 100644
--- a/djangoldp/tests/runner.py
+++ b/djangoldp/tests/runner.py
@@ -50,7 +50,6 @@ settings.configure(DEBUG=False,
                                    'djangoldp',
                                    'djangoldp.tests',
                                    ),
-                   SEND_BACKLINKS=False,
                    SITE_URL='http://happy-dev.fr',
                    BASE_URL='http://happy-dev.fr',
                    REST_FRAMEWORK = {
@@ -76,8 +75,7 @@ failures = test_runner.run_tests([
     'djangoldp.tests.tests_delete',
     'djangoldp.tests.tests_sources',
     'djangoldp.tests.tests_pagination',
-    'djangoldp.tests.tests_inbox',
-    #'djangoldp.tests.tests_temp'
+    # 'djangoldp.tests.tests_temp'
 ])
 if failures:
     sys.exit(failures)
diff --git a/djangoldp/tests/tests_auto_author.py b/djangoldp/tests/tests_auto_author.py
index 3a095e3e..2f85605a 100644
--- a/djangoldp/tests/tests_auto_author.py
+++ b/djangoldp/tests/tests_auto_author.py
@@ -20,4 +20,5 @@ class TestAutoAuthor(APITestCase):
 
         response = self.client.post('/posts/', data=json.dumps(post), content_type='application/ld+json')
         self.assertEqual(response.status_code, 201)
+        self.assertNotIn('author', response.data)
         self.assertEquals(response.data['content'], "post content")
diff --git a/djangoldp/tests/tests_inbox.py b/djangoldp/tests/tests_inbox.py
deleted file mode 100644
index cc1d8a81..00000000
--- a/djangoldp/tests/tests_inbox.py
+++ /dev/null
@@ -1,453 +0,0 @@
-import json
-from django.contrib.auth import get_user_model
-from rest_framework.test import APIClient, APITestCase
-from djangoldp.tests.models import Circle, CircleMember, Project, UserProfile
-from djangoldp.models import Activity, Follower
-
-
-class TestsInbox(APITestCase):
-
-    def setUp(self):
-        self.client = APIClient(enforce_csrf_checks=True)
-
-    #
-    #   CREATE ACTIVITY
-    #
-    def test_create_activity_circle(self):
-        # a local user has been set as the owner of a distant circle
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        payload = {
-          "@context": [
-              "https://www.w3.org/ns/activitystreams",
-              {"hd": "http://happy-dev.fr/owl/#"}
-          ],
-          "summary": "A circle was created",
-          "type": "Create",
-          "actor": {
-            "type": "Service",
-            "name": "Backlinks Service"
-          },
-          "object": {
-            "@type": "hd:circle",
-            "@id": "https://distant.com/circles/1/",
-            "owner": {
-                "@type": "foaf:user",
-                "@id": user.urlid
-            }
-          }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload), content_type='application/ld+json')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the circle was created and the user associated as owner
-        circles = Circle.objects.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(circles), 1)
-        self.assertEquals(len(activities), 1)
-        self.assertIn("https://distant.com/circles/1/", circles.values_list('urlid', flat=True))
-        self.assertEqual(circles[0].owner, user)
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    #
-    #   ADD ACTIVITIES
-    #
-    # project model has a direct many-to-many with User
-    def test_add_activity_project(self):
-        # a local user has joined a distant project
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": user.get_full_name() + " was added to Test Project",
-            "type": "Add",
-            "actor": {
-              "type": "Service",
-              "name": "Backlinks Service"
-            },
-            "object": {
-                "@type": "hd:project",
-                "@id": "https://distant.com/projects/1/"
-            },
-            "target": {
-                "@type": "foaf:user",
-                "name": user.get_full_name(),
-                "@id": user.urlid
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload), content_type='application/ld+json')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the project backlink(s) & activity were created
-        projects = Project.objects.all()
-        user_projects = user.projects.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(projects), 1)
-        self.assertEquals(len(user_projects), 1)
-        self.assertEquals(len(activities), 1)
-        self.assertIn("https://distant.com/projects/1/", projects.values_list('urlid', flat=True))
-        self.assertIn("https://distant.com/projects/1/", user_projects.values_list('urlid', flat=True))
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    # circle model has a many-to-many with user, through an intermediate model
-    def test_add_activity_circle(self):
-        # a local user has joined a distant circle
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": user.get_full_name() + " was added to Test Circle",
-            "type": "Add",
-            "actor": {
-              "type": "Service",
-              "name": "Backlinks Service"
-            },
-            "object": {
-                "@type": "hd:circlemember",
-                "@id": "https://distant.com/circle-members/1/",
-                "user": {
-                  "@type": "foaf:user",
-                  "@id": user.urlid
-                },
-                "circle": {
-                    "@type": "hd:circle",
-                    "@id": "https://distant.com/circles/1/"
-                }
-            },
-            "target": {
-                "@type": "foaf:user",
-                "name": user.get_full_name(),
-                "@id": user.urlid
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload), content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the circle backlink(s) & activity were created
-        circles = Circle.objects.all()
-        user_circles = user.circles.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(circles), 1)
-        self.assertEquals(len(user_circles), 1)
-        self.assertEquals(len(activities), 1)
-        self.assertIn("https://distant.com/circles/1/", circles.values_list('urlid', flat=True))
-        self.assertIn("https://distant.com/circle-members/1/", user_circles.values_list('urlid', flat=True))
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    # test sending an add activity when the backlink already exists
-    def test_add_activity_object_already_added(self):
-        # a local user has joined a distant circle
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        # ..but the receiver already knows about it
-        circle = Circle.objects.create(urlid="https://distant.com/circles/1/", is_backlink=True)
-        CircleMember.objects.create(urlid="https://distant.com/circle-members/1/", circle=circle, user=user,
-                                    is_backlink=True)
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": user.get_full_name() + " was added to Test Circle",
-            "type": "Add",
-            "actor": {
-                "type": "Service",
-                "name": "Backlinks Service"
-            },
-            "object": {
-                "@type": "hd:circlemember",
-                "@id": "https://distant.com/circle-members/1/",
-                "user": {
-                    "@type": "foaf:user",
-                    "@id": user.urlid
-                },
-                "circle": {
-                    "@type": "hd:circle",
-                    "@id": "https://distant.com/circles/1/"
-                }
-            },
-            "target": {
-                "@type": "foaf:user",
-                "name": user.get_full_name(),
-                "@id": user.urlid
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload),
-                                    content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the circle backlink(s) & activity were created
-        circles = Circle.objects.all()
-        user_circles = user.circles.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(circles), 1)
-        self.assertEquals(len(user_circles), 1)
-        self.assertEquals(len(activities), 1)
-        self.assertIn("https://distant.com/circles/1/", circles.values_list('urlid', flat=True))
-        self.assertIn("https://distant.com/circle-members/1/", user_circles.values_list('urlid', flat=True))
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    # TODO: adding to a model which has multiple relationships with this RDF type
-
-    # error behaviour - unknown model
-    def test_add_activity_unknown(self):
-        # a local user has joined a distant circle
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": user.get_full_name() + " was added to Test Circle",
-            "type": "Add",
-            "actor": {
-              "type": "Service",
-              "name": "Backlinks Service"
-            },
-            "object": {
-                "@type": "hd:somethingunknown",
-                "@id": "https://distant.com/somethingunknown/1/"
-            },
-            "target": {
-                "@type": "foaf:user",
-                "name": user.get_full_name(),
-                "@id": user.urlid
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload), content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
-        self.assertEqual(response.status_code, 404)
-
-    #
-    #   REMOVE & DELETE ACTIVITIES
-    #
-    # project model has a direct many-to-many with User
-    def test_remove_activity_project_using_origin(self):
-        # a local user has a distant project attached
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-        project = Project.objects.create(urlid="https://distant.com/projects/1/", is_backlink=True)
-        user.projects.add(project)
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": user.get_full_name() + " removed Test Project",
-            "type": "Remove",
-            "actor": {
-              "type": "Service",
-              "name": "Backlinks Service"
-            },
-            "object": {
-                "@type": "hd:project",
-                "@id": "https://distant.com/projects/1/"
-            },
-            "origin": {
-                "@type": "foaf:user",
-                "name": user.get_full_name(),
-                "@id": user.urlid
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload),
-                                    content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the circle backlink(s) were removed & activity were created
-        projects = Project.objects.all()
-        user_projects = user.projects.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(projects), 1)
-        self.assertEquals(len(user_projects), 0)
-        self.assertEquals(len(activities), 1)
-        self.assertIn("https://distant.com/projects/1/", projects.values_list('urlid', flat=True))
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    # TODO: test_remove_activity_project_using_target
-    # TODO: error behaviour - project does not exist on user
-
-    # Delete CircleMember
-    def test_delete_activity_circle_using_origin(self):
-        # a local user has a distant circle attached
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-        circle = Circle.objects.create(urlid="https://distant.com/circles/1/", allow_create_backlink=False)
-        CircleMember.objects.create(urlid="https://distant.com/circle-members/1/",circle=circle, user=user,
-                                    is_backlink=True)
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": "CircleMember was deleted",
-            "type": "Delete",
-            "actor": {
-              "type": "Service",
-              "name": "Backlinks Service"
-            },
-            "object": {
-                "@type": "hd:circlemember",
-                "@id": "https://distant.com/circle-members/1/",
-                "user": {
-                    "@type": "foaf:user",
-                    "@id": user.urlid
-                },
-                "circle": {
-                    "@type": "hd:circle",
-                    "@id": "https://distant.com/circles/1/"
-                }
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload),
-                                    content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the CircleMember was deleted and activity was created
-        circles = Circle.objects.all()
-        user_circles = user.circles.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(circles), 1)
-        self.assertEquals(len(user_circles), 0)
-        self.assertEquals(len(activities), 1)
-        self.assertIn("https://distant.com/circles/1/", circles.values_list('urlid', flat=True))
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    # TODO: test_delete_activity_circle_using_target
-
-    #
-    #   UPDATE Activities
-    #
-    def test_update_activity_circle(self):
-        # a local user was set as the owner of a distant circle, but the owner has been changed
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        circle = Circle.objects.create(urlid="https://distant.com/circles/1/", owner=user, is_backlink=True)
-        self.assertEqual(circle.owner, user)
-
-        payload = {
-          "@context": [
-              "https://www.w3.org/ns/activitystreams",
-              {"hd": "http://happy-dev.fr/owl/#"}
-          ],
-          "summary": "A circle was updated",
-          "type": "Update",
-          "actor": {
-            "type": "Service",
-            "name": "Backlinks Service"
-          },
-          "object": {
-            "@type": "hd:circle",
-            "@id": "https://distant.com/circles/1/",
-            "owner": {
-                "@type": "foaf:user",
-                "@id": "https://distant.com/users/1/"
-            }
-          }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload), content_type='application/ld+json')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that the circle was created and the user associated as owner
-        circles = Circle.objects.all()
-        activities = Activity.objects.all()
-        users = get_user_model().objects.all()
-        self.assertEquals(len(circles), 1)
-        self.assertEquals(len(activities), 1)
-        self.assertEquals(len(users), 2)
-        distant_user = get_user_model().objects.get(urlid="https://distant.com/users/1/")
-        self.assertIn("https://distant.com/circles/1/", circles.values_list('urlid', flat=True))
-        self.assertEqual(circles[0].owner, distant_user)
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-
-    #
-    #   FOLLOW activities
-    #
-    def test_follow_activity(self):
-        # a local user was set as the owner of a distant circle, but the owner has been changed
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        circle = Circle.objects.create(description='Test Description')
-
-        payload = {
-            "@context": [
-                "https://www.w3.org/ns/activitystreams",
-                {"hd": "http://happy-dev.fr/owl/#"}
-            ],
-            "summary": user.urlid + " followed " + circle.urlid,
-            "type": "Follow",
-            "actor": {
-                "type": "Service",
-                "name": "Backlinks Service",
-                "inbox": "http://127.0.0.1:8000/inbox/"
-            },
-            "object": {
-                "@type": "hd:circle",
-                "@id": circle.urlid
-            }
-        }
-
-        response = self.client.post('/inbox/',
-                                    data=json.dumps(payload), content_type='application/ld+json')
-        self.assertEqual(response.status_code, 201)
-
-        # assert that Follower was created with correct values
-        followers = Follower.objects.all()
-        activities = Activity.objects.all()
-        self.assertEquals(len(followers), 1)
-        self.assertEquals(len(activities), 1)
-        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
-        follower = followers[0]
-        self.assertEqual("http://127.0.0.1:8000/inbox/", follower.inbox)
-        self.assertEqual(circle.urlid, follower.object)
-
-    # test Followers are auto-deleted when the object they're following is deleted
-    def test_follower_auto_delete(self):
-        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
-        UserProfile.objects.create(user=user)
-
-        circle = Circle.objects.create(description='Test Description')
-        Follower.objects.create(object=circle.urlid, inbox="http://127.0.0.1:8000/inbox/")
-        followers = Follower.objects.all()
-        self.assertEquals(len(followers), 1)
-        circle.delete()
-        followers = Follower.objects.all()
-        self.assertEquals(len(followers), 0)
-
-    #
-    #   GET Inbox
-    #
-    def test_get_inbox(self):
-        response = self.client.get('/inbox/')
-        self.assertEqual(response.status_code, 405)
diff --git a/djangoldp/tests/tests_save.py b/djangoldp/tests/tests_save.py
index 5ec9830d..52e8bf4b 100644
--- a/djangoldp/tests/tests_save.py
+++ b/djangoldp/tests/tests_save.py
@@ -5,7 +5,7 @@ from rest_framework.utils import json
 
 from djangoldp.models import Model
 from djangoldp.serializers import LDPSerializer
-from djangoldp.tests.models import Skill, JobOffer, Invoice, LDPDummy, Resource, Post, Circle, Project
+from djangoldp.tests.models import Skill, JobOffer, Invoice, LDPDummy, Resource, Post, Circle
 
 
 class Save(TestCase):
@@ -329,15 +329,15 @@ class Save(TestCase):
         self.assertEqual(saved_post.urlid, "http://happy-dev.fr/posts/1/")
 
     def test_nested_container_user_federated(self):
-        project = Project.objects.create()
+        circle = Circle.objects.create()
         body = {
             'http://happy-dev.fr/owl/#@id': "http://external.user/user/1/",
         }
 
-        response = self.client.post('/projects/{}/team/'.format(project.pk),
+        response = self.client.post('/circles/{}/team/'.format(circle.pk),
                                     data=json.dumps(body),
                                     content_type='application/ld+json')
         self.assertEqual(response.status_code, 201)
-        self.assertEqual(response.data['projects']['ldp:contains'][0]['@id'],
-                         "http://testserver/projects/{}/".format(project.pk))
+        self.assertEqual(response.data['circle_set']['ldp:contains'][0]['@id'],
+                         "http://testserver/circles/{}/".format(circle.pk))
         self.assertEqual(response.data['@id'], "http://external.user/user/1/")
diff --git a/djangoldp/tests/tests_update.py b/djangoldp/tests/tests_update.py
index 7157b72e..a98b342d 100644
--- a/djangoldp/tests/tests_update.py
+++ b/djangoldp/tests/tests_update.py
@@ -6,7 +6,7 @@ from rest_framework.utils import json
 
 from djangoldp.serializers import LDPSerializer
 from djangoldp.tests.models import Post, UserProfile, Resource, Circle
-from djangoldp.tests.models import Skill, JobOffer, Conversation, Message, Project
+from djangoldp.tests.models import Skill, JobOffer, Conversation, Message
 
 
 class Update(TestCase):
@@ -510,15 +510,15 @@ class Update(TestCase):
         self.assertEqual(user.peers_conv.count(), 1)
 
     def test_m2m_user_link_federated(self):
-        project = Project.objects.create(description="project name")
+        circle = Circle.objects.create(description="cicle name")
         body = {
-            'http://happy-dev.fr/owl/#description': 'project name',
+            'http://happy-dev.fr/owl/#description': 'circle name',
             'http://happy-dev.fr/owl/#team': {
                 'http://happy-dev.fr/owl/#@id': 'http://external.user/user/1',
             }
         }
 
-        response = self.client.put('/projects/{}/'.format(project.pk),
+        response = self.client.put('/circles/{}/'.format(circle.pk),
                                    data=json.dumps(body),
                                    content_type='application/ld+json')
         self.assertEqual(response.status_code, 200)
@@ -526,26 +526,26 @@ class Update(TestCase):
                          "http://external.user/user/1")
 
     def test_m2m_user_link_existing_external(self):
-        project = Project.objects.create(description="project name")
+        circle = Circle.objects.create(description="cicle name")
         ext_user = get_user_model().objects.create(username=str(uuid.uuid4()), urlid='http://external.user/user/1')
         body = {
-            'http://happy-dev.fr/owl/#description': 'project name',
+            'http://happy-dev.fr/owl/#description': 'circle name',
             'http://happy-dev.fr/owl/#team': {
                 'http://happy-dev.fr/owl/#@id': ext_user.urlid,
             }
         }
 
-        response = self.client.put('/projects/{}/'.format(project.pk),
+        response = self.client.put('/circles/{}/'.format(circle.pk),
                                    data=json.dumps(body),
                                    content_type='application/ld+json')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.data['team']['ldp:contains'][0]['@id'], ext_user.urlid)
 
-        project = Project.objects.get(pk=project.pk)
-        self.assertEqual(project.team.count(), 1)
+        circle = Circle.objects.get(pk=circle.pk)
+        self.assertEqual(circle.team.count(), 1)
 
         user = get_user_model().objects.get(pk=ext_user.pk)
-        self.assertEqual(user.projects.count(), 1)
+        self.assertEqual(user.circle_set.count(), 1)
 
     def test_create_sub_object_in_existing_object_with_reverse_1to1_relation(self):
         """
@@ -575,22 +575,22 @@ class Update(TestCase):
 
     def test_m2m_user_link_remove_existing_link(self):
         ext_user = get_user_model().objects.create(username=str(uuid.uuid4()), urlid='http://external.user/user/1')
-        project = Project.objects.create(description="project name")
-        project.team.add(ext_user)
-        project.save()
+        circle = Circle.objects.create(description="cicle name")
+        circle.team.add(ext_user)
+        circle.save()
         body = {
-            'http://happy-dev.fr/owl/#description': 'project name',
+            'http://happy-dev.fr/owl/#description': 'circle name',
             'http://happy-dev.fr/owl/#team': {
             }
         }
 
-        response = self.client.put('/projects/{}/'.format(project.pk),
+        response = self.client.put('/circles/{}/'.format(circle.pk),
                                    data=json.dumps(body),
                                    content_type='application/ld+json')
         self.assertEqual(response.status_code, 200)
 
-        project = Project.objects.get(pk=project.pk)
-        self.assertEqual(project.team.count(), 0)
+        circle = Circle.objects.get(pk=circle.pk)
+        self.assertEqual(circle.team.count(), 0)
 
         user = get_user_model().objects.get(pk=ext_user.pk)
-        self.assertEqual(user.projects.count(), 0)
+        self.assertEqual(user.circle_set.count(), 0)
diff --git a/djangoldp/urls.py b/djangoldp/urls.py
index 62b9a61a..38bfe520 100644
--- a/djangoldp/urls.py
+++ b/djangoldp/urls.py
@@ -5,7 +5,7 @@ from django.conf.urls import url, include
 
 from djangoldp.models import LDPSource, Model
 from djangoldp.permissions import LDPPermissions
-from djangoldp.views import LDPSourceViewSet, WebFingerView, InboxView
+from djangoldp.views import LDPSourceViewSet, WebFingerView
 from djangoldp.views import LDPViewSet
 
 
@@ -22,7 +22,6 @@ urlpatterns = [
     url(r'^sources/(?P<federation>\w+)/', LDPSourceViewSet.urls(model=LDPSource, fields=['federation', 'urlid'],
                                                                 permission_classes=[LDPPermissions], )),
     url(r'^\.well-known/webfinger/?$', WebFingerView.as_view()),
-    url(r'^inbox/$', InboxView.as_view()),
 ]
 
 for package in settings.DJANGOLDP_PACKAGES:
diff --git a/djangoldp/views.py b/djangoldp/views.py
index c6418ac8..720f4cef 100644
--- a/djangoldp/views.py
+++ b/djangoldp/views.py
@@ -1,32 +1,25 @@
-import copy
-import json
 from django.apps import apps
 from django.conf import settings
 from django.conf.urls import url, include
 from django.contrib.auth import get_user_model
 from django.core.exceptions import FieldDoesNotExist
 from django.core.urlresolvers import get_resolver
-from django.http import JsonResponse, Http404
+from django.http import JsonResponse
 from django.shortcuts import get_object_or_404
 from django.utils.decorators import classonlymethod
 from django.views import View
 from pyld import jsonld
 from rest_framework import status
 from rest_framework.authentication import SessionAuthentication
-from rest_framework.permissions import AllowAny
 from rest_framework.parsers import JSONParser
 from rest_framework.renderers import JSONRenderer
 from rest_framework.response import Response
-from rest_framework.utils import model_meta
-from rest_framework.views import APIView
 from rest_framework.viewsets import ModelViewSet
 
 from djangoldp.endpoints.webfinger import WebFingerEndpoint, WebFingerError
-from djangoldp.models import LDPSource, Model, Activity, Follower
+from djangoldp.models import LDPSource, Model
 from djangoldp.permissions import LDPPermissions
 from djangoldp.filters import LocalObjectOnContainerPathBackend
-from djangoldp.activities import ActivityPubService, as_activitystream
-from djangoldp.activities.errors import ActivityStreamDecodeError
 
 
 get_user_model()._meta.rdf_context = {"get_full_name": "rdfs:label"}
@@ -65,183 +58,6 @@ class NoCSRFAuthentication(SessionAuthentication):
         return
 
 
-class InboxView(APIView):
-    """
-    Receive linked data notifications
-    """
-    permission_classes=[AllowAny,]
-
-    def post(self, request, *args, **kwargs):
-        '''
-        receiver for inbox messages. See https://www.w3.org/TR/ldn/
-        '''
-        payload = request.body.decode("utf-8")
-
-        try:
-            activity = json.loads(payload, object_hook=as_activitystream)
-            activity.validate()
-        except ActivityStreamDecodeError:
-            return Response('Activity type unsupported', status=status.HTTP_405_METHOD_NOT_ALLOWED)
-
-        if activity.type == 'Add':
-            self.handle_add_activity(activity, **kwargs)
-        elif activity.type == 'Remove':
-            self.handle_remove_activity(activity, **kwargs)
-        elif activity.type == 'Delete':
-            self.handle_delete_activity(activity, **kwargs)
-        elif activity.type == 'Create' or activity.type == 'Update':
-            self.handle_create_or_update_activity(activity, **kwargs)
-        elif activity.type == 'Follow':
-            self.handle_follow_activity(activity, **kwargs)
-
-        # save the activity and return 201
-        payload = bytes(json.dumps(activity.to_json()), "utf-8")
-        obj = Activity.objects.create(local_id=request.path_info, payload=payload)
-        obj.aid = Model.absolute_url(obj)
-        obj.save()
-
-        response = Response({}, status=status.HTTP_201_CREATED)
-        response['Location'] = obj.aid
-
-        return response
-
-    def get_or_create_nested_backlinks(self, object, object_model=None, update=False):
-        '''
-        recursively constructs a tree of nested objects, using get_or_create on each leaf/branch
-        :param object: Dict representation of the object
-        :param object_model: The Model class of the object. Will be discovered if set to None
-        :param update: if True will update retrieved objects with new data
-        '''
-        # store a list of the object's sub-items
-        if object_model is None:
-            object_model = Model.get_subclass_with_rdf_type(object['@type'])
-        if object_model is None:
-            raise Http404('unable to store type ' + object['@type'] + ', model with this rdf_type not found')
-        branches = {}
-
-        for item in object.items():
-            # TODO: parse other data types. Match the key to the field_name
-            if isinstance(item[1], dict):
-                item_value = item[1]
-                item_model = Model.get_subclass_with_rdf_type(item_value['@type'])
-                if item_model is None:
-                    raise Http404('unable to store type ' + item_value['@type'] + ', model with this rdf_type not found')
-
-                # push nested object tuple as a branch
-                backlink = self.get_or_create_nested_backlinks(item_value, item_model)
-                branches[item[0]] = backlink
-
-        # get or create the backlink
-        return Model.get_or_create(object_model, object['@id'], update=update, **branches)
-
-    # TODO: a fallback here? Saving the backlink as Object or similar
-    def _get_subclass_with_rdf_type_or_404(self, rdf_type):
-        model = Model.get_subclass_with_rdf_type(rdf_type)
-        if model is None:
-            raise Http404('unable to store type ' + rdf_type + ', model not found')
-        return model
-
-    def handle_add_activity(self, activity, **kwargs):
-        '''
-        handles Add Activities. See https://www.w3.org/ns/activitystreams
-        Indicates that the actor has added the object to the target
-        '''
-        object_model = self._get_subclass_with_rdf_type_or_404(activity.object['@type'])
-        target_model = self._get_subclass_with_rdf_type_or_404(activity.target['@type'])
-
-        # store backlink(s) in database
-        backlink = self.get_or_create_nested_backlinks(activity.object, object_model)
-
-        # add object to target
-        try:
-            target_info = model_meta.get_field_info(target_model)
-            target = target_model.objects.get(urlid=activity.target['@id'])
-
-            for field_name, relation_info in target_info.relations.items():
-                if relation_info.related_model == object_model:
-                    getattr(target, field_name).add(backlink)
-        except target_model.DoesNotExist:
-            return Response({}, status=status.HTTP_404_NOT_FOUND)
-
-    def handle_remove_activity(self, activity, **kwargs):
-        '''
-        handles Remove Activities. See https://www.w3.org/ns/activitystreams
-        Indicates that the actor has removed the object from the origin
-        '''
-        # TODO: Remove Activity may pass target instead
-        object_model = self._get_subclass_with_rdf_type_or_404(activity.object['@type'])
-        origin_model = self._get_subclass_with_rdf_type_or_404(activity.origin['@type'])
-
-        # get the model reference to saved object
-        try:
-            object_instance = object_model.objects.get(urlid=activity.object['@id'])
-        except object_model.DoesNotExist:
-            return
-
-        # remove object from origin
-        try:
-            origin_info = model_meta.get_field_info(origin_model)
-            origin = origin_model.objects.get(urlid=activity.origin['@id'])
-
-            for field_name, relation_info in origin_info.relations.items():
-                if relation_info.related_model == object_model:
-                    getattr(origin, field_name).remove(object_instance)
-        # TODO: decipher from history if the resource has been moved?
-        except origin_model.DoesNotExist:
-            raise Http404()
-
-    def handle_create_or_update_activity(self, activity, **kwargs):
-        '''
-        handles Create & Update Activities. See https://www.w3.org/ns/activitystreams
-        '''
-        object_model = self._get_subclass_with_rdf_type_or_404(activity.object['@type'])
-        self.get_or_create_nested_backlinks(activity.object, object_model, update=True)
-
-    def handle_delete_activity(self, activity, **kwargs):
-        '''
-        handles Remove Activities. See https://www.w3.org/ns/activitystreams
-        Indicates that the actor has deleted the object
-        '''
-        object_model = self._get_subclass_with_rdf_type_or_404(activity.object['@type'])
-
-        # get the model reference to saved object
-        try:
-            object_instance = object_model.objects.get(urlid=activity.object['@id'])
-        except object_model.DoesNotExist:
-            return
-
-        # disable backlinks first - prevents a duplicate being sent back
-        object_instance.allow_create_backlink = False
-        object_instance.save()
-        object_instance.delete()
-
-    def handle_follow_activity(self, activity, **kwargs):
-        '''
-        handles Follow Activities. See https://www.w3.org/ns/activitystreams
-        Indicates that the actor is following the object, and should receive Updates on what happens to it
-        '''
-        object_model = self._get_subclass_with_rdf_type_or_404(activity.object['@type'])
-
-        # get the model reference to saved object
-        try:
-            object_instance = object_model.objects.get(urlid=activity.object['@id'])
-        except object_model.DoesNotExist:
-            raise Http404()
-        if Model.is_external(object_instance):
-            raise Http404()
-
-        # get the inbox field from the actor
-        if isinstance(activity.actor, str):
-            inbox = activity.actor
-        else:
-            inbox = getattr(activity.actor, 'inbox', None)
-            if inbox is None:
-                inbox = getattr(activity.actor, 'id', getattr(activity.actor, '@id'))
-
-        if not Follower.objects.filter(object=object_instance.urlid, inbox=inbox).exists():
-            Follower.objects.create(object=object_instance.urlid, inbox=inbox)
-
-
 class LDPViewSetGenerator(ModelViewSet):
     """An extension of ModelViewSet that generates automatically URLs for the model"""
     model = None
-- 
GitLab