djangoldp-notification/djangoldp_notification/models.py~

311 lines
13 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import requests
import json
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.template import loader
from django.urls import NoReverseMatch, get_resolver, reverse
from django.utils.translation import ugettext_lazy as _
from djangoldp.fields import LDPUrlField
from djangoldp.models import Model
from djangoldp.views import LDPViewSet
from djangoldp.activities.services import ActivityQueueService, ActivityPubService, activity_sending_finished
from djangoldp_notification.middlewares import MODEL_MODIFICATION_USER_FIELD
from djangoldp_notification.permissions import InboxPermissions, SubscriptionsPermissions
from djangoldp_notification.views import LDPNotificationsViewSet
from djangoldp_webpushnotification.views import _send_push
import logging
logger = logging.getLogger('djangoldp')
class Notification(Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='inbox', on_delete=models.deletion.CASCADE)
author = LDPUrlField()
object = LDPUrlField()
type = models.CharField(max_length=255)
summary = models.TextField()
date = models.DateTimeField(auto_now_add=True)
unread = models.BooleanField(default=True)
class Meta(Model.Meta):
owner_field = 'user'
ordering = ['-date']
permission_classes = [InboxPermissions]
anonymous_perms = ['add']
authenticated_perms = ['inherit']
owner_perms = ['view', 'change', 'control']
view_set = LDPNotificationsViewSet
# NOTE: this would be our ideal cache behaviour
# the functionality for optimising it was removed because of an issue with extensibility
# https://git.startinblox.com/djangoldp-packages/djangoldp-notification/merge_requests/42#note_58559
'''def clear_djangoldp_cache(self, cache, cache_entry):
# should only clear the users/x/inbox
lookup_arg = LDPViewSet.get_lookup_arg(model=get_user_model())
url = reverse('{}-{}-list'.format(self.user.__class__.__name__.lower(), self.__class__.__name__.lower()),
args=[getattr(self.user, lookup_arg)])
url = '{}{}'.format(settings.SITE_URL, url)
cache.invalidate(cache_entry, url)
# invalidate the global /notifications/ container also
url = '{}{}'.format(settings.SITE_URL, reverse('{}-list'.format(self.__class__.__name__.lower())))
cache.invalidate(cache_entry, url)'''
def __str__(self):
return '{}'.format(self.type)
def save(self, *args, **kwargs):
# I cannot send a notification to myself
if self.author.startswith(settings.SITE_URL):
try:
# author is a WebID.. convert to local representation
author = Model.resolve(self.author.replace(settings.SITE_URL, ''))[1]
except NoReverseMatch:
author = None
if author == self.user:
return
super(Notification, self).save(*args, **kwargs)
class NotificationSetting(Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="settings")
receiveMail = models.BooleanField(default=True)
class Meta:
auto_author = 'user'
owner_field = 'user'
anonymous_perms = []
authenticated_perms = []
owner_perms = ['view', 'change']
container_path = 'settings/'
serializer_fields = ['@id', 'receiveMail']
rdf_type = 'sib:usersettings'
def __str__(self):
return '{} ({})'.format(self.user.get_full_name(), self.user.urlid)
class Subscription(Model):
object = 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):
return '{}'.format(self.object)
class Meta(Model.Meta):
anonymous_perms = []
authenticated_perms = ["add", "view", "delete"]
permission_classes = [SubscriptionsPermissions]
@receiver(post_save, sender=Subscription, dispatch_uid="nested_subscriber_check")
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', [])
# Don't create nested subscriptions for user model (Notification loop issue)
if local._meta.model_name == get_user_model()._meta.model_name:
return
for nested_field in nested_fields:
try:
field = local._meta.get_field(nested_field)
nested_container = field.related_model
nested_container_url = Model.absolute_url(nested_container)
if field.one_to_many:
# get the nested view set
nested_url = str(instance.object) + '1/' + nested_field + '/'
view, args, kwargs = get_resolver().resolve(nested_url.replace(settings.SITE_URL, ''))
# get the reverse name for the field
field_name = view.initkwargs['nested_related_name']
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
# --- SUBSCRIPTION SYSTEM ---
@receiver(post_save, dispatch_uid="callback_notif")
@receiver(post_delete, dispatch_uid="delete_callback_notif")
def send_notification(sender, instance, **kwargs):
if(type(instance).__name__ != "ScheduledActivity" and type(instance).__name__ != "LogEntry" and type(instance).__name__ != "Activity"):
if sender != Notification:
# don't send notifications for foreign resources
if hasattr(instance, 'urlid') and Model.is_external(instance.urlid):
return
recipients = []
try:
url_container = settings.BASE_URL + Model.container_id(instance)
url_resource = settings.BASE_URL + Model.resource_id(instance)
except NoReverseMatch:
return
# 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)):
if 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:
try:
if kwargs.get("created"):
continue
instance = getattr(instance, subscription.field, instance)
# don't send notifications for foreign resources
if hasattr(instance, 'urlid') and Model.is_external(instance.urlid):
continue
url_resource = settings.BASE_URL + Model.resource_id(instance)
except NoReverseMatch:
continue
except ObjectDoesNotExist:
continue
send_request(subscription.inbox, url_resource, instance, kwargs.get("created"))
recipients.append(subscription.inbox)
@receiver(activity_sending_finished, sender=ActivityQueueService)
def _handle_prosody_response(sender, response, saved_activity, **kwargs):
'''callback function for handling a response from Prosody on a notification'''
# if text is defined in the response body then it's an error
if saved_activity is not None:
response_body = saved_activity.response_to_json()
if 'condition' in response_body:
logger.error("[DjangoLDP-Notification.models._handle_prosody_response] error in Prosody response " +
str(response_body))
def send_request(target, object_iri, instance, created):
author = getattr(getattr(instance, MODEL_MODIFICATION_USER_FIELD, None), "urlid", str(_("Auteur inconnu")))
if(created is not None):
request_type = "creation" if created else "update"
else:
request_type = "deletion"
# local inbox
if target.startswith(settings.SITE_URL):
user = Model.resolve_parent(target.replace(settings.SITE_URL, ''))
Notification.objects.create(user=user, object=object_iri, type=request_type, author=author)
# external inbox
else:
json = {
"@context": settings.LDP_RDF_CONTEXT,
"object": object_iri,
"author": author,
"type": request_type
}
ActivityQueueService.send_activity(target, json)
@receiver(post_save, sender=Notification)
def send_email_on_notification(sender, instance, created, **kwargs):
if created and instance.summary and getattr(settings,'JABBER_DEFAULT_HOST',False) and instance.user.email:
# get author name, and store in who
try:
# local author
if instance.author.startswith(settings.SITE_URL):
who = str(Model.resolve_id(instance.author.replace(settings.SITE_URL, '')).get_full_name())
# external author
else:
who = requests.get(instance.author).json()['name']
except:
who = "Quelqu'un"
# get identifier for resource triggering notification, and store in where
try:
if instance.object.startswith(settings.SITE_URL):
if hasattr(Model.resolve_id(instance.object.replace(settings.SITE_URL, '')), 'get_full_name'):
where = Model.resolve_id(instance.object.replace(settings.SITE_URL, '')).get_full_name()
else:
where = str(Model.resolve_id(instance.object.replace(settings.SITE_URL, '')).name)
else:
where = requests.get(instance.object).json()['name']
except:
where = "le chat"
if who == where:
where = "t'a envoyé un message privé"
else:
where = "t'a mentionné sur " + where
on = (getattr(settings, 'INSTANCE_DEFAULT_CLIENT', False) or settings.JABBER_DEFAULT_HOST)
html_message = loader.render_to_string(
'email.html',
{
'on': on,
'instance': instance,
'author': who,
'object': where
}
)
if instance.user.settings.receiveMail:
send_mail(
'Notification sur ' + on,
instance.summary,
(getattr(settings, 'EMAIL_HOST_USER', False) or "noreply@" + settings.JABBER_DEFAULT_HOST),
[instance.user.email],
fail_silently=True,
html_message=html_message
)
@receiver(post_save, sender=Notification)
def send_webpush_on_notification(sender, instance, created, **kwargs):
if instance.summary :
if instance.user: # if the person exists and accepts notifications
try:
# local author
if instance.author.startswith(settings.SITE_URL):
who = str(Model.resolve_id(instance.author.replace(settings.SITE_URL, '')).get_full_name())
# external author
else:
who = requests.get(instance.author).json()['name']
except:
who = "Quelqu'un"
payload = {
"head": "Notification from " + who,
"body": instance.summary,
"id": instance.user.id
} # make the payload that we wanna send?
_send_push(payload)
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_settings(sender, instance, created, **kwargs):
try:
if created and instance.urlid.startswith(settings.SITE_URL):
NotificationSetting.objects.create(user=instance)
except:
pass