Example #1
0
    def test_get_time_zones_as_tuple(self):
        """
        Makes sure the time zones are returned as tuples and that eastern is first
        """

        time_zones = get_time_zones(return_as_tuple=True)

        self.assertEqual(time_zones[0][0], 'US/Eastern')
        self.assertEqual(time_zones[0][1], 'US/Eastern (EST) (GMT -5)')
Example #2
0
    def test_get_time_zones(self):
        """
        Makes sure the time zones are returned as dicts and that eastern is first
        """

        time_zones = get_time_zones()

        self.assertEqual(time_zones[0]['id'], 'US/Eastern')
        self.assertEqual(time_zones[0]['name'], 'US/Eastern (EST) (GMT -5)')
        self.assertTrue(len(time_zones) > 400)
Example #3
0
class RecurrenceForm(forms.Form):
    """
    Handles submission of data for populating rrule objects. The field names are based on the rrule
    params defined here http://dateutil.readthedocs.io/en/stable/rrule.html
    """

    # Date from which the recurrence will be started from. This might not always be the first recurrence in the series
    dtstart = forms.DateField(
        error_messages={'required': 'Starts on is required'}, )

    # The hour for each recurrence (0-23)
    byhour = forms.IntegerField(
        error_messages={'required': 'Time is required'}, )

    # The time zone which the submitted time is treated as. The submitted time is converted to utc
    time_zone = forms.ChoiceField(
        choices=get_time_zones(return_as_tuple=True),
        error_messages={'required': 'Time Zone is required'},
    )

    # Type of interval - daily, weekly, etc
    freq = forms.ChoiceField(
        choices=FREQ_CHOICES,
        error_messages={'required': 'Frequency is required'},
    )

    # How often does the event repeat? This is related to the repeats value.
    interval = forms.IntegerField()

    # Day checkboxes, required if frequency is weekly. json encoded list of day numbers ex: [0,2,4]
    byweekday = forms.CharField(required=False)

    # Required if frequency is monthly and day of week. json encoded list of nth day data ex: [[1,-2]]
    bynweekday = forms.CharField(required=False)

    # Only required to monthly frequency
    repeat_by = forms.ChoiceField(choices=REPEAT_BY_CHOICES, required=False)

    # Choice of how the recurrence can be ended
    ends = forms.ChoiceField(choices=ENDS_CHOICES)

    # Number of times the recurrence will occur. Only required if ends is set to AFTER
    count = forms.IntegerField(required=False)

    # Date when the recurrence will end. Only required if ends is set to ON. The 'until' date might not be the last
    # recurrence date
    until = forms.DateField(required=False)

    def __init__(self, *args, **kwargs):
        # Remove the instance param if it exists. Model forms will try to pass this, but it isn't used here and will
        # cause the base form init to fail
        kwargs.pop('instance', None)

        super(RecurrenceForm, self).__init__(*args, **kwargs)

    def clean_freq(self):
        """
        Make sure the frequency is an integer
        """
        return int(self.data.get('freq', -1))

    def clean(self):
        """
        Perform additional form validation based on submitted params
        """
        cleaned_data = super(RecurrenceForm, self).clean()

        if self.errors:
            return cleaned_data

        # Check if count is required
        if self.cleaned_data[
                'ends'] == RecurrenceEnds.AFTER and not self.cleaned_data[
                    'count']:
            raise ValidationError('Number of occurrences is required')

        # Check if until is required
        if self.cleaned_data[
                'ends'] == RecurrenceEnds.ON and not self.cleaned_data['until']:
            raise ValidationError('Ending date is required')

        # Unset until date if end ON is not selected
        if self.cleaned_data.get('ends') != RecurrenceEnds.ON:
            self.cleaned_data['until'] = ''

        # Check end date is after start date
        if self.cleaned_data.get('until') and self.cleaned_data.get(
                'until') <= self.cleaned_data.get('dtstart'):
            raise ValidationError('End date must be after the start date')

        # Check if byweekday is required
        if self.cleaned_data[
                'freq'] == rrule.WEEKLY and not self.cleaned_data['byweekday']:
            raise ValidationError('At least one day choice is required')

        # Check if repeat_by is required
        if self.cleaned_data[
                'freq'] == rrule.MONTHLY and not self.cleaned_data.get(
                    'repeat_by'):
            raise ValidationError('Repeat by is required')

        return cleaned_data

    def clean_byweekday(self):
        """
        Decode the byweekday option
        """
        value = []

        try:
            value = json.loads(self.data.get('byweekday', '[]'))
        except ValueError:
            pass

        return value

    def clean_bynweekday(self):
        """
        Decode the bynweekday option
        """
        value = []

        try:
            value = json.loads(self.data.get('bynweekday', '[]'))
        except ValueError:
            pass

        return value

    def save(self, **kwargs):
        """
        Saves the RRule model and returns it
        """
        rrule_freq = self.cleaned_data['freq']
        start_date = self.cleaned_data['dtstart']

        # Convert date to datetime
        start_datetime = datetime.combine(start_date, datetime.min.time())

        # Build params for rrule object
        params = {
            'freq': rrule_freq,
            'dtstart': start_datetime,
            'byhour': self.cleaned_data.get('byhour'),
        }

        params['interval'] = self.cleaned_data.get('interval', 1)

        if self.cleaned_data.get('count'):
            params['count'] = self.cleaned_data.get('count')

        if self.cleaned_data.get('until'):
            # Convert date to datetime
            until = datetime.combine(self.cleaned_data.get('until'),
                                     datetime.min.time())

            # Add hour to until datetime if it exists
            until = until.replace(hour=params['byhour'])
            params['until'] = until

        # Add day choices
        if self.cleaned_data.get('freq') == rrule.WEEKLY:
            params['byweekday'] = self.cleaned_data.get('byweekday')

        # Add repeat by choices
        if self.cleaned_data.get('freq') == rrule.MONTHLY:
            if self.cleaned_data.get('repeat_by') == 'DAY_OF_THE_MONTH':
                params['bymonthday'] = start_datetime.day
            else:
                params['byweekday'] = self.cleaned_data['bynweekday'][0][0]
                params['bysetpos'] = self.cleaned_data['bynweekday'][0][1]

        # Create or update the rule
        rrule_model = kwargs.get('recurrence') or RRule()
        rrule_model.rrule_params = params
        rrule_model.time_zone = self.cleaned_data.get('time_zone')
        for key, value in kwargs.items():
            if hasattr(rrule_model, key):
                # This try except is because some field names might be reverse foreign key relationships
                try:
                    setattr(rrule_model, key, value)
                except TypeError:  # pragma: no cover
                    pass

        rrule_model.save()

        # Return the rule
        return rrule_model