示例#1
0
    def _convert_to_recurring_event(self, updates, original):
        """Convert a single event to a series of recurring events"""
        # Convert the single event to a series of recurring events
        merged = copy.deepcopy(original)
        merged.update(updates)
        generated_events = generate_recurring_events(merged)

        # Remove the first element in the list (the current event being updated)
        # And update the start/end dates to be in line with the new recurring rules
        updated_event = generated_events.pop(0)
        updates['dates']['start'] = updated_event['dates']['start']
        updates['dates']['end'] = updated_event['dates']['end']

        # Create the new events and generate their history
        self.create(generated_events)
        app.on_inserted_events(generated_events)
        return generated_events
示例#2
0
    def _convert_to_recurring_event(self, updates, original):
        """Convert a single event to a series of recurring events"""
        self._validate_convert_to_recurring(updates, original)
        updates['recurrence_id'] = generate_guid(type=GUID_NEWSML)

        merged = copy.deepcopy(original)
        merged.update(updates)

        # Generated new events will be "draft"
        merged[ITEM_STATE] = WORKFLOW_STATE.DRAFT

        generated_events = generate_recurring_events(merged)
        updated_event = generated_events.pop(0)

        # Check to see if the first generated event is different from original
        # If yes, mark original as rescheduled with generated recurrence_id
        if updated_event['dates']['start'].date(
        ) != original['dates']['start'].date():
            # Reschedule original event
            updates['update_method'] = UPDATE_SINGLE
            event_reschedule_service = get_resource_service(
                'events_reschedule')
            updates['dates'] = updated_event['dates']
            set_planning_schedule(updates)
            event_reschedule_service.update_single_event(updates, original)
            if updates.get('state') == WORKFLOW_STATE.RESCHEDULED:
                history_service = get_resource_service('events_history')
                history_service.on_reschedule(updates, original)
        else:
            # Original event falls as a part of the series
            # Remove the first element in the list (the current event being updated)
            # And update the start/end dates to be in line with the new recurring rules
            updates['dates']['start'] = updated_event['dates']['start']
            updates['dates']['end'] = updated_event['dates']['end']
            set_planning_schedule(updates)
            remove_lock_information(item=updates)

        # Create the new events and generate their history
        self.create(generated_events)
        app.on_inserted_events(generated_events)
        return generated_events
    def update_recurring_events(self, updates, original, update_method):
        remove_lock_information(updates)

        rules_changed = updates['dates']['recurring_rule'] != original['dates']['recurring_rule']
        times_changed = updates['dates']['start'] != original['dates']['start'] or \
            updates['dates']['end'] != original['dates']['end']
        reason = updates.pop('reason', None)

        events_service = get_resource_service('events')
        historic, past, future = self.get_recurring_timeline(original, postponed=True)

        # Determine if the selected event is the first one, if so then
        # act as if we're changing future events
        if len(historic) == 0 and len(past) == 0:
            update_method = UPDATE_FUTURE

        if update_method == UPDATE_FUTURE:
            rescheduled_events = [original] + future
            new_start_date = updates['dates']['start']
            original_start_date = original['dates']['start']
            original_rule = original['dates']['recurring_rule']
        else:
            rescheduled_events = past + [original] + future

            # Assign the date from the beginning of the new series
            new_start_date = updates['dates']['start']
            original_start_date = past[0]['dates']['start']
            original_rule = past[0]['dates']['recurring_rule']

        updated_rule = deepcopy(updates['dates']['recurring_rule'])
        if updated_rule['endRepeatMode'] == 'count':
            num_events = len(historic) + len(past) + len(future) + 1
            updated_rule['count'] -= num_events - len(rescheduled_events)

        # Compute the difference between start and end in the updated event
        time_delta = updates['dates']['end'] - updates['dates']['start']

        # Generate the dates for the new event series
        new_dates = [date for date in islice(generate_recurring_dates(
            start=new_start_date,
            tz=updates['dates'].get('tz') and pytz.timezone(updates['dates']['tz'] or None),
            date_only=True,
            **updated_rule
        ), 0, 200)]

        # Generate the dates for the original events
        original_dates = [date for date in islice(generate_recurring_dates(
            start=original_start_date,
            tz=original['dates'].get('tz') and pytz.timezone(original['dates']['tz'] or None),
            date_only=True,
            **original_rule
        ), 0, 200)]

        self.set_next_occurrence(updates)

        dates_processed = []

        # Iterate over the current events in the series and delete/spike
        # or update the event accordingly
        deleted_events = {}
        for event in rescheduled_events:
            if event[config.ID_FIELD] == original[config.ID_FIELD]:
                event_date = updates['dates']['start'].replace(tzinfo=None).date()
            else:
                event_date = event['dates']['start'].replace(tzinfo=None).date()
            # If the event does not occur in the new dates, then we need to either
            # delete or spike this event
            if event_date not in new_dates:
                # Add it to the list of events to delete or spike
                # This is done later so that we can perform a single
                # query against mongo, rather than one per deleted event
                deleted_events[event[config.ID_FIELD]] = event

            # If the date has already been processed, then we should mark this event for deletion
            # This occurs when the selected Event is being updated to an Event that already exists
            # in another Event in the series.
            # This stops multiple Events to occur on the same day
            elif event_date in new_dates and event_date in dates_processed:
                deleted_events[event[config.ID_FIELD]] = event

            # Otherwise this Event does occur in the new dates
            else:
                # Because this Event occurs in the new dates, then we are not to set the state to 'rescheduled',
                # instead we set it to either 'scheduled' (if public) or 'draft' (if not public)
                new_state = WORKFLOW_STATE.SCHEDULED if event.get('pubstatus') else WORKFLOW_STATE.DRAFT

                # If this is the selected Event, then simply update the fields and
                # Reschedule associated Planning items
                if event[config.ID_FIELD] == original[config.ID_FIELD]:
                    self._mark_event_rescheduled(updates, original, reason, True)
                    updates['state'] = new_state
                    self._reschedule_event_plannings(event, reason, state=WORKFLOW_STATE.DRAFT)

                else:
                    new_updates = {
                        'reason': reason,
                        'skip_on_update': True
                    }
                    self._mark_event_rescheduled(new_updates, event, reason)
                    new_updates['state'] = new_state

                    # Update the 'start', 'end' and 'recurring_rule' fields of the Event
                    if rules_changed or times_changed:
                        new_updates['state'] = new_state
                        new_updates['dates'] = event['dates']
                        new_updates['dates']['start'] = datetime.combine(event_date, updates['dates']['start'].time())
                        new_updates['dates']['end'] = new_updates['dates']['start'] + time_delta
                        new_updates['dates']['recurring_rule'] = updates['dates']['recurring_rule']
                        self.set_planning_schedule(new_updates)

                    # And finally update the Event, and Reschedule associated Planning items
                    self.patch(event[config.ID_FIELD], new_updates)
                    self._reschedule_event_plannings(event, reason, state=WORKFLOW_STATE.DRAFT)
                    app.on_updated_events_reschedule(new_updates, {'_id': event[config.ID_FIELD]})

                # Mark this date as being already processed
                dates_processed.append(event_date)

        # Create new events that do not fall on the original occurrence dates
        new_events = []
        for date in new_dates:
            # If the new date falls on the original occurrences, or if the
            # start date of the selected one, then skip this date occurrence
            if date in original_dates or date in dates_processed:
                continue

            # Create a copy of the metadata to use for the new event
            new_event = deepcopy(original)
            new_event.update(deepcopy(updates))

            # Remove fields not required by the new events
            for key in list(new_event.keys()):
                if key.startswith('_'):
                    new_event.pop(key)
                elif key.startswith('lock_'):
                    new_event.pop(key)

            # Set the new start and end dates, as well as the _id and guid fields
            new_event['dates']['start'] = datetime.combine(date, updates['dates']['start'].time())
            new_event['dates']['end'] = new_event['dates']['start'] + time_delta
            new_event[config.ID_FIELD] = new_event['guid'] = generate_guid(type=GUID_NEWSML)
            new_event.pop('reason', None)
            self.set_planning_schedule(new_event)

            # And finally add this event to the list of events to be created
            new_events.append(new_event)

        # Now iterate over the new events and create them
        if new_events:
            events_service.create(new_events)
            app.on_inserted_events(new_events)

        # Iterate over the events to delete/spike
        self._set_events_planning(deleted_events)

        for event in deleted_events.values():
            event_plans = event.get('_plans', [])
            is_original = event[config.ID_FIELD] == original[config.ID_FIELD]
            if len(event_plans) > 0 or event.get('pubstatus', None) is not None:
                if is_original:
                    self._mark_event_rescheduled(updates, original, reason)
                else:
                    # This event has Planning items, so spike this event and
                    # all Planning items
                    new_updates = {
                        'skip_on_update': True,
                        'reason': reason
                    }
                    self._mark_event_rescheduled(new_updates, original, reason)
                    self.patch(event[config.ID_FIELD], new_updates)

                if len(event_plans) > 0:
                    self._reschedule_event_plannings(original, reason, event_plans)
            else:
                # This event has no Planning items, therefor we can safely
                # delete this event
                events_service.delete_action(lookup={'_id': event[config.ID_FIELD]})
                app.on_deleted_item_events(event)

                if is_original:
                    updates['_deleted'] = True
