示例#1
0
class EventRelation(with_metaclass(ModelBase, *get_model_bases())):
    '''
    This is for relating data to an Event, there is also a distinction, so that
    data can be related in different ways.  A good example would be, if you have
    events that are only visible by certain users, you could create a relation
    between events and users, with the distinction of 'visibility', or
    'ownership'.

    event: a foreign key relation to an Event model.
    content_type: a foreign key relation to ContentType of the generic object
    object_id: the id of the generic object
    content_object: the generic foreign key to the generic object
    distinction: a string representing a distinction of the relation, User could
    have a 'viewer' relation and an 'owner' relation for example.

    DISCLAIMER: while this model is a nice out of the box feature to have, it
    may not scale well.  If you use this keep that in mind.
    '''
    event = models.ForeignKey(Event, on_delete=models.CASCADE, verbose_name=_("event"))
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.IntegerField()
    content_object = fields.GenericForeignKey('content_type', 'object_id')
    distinction = models.CharField(_("distinction"), max_length=20, null=True)

    objects = EventRelationManager()

    class Meta(object):
        verbose_name = _("event relation")
        verbose_name_plural = _("event relations")
        app_label = 'schedule'

    def __str__(self):
        return '%s(%s)-%s' % (self.event.title, self.distinction, self.content_object)
示例#2
0
    def test_get_model_bases_with_custom_dict_specific(self):
        model_mock = mock.Mock()
        expected_result = [model_mock]

        self.import_string_mock.return_value = model_mock
        actual_result = get_model_bases('ClassName')

        self.assertListEqual(actual_result, expected_result)

        self.import_string_mock.assert_called_once_with('path.to.module.AbstractClass')
示例#3
0
    def test_get_model_bases_with_custom_dict_specific(self):
        model_mock = mock.Mock()
        expected_result = [model_mock]

        self.import_string_mock.return_value = model_mock
        actual_result = get_model_bases('ClassName')

        self.assertListEqual(actual_result, expected_result)

        self.import_string_mock.assert_called_once_with(
            'path.to.module.AbstractClass')
示例#4
0
    def test_get_model_bases_with_custom_list(self):
        model_mock1 = mock.Mock()
        model_mock2 = mock.Mock()

        expected_result = [model_mock1, model_mock2]

        self.import_string_mock.side_effect = [model_mock1, model_mock2]
        actual_result = get_model_bases('ClassName')

        self.assertListEqual(actual_result, expected_result)

        self.import_string_mock.assert_any_call('path.to.module.AbstractClass1')
        self.import_string_mock.assert_any_call('path.to.module.AbstractClass2')
        self.assertEqual(self.import_string_mock.call_count, 2)
示例#5
0
    def test_get_model_bases_with_custom_list(self):
        model_mock1 = mock.Mock()
        model_mock2 = mock.Mock()

        expected_result = [model_mock1, model_mock2]

        self.import_string_mock.side_effect = [model_mock1, model_mock2]
        actual_result = get_model_bases('ClassName')

        self.assertListEqual(actual_result, expected_result)

        self.import_string_mock.assert_any_call(
            'path.to.module.AbstractClass1')
        self.import_string_mock.assert_any_call(
            'path.to.module.AbstractClass2')
        self.assertEqual(self.import_string_mock.call_count, 2)
示例#6
0
class CalendarRelation(
        with_metaclass(ModelBase, *get_model_bases('CalendarRelation'))):
    '''
    This is for relating data to a Calendar, and possible all of the events for
    that calendar, there is also a distinction, so that the same type or kind of
    data can be related in different ways.  A good example would be, if you have
    calendars that are only visible by certain users, you could create a
    relation between calendars and users, with the distinction of 'visibility',
    or 'ownership'.  If inheritable is set to true, all the events for this
    calendar will inherit this relation.

    calendar: a foreign key relation to a Calendar object.
    content_type: a foreign key relation to ContentType of the generic object
    object_id: the id of the generic object
    content_object: the generic foreign key to the generic object
    distinction: a string representing a distinction of the relation, User could
    have a 'veiwer' relation and an 'owner' relation for example.
    inheritable: a boolean that decides if events of the calendar should also
    inherit this relation

    DISCLAIMER: while this model is a nice out of the box feature to have, it
    may not scale well.  If you use this, keep that in mind.
    '''

    calendar = models.ForeignKey(Calendar,
                                 on_delete=models.CASCADE,
                                 verbose_name=_("calendar"))
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.IntegerField()
    content_object = fields.GenericForeignKey('content_type', 'object_id')
    distinction = models.CharField(_("distinction"), max_length=20)
    inheritable = models.BooleanField(_("inheritable"), default=True)

    objects = CalendarRelationManager()

    class Meta(object):
        verbose_name = _('calendar relation')
        verbose_name_plural = _('calendar relations')
        app_label = 'schedule'

    def __str__(self):
        return '%s - %s' % (self.calendar, self.content_object)
