conference-website/conference_main/signals.py

195 lines
6.4 KiB
Python

import logging
from typing import Dict, Optional
from blender_id_oauth_client import signals as bid_signals
from django import urls
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.db.models.signals import post_save, post_delete, pre_delete, pre_save
from django.dispatch import receiver
from conference_main.models import (
Event,
FestivalEntry,
Message,
MessageExchange,
Edition,
SiteSettings,
FlatFile,
)
log = logging.getLogger(__name__)
def clear_site_cache(sender: object, instance: Edition, **kwargs: object) -> None:
"""
Clear the cache for `Site.objects.get_current`.
This is needed because otherwise changes to e.g. Editions or SiteSettings
would not be taken into account when using `Site.object.get_current`.
"""
Site.objects.clear_cache()
post_save.connect(clear_site_cache, sender=Edition)
post_save.connect(clear_site_cache, sender=SiteSettings)
post_delete.connect(clear_site_cache, sender=Edition)
post_delete.connect(clear_site_cache, sender=SiteSettings)
@receiver(bid_signals.user_created)
def set_profile_fullname(
sender: object, instance: User, oauth_info: Dict[str, str], **kwargs: object
) -> None:
"""Create a Profile when a new User is created via OAuth"""
my_log = log.getChild('create_profile')
if not instance.profile:
my_log.info('NOT updating user full_name of user %s, as they have no Profile')
return
my_log.info('Updating Profile full_name as result of OAuth login of %s', instance.pk)
instance.profile.full_name = oauth_info['full_name']
instance.profile.save()
@receiver(post_save, sender=User)
def create_profile(
sender: object, instance: User, raw: bool, created: bool, **kwargs: object
) -> None:
from conference_main.models import Profile
if raw:
return
if not created:
return
my_log = log.getChild('create_profile')
try:
profile = instance.profile
except Profile.DoesNotExist:
pass
else:
my_log.debug(
'Newly created User %d already has a Profile %d, not creating new one',
instance.pk,
profile.pk,
)
return
my_log.info('Creating new Profile due to creation of user %s', instance.pk)
Profile.objects.create(user=instance)
@receiver(post_save, sender=MessageExchange)
def message_created(
sender: object, instance: MessageExchange, raw: bool, created: bool, **kwargs: object
) -> None:
if raw:
return
if not created:
return
# TODO(fsiddi) Send email asynchronously
# TODO(fsiddi) Extend logging
log.debug('Sending email notification about new message')
# If object belongs to staff, skip notifications
# TODO(fsiddi) enable once we are happy with the look of mails
# if instance.content_object.user.is_staff:
# return
if isinstance(instance.content_object, Event):
send_email_notification_for_event_message(instance.content_object, instance.message)
elif isinstance(instance.content_object, FestivalEntry):
send_email_notification_for_festival_entry_message(
instance.content_object, instance.message
)
def send_email_notification_for_event_message(event: Event, message: Message) -> None:
presentation_url = urls.reverse(
'presentation_detail', kwargs={'edition_path': event.edition.path, 'pk': event.pk}
)
# Build URL pointing directly to the message
current_site = Site.objects.get_current()
presentation_url = f'http://{current_site}{presentation_url}review/#message-{message.id}'
# If staff posted a message, notify speaker (unless speaker is staff)
if message.user.is_staff:
# TODO(fsiddi) Improve content and layout of the message
send_mail(
f'New comment on your {event.edition.title} presentation',
'Hi,\n\n'
f'There is a new comment on your Blender Conference presentation. \n'
f'Check it out at {presentation_url} \n\n'
f'{event.edition.title} team',
settings.DEFAULT_FROM_EMAIL,
[event.user.email],
fail_silently=False,
)
# In any other case, notify staff
else:
send_mail(
f'New comment on presentation "{event.name}"',
'Hi, \n\n'
f'There is a new comment on presentation "{event.name}". \n'
f'Check it out at {presentation_url} \n\n',
settings.DEFAULT_FROM_EMAIL,
[settings.DEFAULT_REPLY_TO_EMAIL],
fail_silently=False,
)
def send_email_notification_for_festival_entry_message(
festival_entry: FestivalEntry, message: Message
) -> None:
festival_entry_url = urls.reverse(
'festival_entry_detail',
kwargs={'edition_path': festival_entry.edition.path, 'pk': festival_entry.pk},
)
festival_entry_url = f'https://conference.blender.org{festival_entry_url}'
if message.user.is_staff:
# If staff posted a message, notify speaker.
send_mail(
'New comment on your Suzanne Awards festival entry',
'Hi,\n\n'
f'There is a new comment on your Blender Conference festival entry. \n'
f'Check it out at {festival_entry_url} \n\n'
f'Blender Conference 2019 team',
settings.DEFAULT_FROM_EMAIL,
[festival_entry.user.email],
fail_silently=False,
)
else:
# If owner posted a message, notify staff.
send_mail(
f'New comment on festival entry "{festival_entry.title}"',
'Hi, \n\n'
f'There is a new comment on festival entry "{festival_entry.title}". \n'
f'Check it out at {festival_entry_url} \n\n',
settings.DEFAULT_FROM_EMAIL,
[settings.DEFAULT_REPLY_TO_EMAIL],
fail_silently=False,
)
@receiver(pre_delete, sender=FlatFile)
def delete_flatfile_on_delete(sender: object, instance: FlatFile, **kwargs: object) -> None:
instance.file.delete(save=False)
@receiver(pre_save, sender=FlatFile)
def delete_flatfile_on_change(sender: object, instance: FlatFile, **kwargs: object) -> None:
if kwargs.get('raw', False):
return
original_flatfile: Optional[FlatFile] = FlatFile.objects.filter(pk=instance.pk).first()
if original_flatfile and original_flatfile.file != instance.file:
original_flatfile.file.delete(save=False)