示例#4
0
    def _reschedule_recurring_events(self, updates, original, update_method,
                                     events_service):
        original_deleted = False
        rules_changed = updates['dates']['recurring_rule'] != original[
            'dates']['recurring_rule']
        # Release the Lock on the selected Event
        updates.update({
            LOCK_USER: None,
            LOCK_SESSION: None,
            'lock_time': None,
            'lock_action': None
        })

        historic, past, future = events_service.get_recurring_timeline(
            original)

        # Determine if the selected event is the first one, if so then
        # act as if we're changing future events
        if len(historic) == 0 and len(past) == 0:
            update_method = UPDATE_FUTURE

        if update_method == UPDATE_FUTURE:
            rescheduled_events = [original] + future
            new_start_date = updates['dates']['start']
            original_start_date = original['dates']['start']
            original_rule = original['dates']['recurring_rule']
        else:
            rescheduled_events = past + [original] + future

            # Assign the date from the beginning of the new series
            new_start_date = past[0]['dates']['start'] + \
                (updates['dates']['start'] - original['dates']['start'])
            original_start_date = past[0]['dates']['start']
            original_rule = past[0]['dates']['recurring_rule']

        updated_rule = deepcopy(updates['dates']['recurring_rule'])
        if updated_rule['endRepeatMode'] == 'count':
            num_events = len(historic) + len(past) + len(future) + 1
            updated_rule['count'] -= num_events - len(rescheduled_events)

        # Compute the difference between start and end in the updated event
        time_delta = updates['dates']['end'] - updates['dates']['start']

        # Generate the dates for the new event series
        new_dates = [
            date for date in islice(
                generate_recurring_dates(
                    start=new_start_date,
                    tz=updates['dates'].get('tz')
                    and pytz.timezone(updates['dates']['tz'] or None),
                    **updated_rule), 0, 200)
        ]

        # Generate the dates for the original events
        original_dates = [
            date for date in islice(
                generate_recurring_dates(
                    start=original_start_date,
                    tz=original['dates'].get('tz')
                    and pytz.timezone(original['dates']['tz'] or None),
                    **original_rule), 0, 200)
        ]

        set_next_occurrence(updates)

        # Iterate over the current events in the series and delete/spike
        # or update the event accordingly
        deleted_events = {}
        for event in rescheduled_events:
            # If the event does not occur in the new dates, then we need to either
            # delete or spike this event
            if event['dates']['start'].replace(tzinfo=None) not in new_dates:
                # Add it to the list of events to delete or spike
                # This is done later so that we can perform a single
                # query against mongo, rather than one per deleted event
                deleted_events[event[config.ID_FIELD]] = event

            # Otherwise if this Event does occur in the new dates, and the recurring rules have
            # changed, then make sure this event has the new recurring rules applied.
            # This can occur when extending a series where the original Events are kept and only
            # new events are created.
            elif rules_changed and event[config.ID_FIELD] != original[
                    config.ID_FIELD]:
                new_updates = {'dates': event['dates']}
                new_updates['dates']['recurring_rule'] = updates['dates'][
                    'recurring_rule']
                events_service.patch(event[config.ID_FIELD], new_updates)
                app.on_updated_events_reschedule(
                    new_updates, {'_id': event[config.ID_FIELD]})

        # Create new events that do not fall on the original occurrence dates
        new_events = []
        for date in new_dates:
            # If the new date falls on the original occurrences, or is the
            # start date of the selected one, then skip this date occurrence
            if date in original_dates:
                continue

            # Create a copy of the metadata to use for the new event
            new_event = deepcopy(original)
            new_event.update(deepcopy(updates))

            # Remove fields not required by the new events
            for key in list(new_event.keys()):
                if key.startswith('_'):
                    new_event.pop(key)
                elif key.startswith('lock_'):
                    new_event.pop(key)

            # Set the new start and end dates, as well as the _id and guid fields
            new_event['dates']['start'] = date
            new_event['dates']['end'] = date + time_delta
            new_event[config.ID_FIELD] = new_event['guid'] = generate_guid(
                type=GUID_NEWSML)
            new_event.pop('reason', None)

            # And finally add this event to the list of events to be created
            new_events.append(new_event)

        # Now iterate over the new events and create them
        if new_events:
            events_service.create(new_events)
            app.on_inserted_events(new_events)

        # Iterate over the events to delete/spike
        self._set_events_planning(deleted_events)

        for event in deleted_events.values():
            event_plans = event.get('_plans', [])
            is_original = event[config.ID_FIELD] == original[config.ID_FIELD]
            if len(event_plans) > 0 or event.get('pubstatus',
                                                 None) is not None:
                if is_original:
                    self._mark_event_rescheduled(updates, original)
                else:
                    # This event has Planning items, so spike this event and
                    # all Planning items
                    new_updates = {
                        'skip_on_update': True,
                        'reason': updates.get('reason', None)
                    }
                    self._mark_event_rescheduled(new_updates, original)
                    self.patch(event[config.ID_FIELD], new_updates)

                if len(event_plans) > 0:
                    self._reschedule_event_plannings(updates, original,
                                                     event_plans)
            else:
                # This event has no Planning items, therefor we can safely
                # delete this event
                events_service.delete_action(
                    lookup={'_id': event[config.ID_FIELD]})
                app.on_deleted_item_events(event)

                if is_original:
                    original_deleted = True

        return not original_deleted