示例#7
0
class Occurrence(with_metaclass(ModelBase, *get_model_bases('Occurrence'))):

    #ORIGINAL
    event = models.ForeignKey(Event,
                              on_delete=models.CASCADE,
                              verbose_name=_("event"))
    title = models.CharField(_("Title"), max_length=255, blank=True)
    start = models.DateTimeField(_("Start"), db_index=True)
    end = models.DateTimeField(_("End"), db_index=True)
    cancelled = models.BooleanField(_("cancelled"), default=False)
    original_start = models.DateTimeField(_("original start"))
    original_end = models.DateTimeField(_("original end"))
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    updated_on = models.DateTimeField(_("updated on"), auto_now=True)

    #BOOKING
    reservation_spots = models.IntegerField(_("Reservation Spots"), null=True)
    spots_free = models.IntegerField(null=True, default=35)
    description = models.TextField(_("description"), blank=True)

    #PRICING
    price = models.DecimalField(_("price"),
                                max_digits=100,
                                decimal_places=2,
                                default=0.00)
    sale_price = models.DecimalField(_("Sale Price"),
                                     decimal_places=2,
                                     max_digits=20,
                                     null=True,
                                     blank=True)

    #QUERYING BY DATE
    guide = models.ForeignKey('tour.Guide',
                              blank=True,
                              null=True,
                              related_name='guide')

    #GOOGLE MAPS
    latitude = models.DecimalField(_("latitude"),
                                   max_digits=50,
                                   decimal_places=10,
                                   blank=True,
                                   null=True,
                                   db_index=True)
    longitude = models.DecimalField(_("longitude"),
                                    max_digits=50,
                                    decimal_places=10,
                                    blank=True,
                                    null=True,
                                    db_index=True)
    tour_type = models.CharField(_("tour_type"), max_length=255, blank=True)
    tour_icon = models.CharField(_("tour_icon"),
                                 max_length=255,
                                 blank=True,
                                 null=True)
    sail = models.NullBooleanField(_('sail'), default=False)
    bike = models.NullBooleanField(_('bike'), default=False)
    trail = models.NullBooleanField(_('trail'), default=False)

    #TOURS SEARCH BY RADIUS

    class Meta(object):
        verbose_name = _("occurrence")
        verbose_name_plural = _("occurrences")
        app_label = 'schedule'
        index_together = (('start', 'end'), ('latitude', 'longitude'))

    def __init__(self, *args, **kwargs):
        super(Occurrence, self).__init__(*args, **kwargs)
        if not self.title and self.event_id:
            self.title = self.event.title
        if not self.description and self.event_id:
            self.description = self.event.description
        if not self.reservation_spots and self.event_id:
            self.reservation_spots = self.event.reservation_spots
        if not self.spots_free and self.event.id:
            self.spots_free = self.event.reservation_spots
        if not self.latitude and self.event.id:
            self.latitude = self.event.latitude
        if not self.longitude and self.event.id:
            self.longitude = self.event.longitude
        if not self.tour_type and self.event.id:
            self.tour_type = self.event.longitude
        if not self.price and self.event_id:
            self.price = self.event.price
        if not self.sale_price and self.event_id:
            self.sale_price = self.event.sale_price
        if not self.tour_icon and self.event.tour_type:
            if self.event.tour_type == "Boat":
                self.tour_icon = "sail"
                self.sail = True
            elif self.event.tour_type == "Bike":
                self.tour_icon = "hike"
                self.bike = True
            else:
                self.tour_icon = "trail"
                self.trail = True

    def moved(self):
        return self.original_start != self.start or self.original_end != self.end

    moved = property(moved)

    def move(self, new_start, new_end):
        self.start = new_start
        self.end = new_end
        self.save()

    def cancel(self):
        self.cancelled = True
        self.save()

    def uncancel(self):
        self.cancelled = False
        self.save()

    @property
    def seconds(self):
        return (self.end - self.start).total_seconds()

    @property
    def minutes(self):
        return float(self.seconds) / 60

    @property
    def hours(self):
        return float(self.seconds) / 3600

    def get_absolute_url(self):
        if self.pk is not None:
            return reverse('occurrence',
                           kwargs={
                               'occurrence_id': self.pk,
                               'event_id': self.event.id
                           })
        return reverse('occurrence_by_date',
                       kwargs={
                           'event_id': self.event.id,
                           'year': self.start.year,
                           'month': self.start.month,
                           'day': self.start.day,
                           'hour': self.start.hour,
                           'minute': self.start.minute,
                           'second': self.start.second,
                       })

    @property
    def get_cancel_url(self):
        if self.pk is not None:
            return reverse('cancel_occurrence',
                           kwargs={
                               'occurrence_id': self.pk,
                               'event_id': self.event.id
                           })
        return reverse('cancel_occurrence_by_date',
                       kwargs={
                           'event_id': self.event.id,
                           'year': self.start.year,
                           'month': self.start.month,
                           'day': self.start.day,
                           'hour': self.start.hour,
                           'minute': self.start.minute,
                           'second': self.start.second,
                       })

    @property
    def get_edit_url(self):
        if self.pk is not None:
            return reverse('edit_occurrence',
                           kwargs={
                               'occurrence_id': self.pk,
                               'event_id': self.event.id
                           })
        return reverse('edit_occurrence_by_date',
                       kwargs={
                           'event_id': self.event.id,
                           'year': self.start.year,
                           'month': self.start.month,
                           'day': self.start.day,
                           'hour': self.start.hour,
                           'minute': self.start.minute,
                           'second': self.start.second,
                       })

    def __str__(self):
        return ugettext(
            "%(start)s to %(end)s with %(spots_free)s/%(spots_total)s left"
        ) % {
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT),
            'spots_free': self.spots_free,
            'spots_total': self.reservation_spots
        }

    def __lt__(self, other):
        return self.end < other.end

    def __eq__(self, other):
        return (isinstance(other, Occurrence)
                and self.original_start == other.original_start
                and self.original_end == other.original_end)

    def get_tour_time(self):
        return ugettext(
            "%(start)s to %(end)s with %(spots_free)s/%(spots_total)s left"
        ) % {
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT),
            'spots_free': self.spots_free,
            'spots_total': self.reservation_spots
        }

    def get_spots_avaliable(self):
        if self.spots_free != 0:
            return ugettext("%(spots_total)s spots avaliable") % {
                'spots_free': self.spots_free,
            }
        else:
            return ugettext("no spots currently avaliable")

    def get_price(self):
        if self.sale_price is not None:
            return self.sale_price
        else:
            return self.price

    def get_html_price(self):
        if self.sale_price is not None:
            html_text = "<span class='sale-price'>%s</span> <span class='og-price'>%s</span>" % (
                self.sale_price, self.price)
        else:
            html_text = "<span class='price'>%s</span>" % (self.price)
        return mark_safe(html_text)

    #_____IN THE WORKS FOR PRICING TOURS________
    def add_to_cart(self):
        return "%s?item=%s&qty=1" % (reverse("cart"), self.id)

    def remove_from_cart(self):
        return "%s?item=%s&qty=1&delete=True" % (reverse("cart"), self.id)

    def get_tour_title(self):
        return "%s" % (self.title)

    #_____IN THE WORKS FOR PRICING TOURS________


