Files
django-dav-events/dav_events/workflow.py

383 lines
18 KiB
Python

import datetime
import logging
from django.apps import apps
from django.utils import timezone
from . import emails
from .utils import get_users_by_role, has_role
logger = logging.getLogger(__name__)
today = datetime.date.today()
class BasicWorkflow(object):
#
# Status updates
#
@classmethod
def validate_status_code_update(cls, code, event, callback=None, *args, **kwargs):
valid = True
return_code = 'OK'
message = u'OK'
if code.startswith('accept'):
if not event.is_flagged('submitted'):
valid = False
return_code = 'not-submitted'
message = u'Event is not submitted.'
elif code.startswith('publishing'):
if not event.is_flagged('accepted'):
valid = False
return_code = 'not-accepted'
message = u'Event is not accepted.'
elif code.startswith('published'):
if event.planned_publication_date and event.planned_publication_date > today:
valid = False
return_code = 'not-due'
message = u'Event is not due to publication.'
elif not event.is_flagged('accepted'):
valid = False
return_code = 'not-accepted'
message = u'Event is not accepted.'
if callback is not None:
callback(valid, return_code, message, *args, **kwargs)
return valid, return_code, message
@classmethod
def status_code_update(cls, event, code=None):
if not event.id:
return
midnight = datetime.time(00, 00, 00)
if code in (None, 'draft'):
# Check if event has a draft flag.
if not event.flags.filter(status__code='draft').exists():
logger.info('Detected draft state of Event %s', event)
event.set_flag(status='draft', timestamp=event.created_at)
if code in (None, 'accepted'):
# Check if event with accepted flag has a number.
if event.flags.filter(status__code='accepted').exists():
if not event.number:
event.set_next_number()
logger.info('Setting number on Event %s', event)
if code in (None,
'publishing_facebook',
'publishing_web',
'publishing',
'published_facebook',
'published_web',
'published'):
if not event.flags.filter(status__code='published').exists():
# Event is not fully published yet.
# We use some querysets, just to make code more readable.
pub_flags = event.flags.filter(status__code__in=('publishing_facebook',
'publishing_web',
'publishing',
'published_facebook',
'published_web',
'published')).order_by('timestamp')
publishing_web = pub_flags.filter(status__code='publishing_web')
publishing_facebook = pub_flags.filter(status__code='publishing_facebook')
published_web = pub_flags.filter(status__code='published_web')
published_facebook = pub_flags.filter(status__code='published_facebook')
if not event.planned_publication_date or event.planned_publication_date <= today:
# Event is due to be published.
# Timestamp of the detected action flag. No very good.
if event.planned_publication_date:
timestamp = timezone.make_aware(datetime.datetime.combine(
event.planned_publication_date,
midnight)
)
else:
timestamp = None
if event.flags.filter(status__code='publishing').exists():
# The publishers have confirmed the publications date,
# so we can flag the complete publication.
if not timestamp:
timestamp = event.flags.filter(status__code='publishing').last().timestamp
logger.info('Detected published state of Event %s', event)
event.set_flag(status='published', timestamp=timestamp)
elif ((publishing_web.exists() or published_web.exists()) and
(publishing_facebook.exists() or published_facebook.exists())):
# All publishers have confirmed the publication date or have published already
# so we can flag the complete published state.
if not timestamp:
timestamp = pub_flags.last().timestamp
logger.info('Detected general published state of Event %s', event)
event.set_flag(status='published', timestamp=timestamp)
else:
if publishing_web.exists() and not published_web.exists():
# One publisher has confirmed the publication date,
# so we can flag, that he/she has published.
if not timestamp:
timestamp = event.flags.filter(status__code='publishing_web').last().timestamp
logger.info('Detected published_web state of Event %s', event)
event.set_flag(status='published_web', timestamp=timestamp)
if publishing_facebook.exists() and not published_facebook.exists():
# One publisher has confirmed the publication date,
# so we can flag, that he/she has published.
if not timestamp:
timestamp = event.flags.filter(status__code='publishing_facebook').last().timestamp
logger.info('Detected published_facebook state of Event %s', event)
event.set_flag(status='published_facebook', timestamp=timestamp)
if published_web.exists() and published_facebook.exists():
# All publishers have published,
# so we can flag the complete published state.
timestamp = pub_flags.last().timestamp
logger.info('Detected general published state of Event %s', event)
event.set_flag(status='published', timestamp=timestamp)
elif publishing_web.exists() and publishing_facebook.exists():
# Event is not due to be published yet,
# but all publishers have confirmed the publication date,
# so we set a general publishing flag.
logger.info('Detected publishing state of Event %s', event)
flags = event.flags.filter(status_code__in=('publishing_web', 'publishing_facebook'))
timestamp = flags.order_by('timestamp').last().timestamp
event.set_flag(status='publishing', timestamp=timestamp)
if code in (None, 'expired'):
# Check if event is expired now and need a expired flag.
if not event.flags.filter(status__code='expired').exists():
expired_at = None
if event.alt_last_day:
if event.alt_last_day < today:
expired_at = event.alt_last_day
elif event.last_day:
if event.last_day < today:
expired_at = event.last_day
elif event.alt_first_day:
if event.alt_first_day < today:
expired_at = event.alt_first_day
elif event.first_day and event.first_day < today:
expired_at = event.first_day
if expired_at:
logger.info('Detected expired state of Event %s', event)
timestamp = timezone.make_aware(datetime.datetime.combine(expired_at, midnight))
event.set_flag(status='expired', timestamp=timestamp)
@classmethod
def get_status_flags(cls, event):
cls.status_code_update(event)
last_flag = event.flags.last()
if not last_flag:
#last_flag = event.set_flag('void')
return []
flags = [last_flag]
last_status = last_flag.status
if last_status.code.startswith('publishing_'):
flags += event.flags.filter(status__code='accepted')
elif last_status.code.startswith('published_'):
if event.is_flagged('publishing'):
flags += event.flags.filter(status__code='publishing')
else:
flags += event.flags.filter(status__code='accepted')
return flags
@classmethod
def get_number(cls, event):
if event.number and event.flags.filter(status__code='accepted').exists():
return event.number
else:
return None
#
# Permissions
#
@classmethod
def has_global_permission(cls, user, permission):
if user.is_superuser:
return True
if permission == 'export':
return has_role(user, 'publisher')
return False
@classmethod
def has_object_permission(cls, user, permission, obj):
if user.is_superuser:
return True
if permission == 'view':
if user == obj.owner:
return True
if has_role(user, 'manager_super'):
return True
if has_role(user, 'manager_{}'.format(obj.sport.lower())):
return True
if has_role(user, 'publisher') and obj.is_flagged('accepted'):
return True
elif permission == 'submit':
if user == obj.owner:
return True
elif permission == 'accept':
if has_role(user, 'manager_super'):
return True
if has_role(user, 'manager_{}'.format(obj.sport.lower())):
return True
elif permission == 'publish':
if has_role(user, 'publisher'):
return True
elif permission == 'update':
if not obj.is_flagged('submitted'):
if user == obj.owner:
return True
elif not obj.is_flagged('accepted'):
if has_role(user, 'manager_super'):
return True
if has_role(user, 'manager_{}'.format(obj.sport.lower())):
return True
elif has_role(user, 'publisher'):
return True
return False
#
# Signal handlers
#
@classmethod
def send_emails_on_event_update(cls, sender, **kwargs):
event = kwargs.get('event')
diff = kwargs.get('diff')
updater = kwargs.get('user')
app_config = apps.get_containing_app_config(__package__)
if not app_config.settings.enable_email_on_update:
return
if len(diff) < 1:
logger.debug('send_emails_on_event_update(): No diff data -> Skip sending mails.')
return
diff_text = '\n'.join(diff[3:])
# Who should be informed about the update?
recipients = [event.owner]
if event.is_flagged('submitted'):
# If the event is already submitted, add managers to the recipients.
recipients += get_users_by_role('manager_super')
recipients += get_users_by_role('manager_{}'.format(event.sport.lower()))
if event.is_flagged('accepted'):
# If the event is already published, add publishers to the recipients.
recipients += get_users_by_role('publisher_web')
recipients += get_users_by_role('publisher_facebook')
for recipient in recipients:
if recipient.email and recipient.email != updater.email:
email = emails.EventUpdatedMail(recipient=recipient, event=event, editor=updater, diff=diff_text)
email.send()
@classmethod
def send_emails_on_event_status_update(cls, sender, **kwargs):
event = kwargs.get('event')
flag = kwargs.get('flag')
updater = flag.user
app_config = apps.get_containing_app_config(__package__)
if not app_config.settings.enable_email_on_status_update:
return
if flag.status.code == 'submitted':
# Inform event owner about his event (so he can keep the mail as a reminder for the event).
if event.owner.email:
email = emails.EventSubmittedMail(recipient=event.owner, event=event)
email.send()
# Inform managers that they have to accept the event.
# Also create OneClickActions for all of them and add the link to the mail,
# so they can accept the event with a click into the mail.
recipients = get_users_by_role('manager_super')
recipients += get_users_by_role('manager_{}'.format(event.sport.lower()))
OneClickAction = app_config.get_model('OneClickAction')
for recipient in recipients:
if recipient.email:
action = OneClickAction(command='EVENT_STATUS_UPDATE')
action.parameters = '{event},accepted,{user}'.format(event=event.id, user=recipient.id)
action.save()
email = emails.EventToAcceptMail(recipient=recipient, event=event, accept_action=action)
email.send()
elif flag.status.code == 'accepted':
# Inform event owner about the acceptance of his event.
if event.owner.email:
email = emails.EventAcceptedMail(recipient=event.owner, event=event, editor=updater)
email.send()
# Inform publishers that they have to publish the event.
# Also create OneClickActions for all of them and add the link to the mail,
# so they can confirm the publication with a click into the mail.
# Website
recipients = get_users_by_role('publisher_web')
status_code = 'publishing_web'
OneClickAction = app_config.get_model('OneClickAction')
for recipient in recipients:
if recipient.email:
action = OneClickAction(command='EVENT_STATUS_UPDATE')
action.parameters = '{event},{status_code},{user}'.format(event=event.id,
status_code=status_code,
user=recipient.id)
action.save()
email = emails.EventToPublishWebMail(recipient=recipient, event=event, editor=updater,
confirm_publication_action=action)
email.send()
# Facebook
recipients = get_users_by_role('publisher_facebook')
status_code = 'publishing_facebook'
OneClickAction = app_config.get_model('OneClickAction')
for recipient in recipients:
if recipient.email:
action = OneClickAction(command='EVENT_STATUS_UPDATE')
action.parameters = '{event},{status_code},{user}'.format(event=event.id,
status_code=status_code,
user=recipient.id)
action.save()
email = emails.EventToPublishFacebookMail(recipient=recipient, event=event, editor=updater,
confirm_publication_action=action)
email.send()
#
# Misc logic
#
@classmethod
def plan_publication(cls, first_day, deadline=None):
app_config = apps.get_containing_app_config(__package__)
if deadline:
publication_deadline = deadline - datetime.timedelta(app_config.settings.publish_before_deadline_days)
else:
publication_deadline = first_day - datetime.timedelta(app_config.settings.publish_before_begin_days)
for year in (today.year, today.year + 1):
for issue in app_config.settings.publish_issues:
if not ('issue' in issue and 'release' in issue and 'deadline' in issue):
logger.error('workflow.plan_publication(): invalid configured issue.')
continue
issue_release = datetime.date(year, issue['release'][1], issue['release'][0])
if issue_release < today:
continue
issue_deadline = datetime.date(year, issue['deadline'][1], issue['deadline'][0])
if issue_deadline > issue_release:
issue_deadline = datetime.date(year - 1, issue['deadline'][1], issue['deadline'][0])
if publication_deadline > issue_release and today <= issue_deadline:
return issue_release, u'%s/%s' % (issue['issue'], year)
return None, None
workflow = BasicWorkflow