Merge branch '22-send-notifications-to-parents' into 'master'
Feature: parent subscriptions Closes #22 See merge request djangoldp-packages/djangoldp-notification!28
This commit is contained in:
commit
64f778a52c
@ -31,6 +31,9 @@ An object allowing a User to be notified of any change on a resource or a contai
|
|||||||
| -------- | ---------- | ------- | ------------------------------------------------------------ |
|
| -------- | ---------- | ------- | ------------------------------------------------------------ |
|
||||||
| `object` | `URLField` | | ID of the resource or the container to watch |
|
| `object` | `URLField` | | ID of the resource or the container to watch |
|
||||||
| `inbox` | `URLField` | | ID of the inbox to notify when the resource or the container change |
|
| `inbox` | `URLField` | | ID of the inbox to notify when the resource or the container change |
|
||||||
|
| `field` | `CharField` | | (optional) if set, then object['field'] will be sent in the notification, not object |
|
||||||
|
|
||||||
|
For convenience, when you create a subscription on an object, DjangoLDP-Notification will parse the object for any one-to-many nested field relations. It will then create nested-subscriptions, i.e. a subscription on the nested field which sends an update to the same inbox, passing the parent model. If this behaviour is undesired you can delete the `Subscription` instance
|
||||||
|
|
||||||
# Middlewares
|
# Middlewares
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.29 on 2020-06-02 10:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('djangoldp_notification', '0005_auto_20200505_1733'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='subscription',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='djangoldp_notification.Subscription'),
|
||||||
|
),
|
||||||
|
]
|
24
djangoldp_notification/migrations/0007_auto_20200604_1055.py
Normal file
24
djangoldp_notification/migrations/0007_auto_20200604_1055.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.29 on 2020-06-04 10:55
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('djangoldp_notification', '0006_subscription_parent'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='subscription',
|
||||||
|
name='parent',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='subscription',
|
||||||
|
name='field',
|
||||||
|
field=models.CharField(blank=True, help_text='if set to a field name on the object model, the field will be passed instead of the object instance', max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -7,7 +7,7 @@ from django.db import models
|
|||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.urls import NoReverseMatch
|
from django.urls import NoReverseMatch, get_resolver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from djangoldp.fields import LDPUrlField
|
from djangoldp.fields import LDPUrlField
|
||||||
from djangoldp.models import Model
|
from djangoldp.models import Model
|
||||||
@ -53,6 +53,8 @@ class Notification(Model):
|
|||||||
class Subscription(Model):
|
class Subscription(Model):
|
||||||
object = models.URLField()
|
object = models.URLField()
|
||||||
inbox = models.URLField()
|
inbox = models.URLField()
|
||||||
|
field = models.CharField(max_length=255, blank=True, null=True,
|
||||||
|
help_text='if set to a field name on the object model, the field will be passed instead of the object instance')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}'.format(self.object)
|
return '{}'.format(self.object)
|
||||||
@ -62,30 +64,42 @@ class Subscription(Model):
|
|||||||
authenticated_perms = ["add", "view", "delete"]
|
authenticated_perms = ["add", "view", "delete"]
|
||||||
permission_classes = [SubscriptionsPermissions]
|
permission_classes = [SubscriptionsPermissions]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
# save subscriptions for nested fields
|
|
||||||
if self.pk is None and not self.is_backlink and self.object.startswith(settings.SITE_URL):
|
|
||||||
try:
|
|
||||||
# object is a WebID.. convert to local representation
|
|
||||||
local = Model.resolve(self.object.replace(settings.SITE_URL, ''))[0]
|
|
||||||
nested_fields = Model.get_meta(local, 'nested_fields', [])
|
|
||||||
|
|
||||||
for nested_field in nested_fields:
|
@receiver(post_save, sender=Subscription, dispatch_uid="nested_subscriber_check")
|
||||||
nested_url = str(self.object) + '1/' + nested_field + '/'
|
def create_nested_subscribers(sender, instance, created, **kwargs):
|
||||||
|
# save subscriptions for one-to-many nested fields
|
||||||
|
if created and not instance.is_backlink and instance.object.startswith(settings.SITE_URL):
|
||||||
|
try:
|
||||||
|
# object is a WebID.. convert to local representation
|
||||||
|
local = Model.resolve(instance.object.replace(settings.SITE_URL, ''))[0]
|
||||||
|
nested_fields = Model.get_meta(local, 'nested_fields', [])
|
||||||
|
|
||||||
# we have the nested_url, but we want the model contained within's container
|
for nested_field in nested_fields:
|
||||||
nested_container = Model.resolve(nested_url)[0]
|
try:
|
||||||
|
field = local._meta.get_field(nested_field)
|
||||||
|
nested_container = field.related_model
|
||||||
nested_container_url = Model.absolute_url(nested_container)
|
nested_container_url = Model.absolute_url(nested_container)
|
||||||
|
|
||||||
# check a Subscription on this pair doesn't exist already
|
if field.one_to_many:
|
||||||
existing_subscriptions = Subscription.objects.filter(object=nested_container_url, inbox=self.inbox)
|
# get the nested view set
|
||||||
# save a Subscription on this container
|
nested_url = str(instance.object) + '1/' + nested_field + '/'
|
||||||
if not existing_subscriptions.exists():
|
view, args, kwargs = get_resolver().resolve(nested_url.replace(settings.SITE_URL, ''))
|
||||||
Subscription.objects.create(object=nested_container_url, inbox=self.inbox, is_backlink=True)
|
# get the reverse name for the field
|
||||||
except:
|
field_name = view.initkwargs['nested_related_name']
|
||||||
pass
|
|
||||||
|
if field_name is not None and field_name != '':
|
||||||
|
# check that this nested-field subscription doesn't already exist
|
||||||
|
existing_subscriptions = Subscription.objects.filter(object=nested_container_url, inbox=instance.inbox,
|
||||||
|
field=field_name)
|
||||||
|
# save a Subscription on this container
|
||||||
|
if not existing_subscriptions.exists():
|
||||||
|
Subscription.objects.create(object=nested_container_url, inbox=instance.inbox, is_backlink=True,
|
||||||
|
field=field_name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
super(Subscription, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# --- SUBSCRIPTION SYSTEM ---
|
# --- SUBSCRIPTION SYSTEM ---
|
||||||
@ -94,6 +108,7 @@ class Subscription(Model):
|
|||||||
def send_notification(sender, instance, **kwargs):
|
def send_notification(sender, instance, **kwargs):
|
||||||
if sender != Notification:
|
if sender != Notification:
|
||||||
threads = []
|
threads = []
|
||||||
|
recipients = []
|
||||||
try:
|
try:
|
||||||
url_container = settings.BASE_URL + Model.container_id(instance)
|
url_container = settings.BASE_URL + Model.container_id(instance)
|
||||||
url_resource = settings.BASE_URL + Model.resource_id(instance)
|
url_resource = settings.BASE_URL + Model.resource_id(instance)
|
||||||
@ -102,11 +117,21 @@ def send_notification(sender, instance, **kwargs):
|
|||||||
|
|
||||||
# dispatch a notification for every Subscription on this resource
|
# dispatch a notification for every Subscription on this resource
|
||||||
for subscription in Subscription.objects.filter(models.Q(object=url_resource) | models.Q(object=url_container)):
|
for subscription in Subscription.objects.filter(models.Q(object=url_resource) | models.Q(object=url_container)):
|
||||||
if not instance.is_backlink:
|
if not instance.is_backlink and subscription.inbox not in recipients and \
|
||||||
|
(not subscription.is_backlink or not kwargs.get("created")):
|
||||||
|
# I may have configured to send the subscription to a foreign key
|
||||||
|
if subscription.field is not None and len(subscription.field) > 1:
|
||||||
|
instance = getattr(instance, subscription.field, instance)
|
||||||
|
try:
|
||||||
|
url_resource = settings.BASE_URL + Model.resource_id(instance)
|
||||||
|
except NoReverseMatch:
|
||||||
|
continue
|
||||||
|
|
||||||
process = Thread(target=send_request, args=[subscription.inbox, url_resource, instance,
|
process = Thread(target=send_request, args=[subscription.inbox, url_resource, instance,
|
||||||
kwargs.get("created", False)])
|
kwargs.get("created", False)])
|
||||||
process.start()
|
process.start()
|
||||||
threads.append(process)
|
threads.append(process)
|
||||||
|
recipients.append(subscription.inbox)
|
||||||
|
|
||||||
|
|
||||||
def send_request(target, object_iri, instance, created):
|
def send_request(target, object_iri, instance, created):
|
||||||
|
Loading…
Reference in New Issue
Block a user