#GOOGLE MAPS ONLY FIND A PLACE WITHIN A DISTANCE OF THE TYPED IN LOCATION (NOT WORKING)
# class LocationManager(models.Manager):
#     def nearby_locations(self, latitude=0, longitude=0, radius=10, max_results=100, use_miles=True):
#         if use_miles:
#             distance_unit = 3959
#         else:
#             distance_unit = 6371
#
#         from django.db import connection, transaction
#         cursor = connection.cursor()
#         if django_settings.DATABASE_ENGINE == 'sqlite3':
#             connection.connection.create_function('acos', 1, math.acos)
#             connection.connection.create_function('cos', 1, math.cos)
#             connection.connection.create_function('radians', 1, math.radians)
#             connection.connection.create_function('sin', 1, math.sin)
#
#         sql = """SELECT id, (%f * acos( cos( math.radians(%f) ) * cos( math.radians( latitude ) ) *
#         cos( math.radians( longitude ) - math.radians(%f) ) + sin( math.radians(%f) ) * sin( math.radians( latitude ) ) ) )
#         AS distance FROM schedule_occurrence WHERE distance < %d
#         ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results)
#         cursor.execute(sql)
#         ids = [row[0] for row in cursor.fetchall()]
#
#         return self.filter(id__in=ids)
示例#8
0
class Event(with_metaclass(ModelBase, *get_model_bases('Event'))):
    '''
    This model stores meta data for a date.  You can relate this data to many
    other models.
    '''
    TOUR_FIELDS = [('Boat', 'Boat'), ('Bike', 'Bike'), ('Walk', 'Walk')]
    NUMBER_FIELDS = [(5, 5), (10, 10), (15, 15), (20, 20), (25, 25), (30, 30),
                     (35, 35), (50, 50)]
    start = models.DateTimeField(_("Start"), db_index=True)
    end = models.DateTimeField(
        _("End"),
        db_index=True,
        help_text=_("The end time must be later than the start time."))
    title = models.CharField(_("Title"), max_length=255)
    image = models.ImageField(upload_to=upload_location, null=True, blank=True)
    reservation_spots = models.IntegerField(_("Reservation Spots"), null=True, \
        blank=True, choices=NUMBER_FIELDS,
        help_text=_("How large is your tour group?"),
        db_index=True,)
    price = models.DecimalField(max_digits=100, decimal_places=2, default=0.00)
    sale_price = models.DecimalField(decimal_places=2,
                                     max_digits=20,
                                     null=True,
                                     blank=True)
    #GEOCODING: MAKE SO THAT the POSTSAVERECEIVER SAVES THE SPOT AND THEN PUTS PUTS IT IN A GEOLOCATION POINT.
    city = models.TextField(_("City"), blank=True)
    location = models.CharField(_("Location Point"), null=True, \
        blank=True,
        help_text=_("Where does the tour meetup? Please enter like such. 331 NW 26th St, Corvallis OR, 97330"),
        db_index=True, max_length=200)
    latitude = models.DecimalField(max_digits=50,
                                   decimal_places=10,
                                   blank=True,
                                   null=True)
    longitude = models.DecimalField(max_digits=50,
                                    decimal_places=10,
                                    blank=True,
                                    null=True)
    tour_type = models.CharField(_("Meetup Location"), max_length=255, null=True, \
        blank=True, choices=TOUR_FIELDS, help_text=_("What type of tour is this?"), db_index=True,)
    tour_icon = models.CharField(_("tour_icon"),
                                 max_length=255,
                                 blank=True,
                                 null=True)
    #ENDGEOCODING
    description = models.TextField(_("description"), blank=True)
    creator = models.ForeignKey(django_settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE,
                                null=True,
                                blank=True,
                                verbose_name=_("creator"),
                                related_name='creator')
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    updated_on = models.DateTimeField(_("updated on"), auto_now=True)
    rule = models.ForeignKey(
        Rule,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        verbose_name=_("rule"),
        help_text=_("Select '----' for a one time only event."))
    end_recurring_period = models.DateTimeField(
        _("end recurring period"),
        null=True,
        blank=True,
        db_index=True,
        help_text=_("This date is ignored for one time only events."))
    calendar = models.ForeignKey(Calendar,
                                 on_delete=models.CASCADE,
                                 null=True,
                                 blank=True,
                                 verbose_name=_("calendar"))
    color_event = models.CharField(_("Color event"), blank=True, max_length=10)
    objects = EventManager()

    class Meta(object):
        verbose_name = _('event')
        verbose_name_plural = _('events')
        app_label = 'schedule'
        index_together = (('start', 'end'), )

    def __str__(self):
        return ugettext('%(title)s: %(start)s - %(end)s') % {
            'title': self.title,
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT),
        }

    @property
    def seconds(self):
        return (self.end - self.start).total_seconds()

    @property
    def minutes(self):
        return float(self.seconds) / 60

    @property
    def hours(self):
        return float(self.seconds) / 3600

    @property
    def get_image_url(self):
        img = self.image_set.first()
        if img:
            return img.image.url
        return img

    def get_absolute_url(self):
        return reverse('event', args=[self.id])

    def get_occurrences(self, start, end, clear_prefetch=True):
        """
        >>> rule = Rule(frequency = "MONTHLY", name = "Monthly")
        >>> rule.save()
        >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2))
        >>> event.rule
        <Rule: Monthly>
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00']

        Ensure that if an event has no rule, that it appears only once.

        >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0))
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        []
        """

        # Explanation of clear_prefetch:
        #
        # Periods, and their subclasses like Week, call
        # prefetch_related('occurrence_set') on all events in their
        # purview. This reduces the database queries they make from
        # len()+1 to 2. However, having a cached occurrence_set on the
        # Event model instance can sometimes cause Events to have a
        # different view of the state of occurrences than the Period
        # managing them.
        #
        # E.g., if you create an unsaved occurrence, move it to a
        # different time [which saves the event], keep a reference to
        # the moved occurrence, & refetch all occurrences from the
        # Period without clearing the prefetch cache, you'll end up
        # with two Occurrences for the same event but different moved
        # states. It's a complicated scenario, but can happen. (See
        # tests/test_occurrence.py#test_moved_occurrences, which caught
        # this bug in the first place.)
        #
        # To prevent this, we clear the select_related cache by default
        # before we call an event's get_occurrences, but allow Period
        # to override this cache clear since it already fetches all
        # occurrence_sets via prefetch_related in its get_occurrences.
        if clear_prefetch:
            persisted_occurrences = self.occurrence_set.select_related(
                None).all()
        else:
            persisted_occurrences = self.occurrence_set.all()
        occ_replacer = OccurrenceReplacer(persisted_occurrences)

        occurrences = self._get_occurrence_list(start, end)
        final_occurrences = []
        for occ in occurrences:
            # replace occurrences with their persisted counterparts
            if occ_replacer.has_occurrence(occ):
                p_occ = occ_replacer.get_occurrence(occ)
                # ...but only if they are within this period
                if p_occ.start < end and p_occ.end >= start:
                    final_occurrences.append(p_occ)
            else:
                final_occurrences.append(occ)
        # then add persisted occurrences which originated outside of this period but now
        # fall within it
        final_occurrences += occ_replacer.get_additional_occurrences(
            start, end)
        return final_occurrences

    def get_rrule_object(self, tzinfo):
        if self.rule is None:
            return
        params = self._event_params()
        frequency = self.rule.rrule_frequency()
        if timezone.is_naive(self.start):
            dtstart = self.start
        else:
            dtstart = tzinfo.normalize(self.start).replace(tzinfo=None)

        if self.end_recurring_period is None:
            until = None
        elif timezone.is_naive(self.end_recurring_period):
            until = self.end_recurring_period
        else:
            until = tzinfo.normalize(
                self.end_recurring_period.astimezone(tzinfo)).replace(
                    tzinfo=None)

        return rrule.rrule(frequency, dtstart=dtstart, until=until, **params)

    def _create_occurrence(self, start, end=None):
        if end is None:
            end = start + (self.end - self.start)
        return Occurrence(event=self,
                          start=start,
                          end=end,
                          original_start=start,
                          original_end=end)

    def get_occurrence(self, date):
        use_naive = timezone.is_naive(date)
        tzinfo = timezone.utc
        if timezone.is_naive(date):
            date = timezone.make_aware(date, timezone.utc)
        if date.tzinfo:
            tzinfo = date.tzinfo
        rule = self.get_rrule_object(tzinfo)
        if rule:
            next_occurrence = rule.after(
                tzinfo.normalize(date).replace(tzinfo=None), inc=True)
            next_occurrence = tzinfo.localize(next_occurrence)
        else:
            next_occurrence = self.start
        if next_occurrence == date:
            try:
                return Occurrence.objects.get(event=self, original_start=date)
            except Occurrence.DoesNotExist:
                if use_naive:
                    next_occurrence = timezone.make_naive(
                        next_occurrence, tzinfo)
                # Was this before I pushed it out.
                # return self._create_occurrence(next_occurrence)
        return self._create_occurrence(next_occurrence)

    def _get_occurrence_list(self, start, end):
        """
        Returns a list of occurrences that fall completely or partially inside
        the timespan defined by start (inclusive) and end (exclusive)
        """
        if self.rule is not None:
            duration = self.end - self.start
            use_naive = timezone.is_naive(start)

            # Use the timezone from the start date
            tzinfo = timezone.utc
            if start.tzinfo:
                tzinfo = start.tzinfo

            # Limit timespan to recurring period
            occurrences = []
            if self.end_recurring_period and self.end_recurring_period < end:
                end = self.end_recurring_period

            start_rule = self.get_rrule_object(tzinfo)
            start = start.replace(tzinfo=None)
            if timezone.is_aware(end):
                end = tzinfo.normalize(end).replace(tzinfo=None)

            o_starts = []

            # Occurrences that start before the timespan but ends inside or after timespan
            closest_start = start_rule.before(start, inc=False)
            if closest_start is not None and closest_start + duration > start:
                o_starts.append(closest_start)

            # Occurrences starts that happen inside timespan (end-inclusive)
            occs = start_rule.between(start, end, inc=True)
            # The occurrence that start on the end of the timespan is potentially
            # included above, lets remove if thats the case.
            if len(occs) > 0:
                if occs[-1] == end:
                    occs.pop()
            # Add the occurrences found inside timespan
            o_starts.extend(occs)

            # Create the Occurrence objects for the found start dates
            for o_start in o_starts:
                o_start = tzinfo.localize(o_start)
                if use_naive:
                    o_start = timezone.make_naive(o_start, tzinfo)
                o_end = o_start + duration
                occurrence = self._create_occurrence(o_start, o_end)
                if occurrence not in occurrences:
                    occurrences.append(occurrence)
            return occurrences
        else:
            # check if event is in the period
            if self.start < end and self.end > start:
                return [self._create_occurrence(self.start)]
            else:
                return []

    def _occurrences_after_generator(self, after=None):
        """
        returns a generator that produces unpresisted occurrences after the
        datetime ``after``. (Optionally) This generator will return up to
        ``max_occurrences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest.
        """

        tzinfo = timezone.utc
        if after is None:
            after = timezone.now()
        elif not timezone.is_naive(after):
            tzinfo = after.tzinfo
        rule = self.get_rrule_object(tzinfo)
        if rule is None:
            if self.end > after:
                yield self._create_occurrence(self.start, self.end)
            return
        date_iter = iter(rule)
        difference = self.end - self.start
        loop_counter = 0
        for o_start in date_iter:
            o_start = tzinfo.localize(o_start)
            o_end = o_start + difference
            if o_end > after:
                yield self._create_occurrence(o_start, o_end)

            loop_counter += 1

    def occurrences_after(self, after=None, max_occurrences=None):
        """
        returns a generator that produces occurrences after the datetime
        ``after``.  Includes all of the persisted Occurrences. (Optionally) This generator will return up to
        ``max_occurrences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest.
        """
        if after is None:
            after = timezone.now()
        occ_replacer = OccurrenceReplacer(self.occurrence_set.all())
        generator = self._occurrences_after_generator(after)
        trickies = list(
            self.occurrence_set.filter(original_start__lte=after,
                                       start__gte=after).order_by('start'))
        for index, nxt in enumerate(generator):
            if max_occurrences and index > max_occurrences - 1:
                break
            if (len(trickies) > 0
                    and (nxt is None or nxt.start > trickies[0].start)):
                yield trickies.pop(0)
            yield occ_replacer.get_occurrence(nxt)

    @property
    def event_start_params(self):
        start = self.start
        params = {
            'byyearday': start.timetuple().tm_yday,
            'bymonth': start.month,
            'bymonthday': start.day,
            'byweekno': start.isocalendar()[1],
            'byweekday': start.weekday(),
            'byhour': start.hour,
            'byminute': start.minute,
            'bysecond': start.second
        }
        return params

    @property
    def event_rule_params(self):
        return self.rule.get_params()

    def _event_params(self):
        freq_order = freq_dict_order[self.rule.frequency]
        rule_params = self.event_rule_params
        start_params = self.event_start_params
        event_params = {}

        if len(rule_params) == 0:
            event_params['count'] = 0
            return event_params

        for param in rule_params:
            # start date influences rule params
            if (param in param_dict_order
                    and param_dict_order[param] > freq_order
                    and param in start_params):
                sp = start_params[param]
                if sp == rule_params[param] or (hasattr(
                        rule_params[param], '__iter__')
                                                and sp in rule_params[param]):
                    event_params[param] = [sp]
                else:
                    event_params[param] = rule_params[param]
            else:
                event_params[param] = rule_params[param]

        return event_params

    @property
    def event_params(self):
        event_params = self._event_params()
        start = self.effective_start
        if not start:
            empty = True
        elif self.end_recurring_period and start > self.end_recurring_period:
            empty = True
        return event_params, empty

    @property
    def effective_start(self):
        if self.pk and self.end_recurring_period:
            occ_generator = self._occurrences_after_generator(self.start)
            try:
                return next(occ_generator).start
            except StopIteration:
                pass
        elif self.pk:
            return self.start
        return None

    @property
    def effective_end(self):
        if self.pk and self.end_recurring_period:
            params, empty = self.event_params
            if empty or not self.effective_start:
                return None
            elif self.end_recurring_period:
                occ = None
                occ_generator = self._occurrences_after_generator(self.start)
                for occ in occ_generator:
                    pass
                return occ.end
        elif self.pk:
            return datetime.max
        return None
