import logging import requests from django.conf import settings from django.core.mail import send_mail from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from django.template import loader from django.urls import NoReverseMatch from django.utils.translation import ugettext_lazy as _ from djangoldp.fields import LDPUrlField from djangoldp.models import Model from threading import Thread from djangoldp_notification.middlewares import MODEL_MODIFICATION_USER_FIELD from djangoldp_notification.permissions import InboxPermissions, SubscriptionsPermissions 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'] 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 Subscription(Model): object = models.URLField() inbox = models.URLField() def __str__(self): return '{}'.format(self.object) class Meta(Model.Meta): anonymous_perms = [] authenticated_perms = ["add", "view", "delete"] 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: nested_url = str(self.object) + '1/' + nested_field + '/' # we have the nested_url, but we want the model contained within's container nested_container = Model.resolve(nested_url)[0] nested_container_url = Model.absolute_url(nested_container) # check a Subscription on this pair doesn't exist already existing_subscriptions = Subscription.objects.filter(object=nested_container_url, inbox=self.inbox) # save a Subscription on this container if not existing_subscriptions.exists(): Subscription.objects.create(object=nested_container_url, inbox=self.inbox, is_backlink=True) except: pass super(Subscription, self).save(*args, **kwargs) # --- SUBSCRIPTION SYSTEM --- @receiver(post_save, dispatch_uid="callback_notif") def send_notification(sender, instance, created, **kwargs): if sender != Notification: threads = [] 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 not instance.is_backlink: process = Thread(target=send_request, args=[subscription.inbox, url_resource, instance, created]) process.start() threads.append(process) def send_request(target, object_iri, instance, created): unknown = _("Auteur inconnu") author = getattr(getattr(instance, MODEL_MODIFICATION_USER_FIELD, unknown), "urlid", unknown) request_type = "creation" if created else "update" try: # 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 } requests.post(target, json=json, headers={"Content-Type": "application/ld+json"}) except Exception as e: logging.error('Djangoldp_notifications: Error with request: {}'.format(e)) return True @receiver(post_save, sender=Notification) def send_email_on_notification(sender, instance, created, **kwargs): if created and instance.summary and settings.JABBER_DEFAULT_HOST 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, ''))) # external author else: who = requests.get(instance.author).json()['name'] except: who = "Personne inconnue" # get identifier for resource triggering notification, and store in where try: if instance.object.startswith(settings.SITE_URL): where = str(Model.resolve_id(instance.object.replace(settings.SITE_URL, ''))) else: where = requests.get(instance.object).json()['name'] except: where = "Endroit inconnu" if who == where: where = "t'a envoyé un message privé" else: where = "t'a mentionné sur " + where html_message = loader.render_to_string( 'email.html', { 'on': (settings.INSTANCE_DEFAULT_CLIENT or settings.JABBER_DEFAULT_HOST), 'instance': instance, 'author': who, 'object': where } ) send_mail( 'Notification sur ' + (settings.INSTANCE_DEFAULT_CLIENT or settings.JABBER_DEFAULT_HOST), instance.summary, settings.EMAIL_HOST_USER or "noreply@" + settings.JABBER_DEFAULT_HOST, [instance.user.email], fail_silently=True, html_message=html_message )