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)')
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)
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