示例#9
0
    def test_get_model_bases_with_no_setting(self):
        from django.db.models import Model
        expected_result = [Model]
        actual_result = get_model_bases('Event')

        self.assertListEqual(actual_result, expected_result)
示例#10
0
    def test_get_model_bases_with_custom_dict_default(self):
        from django.db.models import Model
        expected_result = [Model]
        actual_result = get_model_bases('Event')

        self.assertListEqual(actual_result, expected_result)
示例#11
0
class Occurrence(with_metaclass(ModelBase, *get_model_bases())):
    event = models.ForeignKey(Event,
                              on_delete=models.CASCADE,
                              verbose_name=_("event"))
    title = models.CharField(_("title"), max_length=255, blank=True, null=True)
    description = models.TextField(_("description"), blank=True, null=True)
    start = models.DateTimeField(_("start"))
    end = models.DateTimeField(_("end"))
    cancelled = models.BooleanField(_("cancelled"), default=False)
    original_start = models.DateTimeField(_("original start"))
    original_end = models.DateTimeField(_("original end"))
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    updated_on = models.DateTimeField(_("updated on"), auto_now=True)

    class Meta(object):
        verbose_name = _("occurrence")
        verbose_name_plural = _("occurrences")
        app_label = 'schedule'

    def __init__(self, *args, **kwargs):
        super(Occurrence, self).__init__(*args, **kwargs)
        if self.title is None and self.event_id:
            self.title = self.event.title
        if self.description is None and self.event_id:
            self.description = self.event.description

    def moved(self):
        return self.original_start != self.start or self.original_end != self.end

    moved = property(moved)

    def move(self, new_start, new_end):
        self.start = new_start
        self.end = new_end
        self.save()

    def cancel(self):
        self.cancelled = True
        self.save()

    def uncancel(self):
        self.cancelled = False
        self.save()

    @property
    def seconds(self):
        return (self.end - self.start).total_seconds()

    @property
    def minutes(self):
        return float(self.seconds) / 60

    @property
    def hours(self):
        return float(self.seconds) / 3600

    def get_absolute_url(self):
        if self.pk is not None:
            return reverse('occurrence',
                           kwargs={
                               'occurrence_id': self.pk,
                               'event_id': self.event.id
                           })
        return reverse('occurrence_by_date',
                       kwargs={
                           'event_id': self.event.id,
                           'year': self.start.year,
                           'month': self.start.month,
                           'day': self.start.day,
                           'hour': self.start.hour,
                           'minute': self.start.minute,
                           'second': self.start.second,
                       })

    def get_cancel_url(self):
        if self.pk is not None:
            return reverse('cancel_occurrence',
                           kwargs={
                               'occurrence_id': self.pk,
                               'event_id': self.event.id
                           })
        return reverse('cancel_occurrence_by_date',
                       kwargs={
                           'event_id': self.event.id,
                           'year': self.start.year,
                           'month': self.start.month,
                           'day': self.start.day,
                           'hour': self.start.hour,
                           'minute': self.start.minute,
                           'second': self.start.second,
                       })

    def get_edit_url(self):
        if self.pk is not None:
            return reverse('edit_occurrence',
                           kwargs={
                               'occurrence_id': self.pk,
                               'event_id': self.event.id
                           })
        return reverse('edit_occurrence_by_date',
                       kwargs={
                           'event_id': self.event.id,
                           'year': self.start.year,
                           'month': self.start.month,
                           'day': self.start.day,
                           'hour': self.start.hour,
                           'minute': self.start.minute,
                           'second': self.start.second,
                       })

    def __str__(self):
        return ugettext("%(start)s to %(end)s") % {
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT)
        }

    def __lt__(self, other):
        return self.end < other.end

    def __eq__(self, other):
        return (isinstance(other, Occurrence)
                and self.original_start == other.original_start
                and self.original_end == other.original_end)
