# -*- coding: utf-8 -*-
# Akvo RSR is covered by the GNU Affero General Public License.
# See more details in the license.txt file located at the root folder of the Akvo RSR module.
# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
import logging
import os
from django.contrib.admin.models import ADDITION, CHANGE
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.apps import apps
from django.db.models import Q
from django.utils import timezone
from sorl.thumbnail import ImageField
from akvo.utils import rsr_send_mail, rsr_send_mail_to_users, get_report_thumbnail, save_image
logger = logging.getLogger('akvo.rsr')
[docs]def create_publishing_status(sender, **kwargs):
"""
called when a new project is saved so an associated published record for the
project is created
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
# kwargs['raw'] is True when we're running manage.py loaddata
from .models import PublishingStatus
if kwargs.get('created', False) and not kwargs.get('raw', False):
new_project = kwargs['instance']
ps = apps.get_model('rsr', 'publishingstatus')(status=PublishingStatus.STATUS_UNPUBLISHED)
ps.project = new_project
ps.save()
[docs]def create_organisation_account(sender, **kwargs):
"""
called when a new organisation is saved so an associated org account is
created with the "free" level of access to widgets
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
# kwargs['raw'] is True when we're running manage.py loaddata
if kwargs.get('created', False) and not kwargs.get('raw', False):
new_org = kwargs['instance']
OrganisationAccount = apps.get_model('rsr', 'OrganisationAccount')
try:
# this should never work
OrganisationAccount.objects.get(organisation=new_org)
except OrganisationAccount.DoesNotExist:
# and when it doesn't we do this
new_acc = OrganisationAccount(organisation=new_org,
account_level=OrganisationAccount.ACCOUNT_FREE)
new_acc.save()
[docs]def change_name_of_file_on_create(sender, **kwargs):
"""
call to create a filename when creating a new model instance with the pattern
ModelName_instance.pk_FieldName_YYYY-MM-DD_HH.MM.SS.ext
Since we cannot do this until the instance of the model has been saved
we do it as a post_save signal callback
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
from .models import ProjectUpdate
# kwargs['raw'] is True when we're running manage.py loaddata
if kwargs.get('created', False) and not kwargs.get('raw', False):
instance = kwargs['instance']
opts = instance._meta
for f in opts.fields:
# extend this list of fields if needed to catch other uploads
if isinstance(f, ImageField):
# the actual image sits directly on the instance of the model
img = getattr(instance, f.name)
if img:
img_name = "%s_%s_%s_%s%s" % (
opts.object_name,
instance.pk or '',
f.name,
timezone.now().strftime("%Y-%m-%d_%H.%M.%S"),
os.path.splitext(img.name)[1],
)
save_image(img, img_name, f.name)
# Create thumbnail for use in reports
if sender == ProjectUpdate:
get_report_thumbnail(img)
[docs]def change_name_of_file_on_change(sender, **kwargs):
"""
call to create a filename when saving the changes of a model with the pattern
ModelName_instance.pk_FieldName_YYYY-MM-DD_HH.MM.SS.ext
this is done before saving the model
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
from .models import ProjectUpdate
if not kwargs.get('created', False):
instance = kwargs['instance']
opts = instance._meta
for f in opts.fields:
# extend this list of fields if needed to catch other uploads
if isinstance(f, ImageField):
img = getattr(instance, f.name)
# if a new image is uploaded it resides in a InMemoryUploadedFile
if img:
try:
if isinstance(img.file, InMemoryUploadedFile):
img.name = "%s_%s_%s_%s%s" % (
opts.object_name,
instance.pk or '',
f.name,
timezone.now().strftime("%Y-%m-%d_%H.%M.%S"),
os.path.splitext(img.name)[1],
)
# Create thumbnail for use in reports
if sender == ProjectUpdate:
get_report_thumbnail(img)
except Exception:
pass
[docs]def set_showcase_project(instance, created, **kwargs):
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
Project = apps.get_model('rsr', 'Project')
if instance.showcase:
Project.objects.exclude(pk=instance.pk).update(showcase=False)
[docs]def set_focus_org(instance, created, **kwargs):
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
Organisation = apps.get_model('rsr', 'Organisation')
if instance.focus_org:
Organisation.objects.exclude(pk=instance.pk).update(focus_org=False)
[docs]def create_benchmark_objects(project):
"""
create the relevant Benchmark objects for this project based on the Categories of the project
"""
# Benchmarks are not used anymore
# from .models import Benchmark
#
# for category in project.categories.all():
# for benchmarkname in category.benchmarknames.all():
# benchmark, created = Benchmark.objects.get_or_create(project=project,
# category=category, name=benchmarkname, defaults={'value': 0})
[docs]def act_on_log_entry(sender, **kwargs):
"""
catch the LogEntry post_save to grab newly added Project instances and create
Benchmark objects for it
we do this at this time to be able to work with a fully populated Project
instance
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
CRITERIA = [
{'app': 'rsr', 'model': 'project', 'action': ADDITION, 'call': create_benchmark_objects},
{'app': 'rsr', 'model': 'project', 'action': CHANGE, 'call': create_benchmark_objects},
]
if kwargs.get('created', False) and not kwargs.get('raw', False):
log_entry = kwargs['instance']
content_type = ContentType.objects.get(pk=log_entry.content_type_id)
for criterion in CRITERIA:
if (
content_type.app_label == criterion['app']
and content_type.model == criterion['model']
and log_entry.action_flag == criterion['action']
):
object = content_type.get_object_for_this_type(pk=log_entry.object_id)
criterion['call'](object)
[docs]def employment_pre_save(sender, **kwargs):
"""
This signal intends to send a mail to the user when his/her account has been approved.
This signal also sets 'Users' Group for the employment if no group has been set
A mail will be sent when:
- A new employment is created with is_approved = True.
* We assume this happens when an existing user is invited for a new organisation.
- An existing employment is updated from is_approved = False changed to True.
* We assume this happens when an existing user has requested to join an organisation himself.
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
# FIXME: The actual save may fail. Why are emails being sent pre_save?!
employment = kwargs.get("instance", None)
if not employment:
return
# If this is an employment with a shadow org, don't send email
if employment.organisation.original is not None:
return
# Set the group to 'Users' when no group has been specified
if not employment.group:
employment.group = Group.objects.get(name='Users')
try:
obj = sender.objects.get(pk=employment.pk)
except sender.DoesNotExist:
# Employment is new, send mail when it is also approved
if employment.is_approved:
rsr_send_mail(
[employment.user.email],
subject='registration/approved_added_email_subject.txt',
message='registration/approved_added_email_message.txt',
subject_context={
'organisation': employment.organisation,
},
msg_context={
'user': employment.user,
'organisation': employment.organisation,
}
)
else:
# Employment already exists, send mail when it wasn't approved before, but is approved now.
if not obj.is_approved and employment.is_approved:
rsr_send_mail(
[employment.user.email],
subject='registration/approved_request_email_subject.txt',
message='registration/approved_request_email_message.txt',
subject_context={
'organisation': employment.organisation,
},
msg_context={
'user': employment.user,
'organisation': employment.organisation,
}
)
[docs]def employment_post_save(sender, **kwargs):
"""
For all employments:
- Set User to is_staff (for admin access) when the employment is approved and the Group is set
to 'Project Editors', 'User managers' or 'Admins', or when the user is a superuser or general
admin.
If a new employment is created for an active user of which the employment is not approved yet:
- Inform RSR support users, organisation admins and organisation user managers of the request
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
# Retrieve all user groups and the employment
project_editors_group = Group.objects.get(name='Project Editors')
user_managers_group = Group.objects.get(name='User Managers')
admins_group = Group.objects.get(name='Admins')
employment = kwargs.get("instance", None)
if not employment:
return
# If this is an employment with a shadow org, don't send email
if employment.organisation.original is not None:
return
user = employment.user
# Set user to staff when in a certain group
if (employment.group in [project_editors_group, user_managers_group, admins_group]
and employment.is_approved) or user.is_superuser or user.is_admin:
user.is_staff = True
user.save()
# Send an 'Organisation request' mail when an employment has been newly created, the
# user is active and the employment has not been approved yet.
if kwargs['created'] and user.is_active and not employment.is_approved:
organisation = employment.organisation
# Retrieve all active support users
active_support_users = get_user_model().objects.filter(is_active=True, is_support=True)
# General (support) admins will always be informed
admin_users = active_support_users.filter(is_admin=True)
# As well as organisation (+ content owners) User managers and Admins
employer_users = active_support_users.filter(
employers__organisation__in=organisation.content_owned_by(),
employers__group__in=[user_managers_group, admins_group]
)
notify = active_support_users.filter(
Q(pk__in=admin_users.values_list('pk', flat=True))
| Q(pk__in=employer_users.values_list('pk', flat=True))
).exclude(pk=user.pk).distinct()
rsr_send_mail_to_users(
notify,
subject='registration/user_organisation_request_subject.txt',
message='registration/user_organisation_request_message.txt',
subject_context={
'user': user,
'organisation': organisation
},
msg_context={
'user': user,
'organisation': organisation
},
)
[docs]def update_project_budget(sender, **kwargs):
"""
called when BudgetItem objects are added/changed/deleted
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
# kwargs['raw'] is True when we're running manage.py loaddata
if not kwargs.get('raw', False):
try:
kwargs['instance'].project.update_budget()
kwargs['instance'].project.update_funds()
kwargs['instance'].project.update_funds_needed()
except ObjectDoesNotExist:
# this happens when a project is deleted, and thus any invoices linked to it go the
# same way.
pass
[docs]def update_project_funding(sender, **kwargs):
"""
called when Partnership objects are added/changed/deleted
"""
# Disable signal handler when loading fixtures
if kwargs.get('raw', False):
return
# kwargs['raw'] is True when we're running manage.py loaddata
if not kwargs.get('raw', False):
try:
Project = apps.get_model('rsr', 'project')
project = Project.objects.get(id=kwargs['instance'].project_id)
project.update_funds()
project.update_funds_needed()
except ObjectDoesNotExist:
# this happens when a project is deleted, and thus any invoices linked to it go the
# same way.
pass