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