示例#12
0
class Occurrence(with_metaclass(ModelBase, *get_model_bases())):
    event = models.ForeignKey(Event, verbose_name=_("event"))
    start = models.DateTimeField(_("start"))
    end = models.DateTimeField(_("end"))
    cancelled = models.BooleanField(_("cancelled"), default=False)
    original_start = models.DateTimeField(_("original start"))
    original_end = models.DateTimeField(_("original end"))
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    updated_on = models.DateTimeField(_("updated on"), auto_now=True)

    class Meta(object):
        verbose_name = _("occurrence")
        verbose_name_plural = _("occurrences")
        app_label = 'schedule'
        unique_together = ('event', 'start') # the api uses event and start as the primary key
        
    objects = models.Manager()

    def moved(self):
        return self.original_start != self.start or self.original_end != self.end

    moved = property(moved)

    def cancel(self):
        self.cancelled = True
        self.save()
        
    def get_lookups(self):
        ''' returns lookup values for this instance as dictionary {key:value, key:value} to use for url reverse '''
        lookups = {'event_id': self.event_id}
        lookups.update({'pk':self.pk} if self.pk else {'start': self.start})
        return lookups

    def move(self, new_start, new_end):
        self.start = new_start
        self.end = new_end
        self.save()

    def uncancel(self):
        self.cancelled = False
        self.save()

    # these get url functions do not apply to the rest api
    def get_absolute_url(self):
        if self.pk is not None:
            return reverse('occurrence', kwargs={'occurrence_id': self.pk,
                                                 'event_id': self.event.id})
        return reverse('occurrence_by_date', kwargs={
            'event_id': self.event.id,
            'year': self.start.year,
            'month': self.start.month,
            'day': self.start.day,
            'hour': self.start.hour,
            'minute': self.start.minute,
            'second': self.start.second,
        })

    def get_cancel_url(self):
        if self.pk is not None:
            return reverse('cancel_occurrence', kwargs={'occurrence_id': self.pk,
                                                        'event_id': self.event.id})
        return reverse('cancel_occurrence_by_date', kwargs={
            'event_id': self.event.id,
            'year': self.start.year,
            'month': self.start.month,
            'day': self.start.day,
            'hour': self.start.hour,
            'minute': self.start.minute,
            'second': self.start.second,
        })

    def get_edit_url(self):
        if self.pk is not None:
            return reverse('edit_occurrence', kwargs={'occurrence_id': self.pk,
                                                      'event_id': self.event.id})
        return reverse('edit_occurrence_by_date', kwargs={
            'event_id': self.event.id,
            'year': self.start.year,
            'month': self.start.month,
            'day': self.start.day,
            'hour': self.start.hour,
            'minute': self.start.minute,
            'second': self.start.second,
        })

    def __str__(self):
        return ugettext("%(start)s to %(end)s") % {
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT)
        }

    def __lt__(self, other):
        return self.end < other.end

    def __eq__(self, other):
        return (isinstance(other, Occurrence) and
                self.original_start == other.original_start and self.original_end == other.original_end)