示例#5
0
    def _update_recurring_events(self, updates, original):
        """Method to update recurring events.

        If the recurring_rule has been removed for this event, process
        it separately, otherwise update the event and/or its recurring rules
        """
        merged = copy.deepcopy(original)
        merged.update(updates)

        if updates.get('dates'):
            if not updates['dates'].get('recurring_rule', None):
                # Recurring rule has been removed for this event,
                # Remove this rule and return from this method
                self._remove_recurring_rules(updates, original)
                push_notification(
                    'events:updated',
                    item=str(original[config.ID_FIELD]),
                    user=str(updates.get('version_creator'))
                )
                return

            # Generate the list of changes to the series of events based on the
            # new recurring_rules
            new_events, updated_events, deleted_events = self._update_recurring_rules(updates, original)
        else:
            # We only update events here, so get the list of future events
            updated_events = self._get_future_events(merged)
            new_events = deleted_events = []

        # Create instances for the new events, and save them to mongo/elastic
        # Then fire off events for them
        added_events = []
        for e in new_events:
            event = copy.deepcopy(merged)
            event['dates']['start'] = e['dates']['start']
            event['dates']['end'] = e['dates']['end']
            event['_id'] = event['guid'] = generate_guid(type=GUID_NEWSML)
            added_events.append(event)

        if added_events:
            self.create(added_events)
            app.on_inserted_events(added_events)

        # For all the deleted events, remove them from mongo/elastic
        # Then fire off events for them
        for e in deleted_events:
            self.delete({'_id': e[config.ID_FIELD]})
            app.on_deleted_item_events(e)

        # For all the updated events, update their dates/metadata
        # Then fire off events for them
        for e in updated_events:
            if e[config.ID_FIELD] == original[config.ID_FIELD]:
                continue
            updated_event = copy.deepcopy(updates)

            if 'dates' not in updated_event:
                updated_event['dates'] = original['dates']

            if 'guid' in updated_event:
                del updated_event['guid']

            updated_event['dates']['start'] = e['dates']['start']
            updated_event['dates']['end'] = e['dates']['end']
            updated_event['skip_on_update'] = True
            self.patch(e[config.ID_FIELD], updated_event)
            app.on_updated_events(updated_event, {'_id': e[config.ID_FIELD]})

        # And finally push a notification to connected clients
        push_notification(
            'events:updated:recurring',
            item=str(original[config.ID_FIELD]),
            recurrence_id=str(original['recurrence_id']),
            user=str(updates.get('version_creator', ''))
        )