示例#13
0
class Event(with_metaclass(ModelBase, *get_model_bases())):
    '''
    This model stores meta data for a date.  You can relate this data to many
    other models.
    '''
    start = models.DateTimeField(_("start"))
    end = models.DateTimeField(_("end"), help_text=_("The end time must be later than the start time."))
    title = models.CharField(_("title"), max_length=255, blank=True, null=True)
    description = models.TextField(_("description"), null=True, blank=True)
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    updated_on = models.DateTimeField(_("updated on"), auto_now=True)
    rule = models.ForeignKey(Rule, null=True, blank=True, verbose_name=_("Repeats"),
                             help_text=_("Select '----' for a one time only event."))
    rule_params = JSONField(_("repeat params"), null=True, blank=True)
    
    end_recurring_period = models.DateTimeField(_("end recurring period"), null=True, blank=True,
                                                help_text=_("This date is ignored for one time only events."))
    calendar = models.ForeignKey(Calendar, null=True, blank=True, verbose_name=_("calendar"))

    objects = EventManager()

    class Meta(object):
        verbose_name = _('event')
        verbose_name_plural = _('events')
        app_label = 'schedule'

    def __str__(self):
        return ugettext('%(title)s: %(start)s - %(end)s') % {
            'title': self.title,
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT),
        }

    @property
    def seconds(self):
        return (self.end - self.start).total_seconds()

    @property
    def minutes(self):
        return float(self.seconds) / 60

    @property
    def hours(self):
        return float(self.seconds) / 3600
        
    def get_absolute_url(self):
        return reverse('event', args=[self.id])

    def get_occurrences(self, start, end):
        """
        >>> rule = Rule(frequency = "MONTHLY", name = "Monthly")
        >>> rule.save()
        >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2))
        >>> event.rule
        <Rule: Monthly>
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00']

        Ensure that if an event has no rule, that it appears only once.

        >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0))
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        []
`
        """
        persisted_occurrences = self.occurrence_set.all()
        occ_replacer = OccurrenceReplacer(persisted_occurrences)
        occurrences = self._get_occurrence_list(start, end)
        final_occurrences = []
        for occ in occurrences:
            # replace occurrences with their persisted counterparts
            if occ_replacer.has_occurrence(occ):
                p_occ = occ_replacer.get_occurrence(occ)
                # ...but only if they are within this period
                if p_occ.start < end and p_occ.end >= start:
                    final_occurrences.append(p_occ)
            else:
                final_occurrences.append(occ)
        # then add persisted occurrences which originated outside of this period but now
        # fall within it
        final_occurrences += occ_replacer.get_additional_occurrences(start, end)
        return final_occurrences

    def get_rrule_object(self):
        if self.rule is not None:
            params = self.rule.get_params() or {}
            params.update(self.rule_params or {})
            frequency = self.rule.rrule_frequency()
            return rrule.rrule(frequency, dtstart=self.start, **params)

    def _create_occurrence(self, start, end=None):
        if end is None:
            end = start + (self.end - self.start)
        return Occurrence(event=self, start=start, end=end, original_start=start, original_end=end)

    def get_occurrence(self, date):
        if timezone.is_naive(date) and django_settings.USE_TZ:
            date = timezone.make_aware(date, timezone.utc)
        rule = self.get_rrule_object()
        if rule:
            next_occurrence = rule.after(date, inc=True)
        else:
            next_occurrence = self.start
        if next_occurrence == date:
            try:
                return Occurrence.objects.get(event=self, original_start=date)
            except Occurrence.DoesNotExist:
                return self._create_occurrence(next_occurrence)

    def _get_occurrence_list(self, start, end):
        """
        returns a list of occurrences for this event from start to end.
        """
        difference = (self.end - self.start)
        if self.rule is not None:
            occurrences = []
            if self.end_recurring_period and self.end_recurring_period < end:
                end = self.end_recurring_period
            rule = self.get_rrule_object()
            o_starts = []
            o_starts.append(rule.between(start, end, inc=True))
            o_starts.append(rule.between(start - (difference // 2), end - (difference // 2), inc=True))
            o_starts.append(rule.between(start - difference, end - difference, inc=True))
            for occ in o_starts:
                for o_start in occ:
                    o_end = o_start + difference
                    occurrence = self._create_occurrence(o_start, o_end)
                    if occurrence not in occurrences:
                        occurrences.append(occurrence)
            return occurrences
        else:
            # check if event is in the period
            if self.start < end and self.end > start:
                return [self._create_occurrence(self.start)]
            else:
                return []

    def _occurrences_after_generator(self, after=None, tzinfo=pytz.utc):
        """
        returns a generator that produces unpresisted occurrences after the
        datetime ``after``.
        """

        if after is None:
            after = timezone.now()
        rule = self.get_rrule_object()
        if rule is None:
            if self.end > after:
                yield self._create_occurrence(self.start, self.end)
            raise StopIteration
        date_iter = iter(rule)
        difference = self.end - self.start
        while True:
            o_start = next(date_iter)
            if self.end_recurring_period and o_start > self.end_recurring_period:
                raise StopIteration
            o_end = o_start + difference
            if o_end > after:
                yield self._create_occurrence(o_start, o_end)

    def occurrences_after(self, after=None):
        """
        returns a generator that produces occurrences after the datetime
        ``after``.  Includes all of the persisted Occurrences.
        """
        occ_replacer = OccurrenceReplacer(self.occurrence_set.all())
        generator = self._occurrences_after_generator(after)
        while True:
            next_occurence = next(generator)
            yield occ_replacer.get_occurrence(next_occurence)
示例#14
0
文件: rules.py 项目: fitahol/fitahol
class Rule(with_metaclass(ModelBase, *get_model_bases())):
    """
    This defines a rule by which an event will recur.  This is defined by the
    rrule in the dateutil documentation.

    * name - the human friendly name of this kind of recursion.
    * description - a short description describing this type of recursion.
    * frequency - the base recurrence period
    * param - extra params required to define this type of recursion. The params
      should follow this format:

        param = [rruleparam:value;]*
        rruleparam = see list below
        value = int[,int]*

      The options are: (documentation for these can be found at
      https://dateutil.readthedocs.io/en/stable/rrule.html)
        ** count 重复次数,如指定到期时间,可无
        ** bymonth: MO, TU, WE, TH, FR, SA, SU 指定星期, 数字指定日期;
        ** bymonthday 每月日期,数字指定日期;
        ** byweekday: MO, TU, WE, TH, FR, SA, SU
        # ** bysetpos
        # ** byyearday 每年日期,数字指定天数,暂停使用;
        # ** byweekno 数字 0至6表示周一到周末;暂停使用
        # ** byhour
        # ** byminute
        # ** bysecond
        # ** byeaster
    """
    name = models.CharField(_("name"), max_length=32)
    description = models.TextField(_("description"))
    frequency = models.CharField(_("frequency"), choices=freqs, max_length=10)
    params = models.TextField(_("params"), null=True, blank=True)
    is_public = models.BooleanField("is public", default=False)

    class Meta(object):
        verbose_name = _('rule')
        verbose_name_plural = _('rules')
        app_label = 'schedule'

    def rrule_frequency(self):
        compatibiliy_dict = {
            'DAILY': DAILY,
            'MONTHLY': MONTHLY,
            'WEEKLY': WEEKLY,
            'YEARLY': YEARLY,
            'HOURLY': HOURLY,
            # 'MINUTELY': MINUTELY,
            # 'SECONDLY': SECONDLY
        }
        return compatibiliy_dict[self.frequency]

    def get_params(self):
        """
        {'count': 5, 'byweekday': [MO, TU, WE, TH]}
        """
        if self.params is None:
            return {}
        return json.loads(self.params)

    def __str__(self):
        """Human readable string for Rule"""
        return 'Rule %s params %s' % (self.name, self.params)
示例#15
0
    def test_get_model_bases_with_no_setting(self):
        from django.db.models import Model
        expected_result = [Model]
        actual_result = get_model_bases('Event')

        self.assertListEqual(actual_result, expected_result)
示例#16
0
文件: rules.py 项目: mamakancha/lfs-1
class Rule(with_metaclass(ModelBase, *get_model_bases())):
    """
    This defines a rule by which an event will recur.  This is defined by the
    rrule in the dateutil documentation.

    * name - the human friendly name of this kind of recursion.
    * description - a short description describing this type of recursion.
    * frequency - the base recurrence period
    * param - extra params required to define this type of recursion. The params
      should follow this format:

        param = [rruleparam:value;]*
        rruleparam = see list below
        value = int[,int]*

      The options are: (documentation for these can be found at
      http://labix.org/python-dateutil#head-470fa22b2db72000d7abe698a5783a46b0731b57)
        ** count
        ** bysetpos
        ** bymonth
        ** bymonthday
        ** byyearday
        ** byweekno
        ** byweekday
        ** byhour
        ** byminute
        ** bysecond
        ** byeaster
    """
    name = models.CharField(_("name"), max_length=32)
    description = models.TextField(_("description"))
    frequency = models.CharField(_("frequency"), choices=freqs, max_length=10)
    params = models.TextField(_("params"), null=True, blank=True)

    _week_days = {'MO': MO,
                  'TU': TU,
                  'WE': WE,
                  'TH': TH,
                  'FR': FR,
                  'SA': SA,
                  'SU': SU}

    class Meta(object):
        verbose_name = _('rule')
        verbose_name_plural = _('rules')
        app_label = 'schedule'

    def rrule_frequency(self):
        compatibiliy_dict = {
            'DAILY': DAILY,
            'MONTHLY': MONTHLY,
            'WEEKLY': WEEKLY,
            'YEARLY': YEARLY,
            'HOURLY': HOURLY,
            'MINUTELY': MINUTELY,
            'SECONDLY': SECONDLY
        }
        return compatibiliy_dict[self.frequency]

    def _weekday_or_number(self, param):
        '''
        Receives a rrule parameter value, returns a upper case version
        of the value if its a weekday or an integer if its a number
        '''
        try:
            return int(param)
        except (TypeError, ValueError):
            uparam = str(param).upper()
            if uparam in Rule._week_days:
                return Rule._week_days[uparam]

    def get_params(self):
        """
        >>> rule = Rule(params = "count:1;bysecond:1;byminute:1,2,4,5")
        >>> rule.get_params()
        {'count': 1, 'byminute': [1, 2, 4, 5], 'bysecond': 1}
        """
        if self.params is None:
            return {}
        params = self.params.split(';')
        param_dict = []
        for param in params:
            param = param.split(':')
            if len(param) != 2:
                continue

            param = (str(param[0]).lower(), [ x for x in
                [self._weekday_or_number(v) for v in param[1].split(',')]
                if x is not None])

            if len(param[1]) == 1:
                 param_value = self._weekday_or_number(param[1][0])
                 param = (param[0], param_value)
            param_dict.append(param)
        return dict(param_dict)

    def __str__(self):
        """Human readable string for Rule"""
        return 'Rule %s params %s' % (self.name, self.params)
示例#17
0
class Event(with_metaclass(ModelBase, *get_model_bases())):
    '''
    This model stores meta data for a date.  You can relate this data to many
    other models.
    '''
    start = models.DateTimeField(_("start"))
    end = models.DateTimeField(
        _("end"),
        help_text=_("The end time must be later than the start time."))
    title = models.CharField(_("title"), max_length=255)
    description = models.TextField(_("description"), null=True, blank=True)
    creator = models.ForeignKey(django_settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE,
                                null=True,
                                blank=True,
                                verbose_name=_("creator"),
                                related_name='creator')
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    updated_on = models.DateTimeField(_("updated on"), auto_now=True)
    rule = models.ForeignKey(
        Rule,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        verbose_name=_("rule"),
        help_text=_("Select '----' for a one time only event."))
    end_recurring_period = models.DateTimeField(
        _("end recurring period"),
        null=True,
        blank=True,
        help_text=_("This date is ignored for one time only events."))
    calendar = models.ForeignKey(Calendar,
                                 on_delete=models.CASCADE,
                                 null=True,
                                 blank=True,
                                 verbose_name=_("calendar"))
    color_event = models.CharField(_("Color event"),
                                   null=True,
                                   blank=True,
                                   max_length=10)
    objects = EventManager()

    class Meta(object):
        verbose_name = _('event')
        verbose_name_plural = _('events')
        app_label = 'schedule'

    def __str__(self):
        return ugettext('%(title)s: %(start)s - %(end)s') % {
            'title': self.title,
            'start': date(self.start, django_settings.DATE_FORMAT),
            'end': date(self.end, django_settings.DATE_FORMAT),
        }

    @property
    def seconds(self):
        return (self.end - self.start).total_seconds()

    @property
    def minutes(self):
        return float(self.seconds) / 60

    @property
    def hours(self):
        return float(self.seconds) / 3600

    def get_absolute_url(self):
        return reverse('event', args=[self.id])

    def get_occurrences(self, start, end, clear_prefetch=True):
        """
        >>> rule = Rule(frequency = "MONTHLY", name = "Monthly")
        >>> rule.save()
        >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1,tzinfo=pytz.utc), end=datetime.datetime(2008,1,2))
        >>> event.rule
        <Rule: Monthly>
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        ['2008-02-01 00:00:00+00:00 to 2008-02-02 00:00:00+00:00', '2008-03-01 00:00:00+00:00 to 2008-03-02 00:00:00+00:00']

        Ensure that if an event has no rule, that it appears only once.

        >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0))
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        []
        """

        # Explanation of clear_prefetch:
        #
        # Periods, and their subclasses like Week, call
        # prefetch_related('occurrence_set') on all events in their
        # purview. This reduces the database queries they make from
        # len()+1 to 2. However, having a cached occurrence_set on the
        # Event model instance can sometimes cause Events to have a
        # different view of the state of occurrences than the Period
        # managing them.
        #
        # E.g., if you create an unsaved occurrence, move it to a
        # different time [which saves the event], keep a reference to
        # the moved occurrence, & refetch all occurrences from the
        # Period without clearing the prefetch cache, you'll end up
        # with two Occurrences for the same event but different moved
        # states. It's a complicated scenario, but can happen. (See
        # tests/test_occurrence.py#test_moved_occurrences, which caught
        # this bug in the first place.)
        #
        # To prevent this, we clear the select_related cache by default
        # before we call an event's get_occurrences, but allow Period
        # to override this cache clear since it already fetches all
        # occurrence_sets via prefetch_related in its get_occurrences.
        if clear_prefetch:
            persisted_occurrences = self.occurrence_set.select_related(
                None).all()
        else:
            persisted_occurrences = self.occurrence_set.all()
        occ_replacer = OccurrenceReplacer(persisted_occurrences)
        occurrences = self._get_occurrence_list(start, end)
        final_occurrences = []
        for occ in occurrences:
            # replace occurrences with their persisted counterparts
            if occ_replacer.has_occurrence(occ):
                p_occ = occ_replacer.get_occurrence(occ)
                # ...but only if they are within this period
                if p_occ.start < end and p_occ.end >= start:
                    final_occurrences.append(p_occ)
            else:
                final_occurrences.append(occ)
        # then add persisted occurrences which originated outside of this period but now
        # fall within it
        final_occurrences += occ_replacer.get_additional_occurrences(
            start, end)
        return final_occurrences

    def get_rrule_object(self, tzinfo):
        if self.rule is not None:
            params, empty = self._event_params()
            frequency = self.rule.rrule_frequency()
            if timezone.is_naive(self.start):
                dtstart = self.start
            else:
                dtstart = tzinfo.normalize(self.start).replace(tzinfo=None)

            if not empty:
                return rrule.rrule(frequency, dtstart=dtstart, **params)
            else:
                year = self.start.year - 1
                return rrule.rrule(frequency,
                                   dtstart=dtstart,
                                   until=self.start.replace(year=year))

    def _create_occurrence(self, start, end=None):
        if end is None:
            end = start + (self.end - self.start)
        return Occurrence(event=self,
                          start=start,
                          end=end,
                          original_start=start,
                          original_end=end)

    def get_occurrence(self, date):
        use_naive = timezone.is_naive(date)
        tzinfo = timezone.utc
        if timezone.is_naive(date):
            date = timezone.make_aware(date, timezone.utc)
        if date.tzinfo:
            tzinfo = date.tzinfo
        rule = self.get_rrule_object(tzinfo)
        if rule:
            next_occurrence = rule.after(
                tzinfo.normalize(date).replace(tzinfo=None), inc=True)
            next_occurrence = tzinfo.localize(next_occurrence)
        else:
            next_occurrence = self.start
        if next_occurrence == date:
            try:
                return Occurrence.objects.get(event=self, original_start=date)
            except Occurrence.DoesNotExist:
                if use_naive:
                    next_occurrence = timezone.make_naive(
                        next_occurrence, tzinfo)
                return self._create_occurrence(next_occurrence)

    def _get_occurrence_list(self, start, end):
        """
        returns a list of occurrences for this event from start to end.
        """
        difference = (self.end - self.start)
        if self.rule is not None:
            use_naive = timezone.is_naive(start)

            # Use the timezone from the start date
            tzinfo = timezone.utc
            if start.tzinfo:
                tzinfo = start.tzinfo

            occurrences = []
            if self.end_recurring_period and self.end_recurring_period < end:
                end = self.end_recurring_period

            rule = self.get_rrule_object(tzinfo)
            start = (start - difference).replace(tzinfo=None)
            end = (end - difference)
            if timezone.is_aware(end):
                end = tzinfo.normalize(end).replace(tzinfo=None)
            o_starts = []
            o_starts.append(rule.between(start, end, inc=True))
            o_starts.append(
                rule.between(start - (difference // 2),
                             end - (difference // 2),
                             inc=True))
            o_starts.append(
                rule.between(start - difference, end - difference, inc=True))
            for occ in o_starts:
                for o_start in occ:
                    o_start = tzinfo.localize(o_start)
                    if use_naive:
                        o_start = timezone.make_naive(o_start, tzinfo)
                    o_end = o_start + difference
                    occurrence = self._create_occurrence(o_start, o_end)
                    if occurrence not in occurrences:
                        occurrences.append(occurrence)
            return occurrences
        else:
            # check if event is in the period
            if self.start < end and self.end > start:
                return [self._create_occurrence(self.start)]
            else:
                return []

    def _occurrences_after_generator(self, after=None):
        """
        returns a generator that produces unpresisted occurrences after the
        datetime ``after``. (Optionally) This generator will return up to
        ``max_occurences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest.
        """

        tzinfo = timezone.utc
        if after is None:
            after = timezone.now()
        elif not timezone.is_naive(after):
            tzinfo = after.tzinfo
        rule = self.get_rrule_object(tzinfo)
        if rule is None:
            if self.end > after:
                yield self._create_occurrence(self.start, self.end)
            return
        date_iter = iter(rule)
        difference = self.end - self.start
        loop_counter = 0
        for o_start in date_iter:
            o_start = tzinfo.localize(o_start)
            if self.end_recurring_period and self.end_recurring_period and o_start > self.end_recurring_period:
                break
            o_end = o_start + difference
            if o_end > after:
                yield self._create_occurrence(o_start, o_end)

            loop_counter += 1

    def occurrences_after(self, after=None, max_occurences=None):
        """
        returns a generator that produces occurrences after the datetime
        ``after``.  Includes all of the persisted Occurrences. (Optionally) This generator will return up to
        ``max_occurences`` occurrences or has reached ``self.end_recurring_period``, whichever is smallest.
        """
        if after is None:
            after = timezone.now()
        occ_replacer = OccurrenceReplacer(self.occurrence_set.all())
        generator = self._occurrences_after_generator(after)
        trickies = list(
            self.occurrence_set.filter(original_start__lte=after,
                                       start__gte=after).order_by('start'))
        for index, nxt in enumerate(generator):
            if max_occurences and index > max_occurences - 1:
                break
            if (len(trickies) > 0
                    and (nxt is None or nxt.start > trickies[0].start)):
                yield trickies.pop(0)
            yield occ_replacer.get_occurrence(nxt)

    @property
    def event_start_params(self):
        start = self.start
        params = {
            'byyearday': start.timetuple().tm_yday,
            'bymonth': start.month,
            'bymonthday': start.day,
            'byweekno': start.isocalendar()[1],
            'byweekday': start.weekday(),
            'byhour': start.hour,
            'byminute': start.minute,
            'bysecond': start.second
        }
        return params

    @property
    def event_rule_params(self):
        return self.rule.get_params()

    def _event_params(self):
        freq_order = freq_dict_order[self.rule.frequency]
        rule_params = self.event_rule_params
        start_params = self.event_start_params
        empty = False

        event_params = {}

        for param in rule_params:
            # start date influences rule params
            if (param in param_dict_order
                    and param_dict_order[param] > freq_order
                    and param in start_params):
                sp = start_params[param]
                if sp == rule_params[param] or sp in rule_params[param]:
                    event_params[param] = [sp]
                else:
                    event_params = {'count': 0}
                    empty = True
                    break
            else:
                event_params[param] = rule_params[param]
        return event_params, empty

    @property
    def event_params(self):
        event_params, empty = self._event_params()
        start = self.effective_start
        if not start:
            empty = True
        elif self.end_recurring_period and start > self.end_recurring_period:
            empty = True
        return event_params, empty

    @property
    def effective_start(self):
        if self.pk and self.end_recurring_period:
            occ_generator = self._occurrences_after_generator(self.start)
            try:
                return next(occ_generator).start
            except StopIteration:
                pass
        elif self.pk:
            return self.start
        return None

    @property
    def effective_end(self):
        if self.pk and self.end_recurring_period:
            params, empty = self.event_params
            if empty or not self.effective_start:
                return None
            elif self.end_recurring_period:
                occ = None
                occ_generator = self._occurrences_after_generator(self.start)
                for occ in occ_generator:
                    pass
                return occ.end
        elif self.pk:
            return datetime.max
        return None
示例#18
0
    def test_get_model_bases_with_custom_dict_default(self):
        from django.db.models import Model
        expected_result = [Model]
        actual_result = get_model_bases('Event')

        self.assertListEqual(actual_result, expected_result)
示例#19
0
class Calendar(with_metaclass(ModelBase, *get_model_bases())):
    '''
    This is for grouping events so that batch relations can be made to all
    events.  An example would be a project calendar.

    name: the name of the calendar
    events: all the events contained within the calendar.
    >>> calendar = Calendar(name = 'Test Calendar')
    >>> calendar.save()
    >>> data = {
    ...         'title': 'Recent Event',
    ...         'start': datetime.datetime(2008, 1, 5, 0, 0),
    ...         'end': datetime.datetime(2008, 1, 10, 0, 0)
    ...        }
    >>> event = Event(**data)
    >>> event.save()
    >>> calendar.events.add(event)
    >>> data = {
    ...         'title': 'Upcoming Event',
    ...         'start': datetime.datetime(2008, 1, 1, 0, 0),
    ...         'end': datetime.datetime(2008, 1, 4, 0, 0)
    ...        }
    >>> event = Event(**data)
    >>> event.save()
    >>> calendar.events.add(event)
    >>> data = {
    ...         'title': 'Current Event',
    ...         'start': datetime.datetime(2008, 1, 3),
    ...         'end': datetime.datetime(2008, 1, 6)
    ...        }
    >>> event = Event(**data)
    >>> event.save()
    >>> calendar.events.add(event)
    '''

    name = models.CharField(_("name"), max_length=200)
    slug = models.SlugField(_("slug"), max_length=200)
    objects = CalendarManager()

    class Meta(object):
        verbose_name = _('calendar')
        verbose_name_plural = _('calendar')
        app_label = 'schedule'

    def __str__(self):
        return self.name

    @property
    def events(self):
        return self.event_set

    def create_relation(self, obj, distinction=None, inheritable=True):
        """
        Creates a CalendarRelation between self and obj.

        if Inheritable is set to true this relation will cascade to all events
        related to this calendar.
        """
        CalendarRelation.objects.create_relation(self, obj, distinction,
                                                 inheritable)

    def get_recent(self,
                   amount=5,
                   in_datetime=datetime.datetime.now,
                   tzinfo=pytz.utc):
        """
        This shortcut function allows you to get events that have started
        recently.

        amount is the amount of events you want in the queryset. The default is
        5.

        in_datetime is the datetime you want to check against.  It defaults to
        datetime.datetime.now
        """
        return self.events.order_by('-start').filter(
            start__lt=timezone.now())[:amount]

    def occurrences_after(self, date=None):
        return EventListManager(self.events.all()).occurrences_after(date)

    def get_absolute_url(self):
        return reverse('calendar_home', kwargs={'calendar_slug': self.slug})

    def add_event_url(self):
        return reverse('calendar_create_event', args=[self.slug])
示例#20
0
class Rule(with_metaclass(ModelBase, *get_model_bases())):
    """
    This defines a rule by which an event will recur.  This is defined by the
    rrule in the dateutil documentation.

    * name - the human friendly name of this kind of recursion.
    * description - a short description describing this type of recursion.
    * frequency - the base recurrence period
    * param - extra params required to define this type of recursion. The params
      should follow this format:

        param = [rruleparam:value;]*
        rruleparam = see list below
        value = int[,int]*

      The options are: (documentation for these can be found at
      http://labix.org/python-dateutil#head-470fa22b2db72000d7abe698a5783a46b0731b57)
        ** count
        ** bysetpos
        ** bymonth
        ** bymonthday
        ** byyearday
        ** byweekno
        ** byweekday
        ** byhour
        ** byminute
        ** bysecond
        ** byeaster
    """
    name = models.CharField(_("name"), max_length=32)
    description = models.TextField(_("description"))
    frequency = models.CharField(_("frequency"), choices=freqs, max_length=10)
    params = models.TextField(_("params"), null=True, blank=True)

    class Meta(object):
        verbose_name = _('rule')
        verbose_name_plural = _('rules')
        app_label = 'schedule'

    def rrule_frequency(self):
        compatibiliy_dict = {
            'DAILY': DAILY,
            'MONTHLY': MONTHLY,
            'WEEKLY': WEEKLY,
            'YEARLY': YEARLY,
            'HOURLY': HOURLY,
            'MINUTELY': MINUTELY,
            'SECONDLY': SECONDLY
        }
        return compatibiliy_dict[self.frequency]

    def get_params(self):
        """
        >>> rule = Rule(params = "count:1;bysecond:1;byminute:1,2,4,5")
        >>> rule.get_params()
        {'count': [1], 'byminute': [1, 2, 4, 5], 'bysecond': [1]}
        """
        if self.params is None:
            return {}
        params = self.params.split(';')
        param_dict = defaultdict(list)
        for param in params:
            param = param.split(':')
            if len(param) == 2:
                param = dict([(str(param[0]), [int(p) for p in param[1].split(',')])])
                param_dict.update(param)
        return param_dict

    def __str__(self):
        """Human readable string for Rule"""
        return 'Rule %s params %s' % (self.name, self.params)