<commit_before># This file is part of Indico.
# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# Indico is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Indico; if not, see <http://www.gnu.org/licenses/>.

from datetime import datetime
from math import ceil

from dateutil import rrule
from sqlalchemy import Date, or_
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import defaultload
from sqlalchemy.sql import cast

from indico.core.db import db
from indico.core.db.sqlalchemy.util.queries import db_dates_overlap
from indico.core.errors import IndicoError
from indico.modules.rb.models.reservation_edit_logs import ReservationEditLog
from indico.modules.rb.models.util import proxy_to_reservation_if_last_valid_occurrence
from indico.util import date_time
from indico.util.date_time import format_date, iterdays
from indico.util.serializer import Serializer
from indico.util.string import return_ascii
from indico.util.user import unify_user_args


class ReservationOccurrence(db.Model, Serializer):
    __tablename__ = 'reservation_occurrences'
    __table_args__ = {'schema': 'roombooking'}
    __api_public__ = (('start_dt', 'startDT'), ('end_dt', 'endDT'), 'is_cancelled', 'is_rejected')

    #: A relationship loading strategy that will avoid loading the
    #: users linked to a reservation.  You want to use this in pretty
    #: much all cases where you eager-load the `reservation` relationship.
    NO_RESERVATION_USER_STRATEGY = defaultload('reservation')
    NO_RESERVATION_USER_STRATEGY.lazyload('created_by_user')
    NO_RESERVATION_USER_STRATEGY.noload('booked_for_user')

    reservation_id = db.Column(
        db.Integer,
        db.ForeignKey('roombooking.reservations.id'),
        nullable=False,
        primary_key=True
    )
    start_dt = db.Column(
        db.DateTime,
        nullable=False,
        primary_key=True,
        index=True
    )
    end_dt = db.Column(
        db.DateTime,
        nullable=False,
        index=True
    )
    notification_sent = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_cancelled = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_rejected = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    rejection_reason = db.Column(
        db.String
    )

    # relationship backrefs:
    # - reservation (Reservation.occurrences)

    @hybrid_property
    def date(self):
        return self.start_dt.date()

    @date.expression
    def date(self):
        return cast(self.start_dt, Date)

    @hybrid_property
    def is_valid(self):
        return not self.is_rejected and not self.is_cancelled

    @is_valid.expression
    def is_valid(self):
        return ~self.is_rejected & ~self.is_cancelled

    @return_ascii
    def __repr__(self):
        return u'<ReservationOccurrence({0}, {1}, {2}, {3}, {4}, {5})>'.format(
            self.reservation_id,
            self.start_dt,
            self.end_dt,
            self.is_cancelled,
            self.is_rejected,
            self.notification_sent
        )

    @classmethod
    def create_series_for_reservation(cls, reservation):
        for o in cls.iter_create_occurrences(reservation.start_dt, reservation.end_dt, reservation.repetition):
            o.reservation = reservation

    @classmethod
    def create_series(cls, start, end, repetition):
        return list(cls.iter_create_occurrences(start, end, repetition))

    @classmethod
    def iter_create_occurrences(cls, start, end, repetition):
        for start in cls.iter_start_time(start, end, repetition):
            end = datetime.combine(start.date(), end.time())
            yield ReservationOccurrence(start_dt=start, end_dt=end)

    @staticmethod
    def iter_start_time(start, end, repetition):
        from indico.modules.rb.models.reservations import RepeatFrequency

        repeat_frequency, repeat_interval = repetition

        if repeat_frequency == RepeatFrequency.NEVER:
            return [start]

        if repeat_frequency == RepeatFrequency.DAY:
            if repeat_interval == 1:
                return rrule.rrule(rrule.DAILY, dtstart=start, until=end)
            else:
                raise IndicoError(u'Unsupported interval')

        elif repeat_frequency == RepeatFrequency.WEEK:
            if 0 < repeat_interval < 4:
                return rrule.rrule(rrule.WEEKLY, dtstart=start, until=end, interval=repeat_interval)
            else:
                raise IndicoError(u'Unsupported interval')

        elif repeat_frequency == RepeatFrequency.MONTH:
            if repeat_interval == 0:
                raise IndicoError(u'Unsupported interval')
            position = int(ceil(start.day / 7.0))
            if position == 5:
                # The fifth weekday of the month will always be the last one
                position = -1
            return rrule.rrule(rrule.MONTHLY, dtstart=start, until=end, byweekday=start.weekday(),
                               bysetpos=position, interval=repeat_interval)

        raise IndicoError(u'Unexpected frequency {}'.format(repeat_frequency))

    @staticmethod<commit_msg>Fix error with >=4 week repetition<commit_after># This file is part of Indico.
# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# Indico is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Indico; if not, see <http://www.gnu.org/licenses/>.

from datetime import datetime
from math import ceil

from dateutil import rrule
from sqlalchemy import Date, or_
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import defaultload
from sqlalchemy.sql import cast

from indico.core.db import db
from indico.core.db.sqlalchemy.util.queries import db_dates_overlap
from indico.core.errors import IndicoError
from indico.modules.rb.models.reservation_edit_logs import ReservationEditLog
from indico.modules.rb.models.util import proxy_to_reservation_if_last_valid_occurrence
from indico.util import date_time
from indico.util.date_time import format_date, iterdays
from indico.util.serializer import Serializer
from indico.util.string import return_ascii
from indico.util.user import unify_user_args


class ReservationOccurrence(db.Model, Serializer):
    __tablename__ = 'reservation_occurrences'
    __table_args__ = {'schema': 'roombooking'}
    __api_public__ = (('start_dt', 'startDT'), ('end_dt', 'endDT'), 'is_cancelled', 'is_rejected')

    #: A relationship loading strategy that will avoid loading the
    #: users linked to a reservation.  You want to use this in pretty
    #: much all cases where you eager-load the `reservation` relationship.
    NO_RESERVATION_USER_STRATEGY = defaultload('reservation')
    NO_RESERVATION_USER_STRATEGY.lazyload('created_by_user')
    NO_RESERVATION_USER_STRATEGY.noload('booked_for_user')

    reservation_id = db.Column(
        db.Integer,
        db.ForeignKey('roombooking.reservations.id'),
        nullable=False,
        primary_key=True
    )
    start_dt = db.Column(
        db.DateTime,
        nullable=False,
        primary_key=True,
        index=True
    )
    end_dt = db.Column(
        db.DateTime,
        nullable=False,
        index=True
    )
    notification_sent = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_cancelled = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_rejected = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    rejection_reason = db.Column(
        db.String
    )

    # relationship backrefs:
    # - reservation (Reservation.occurrences)

    @hybrid_property
    def date(self):
        return self.start_dt.date()

    @date.expression
    def date(self):
        return cast(self.start_dt, Date)

    @hybrid_property
    def is_valid(self):
        return not self.is_rejected and not self.is_cancelled

    @is_valid.expression
    def is_valid(self):
        return ~self.is_rejected & ~self.is_cancelled

    @return_ascii
    def __repr__(self):
        return u'<ReservationOccurrence({0}, {1}, {2}, {3}, {4}, {5})>'.format(
            self.reservation_id,
            self.start_dt,
            self.end_dt,
            self.is_cancelled,
            self.is_rejected,
            self.notification_sent
        )

    @classmethod
    def create_series_for_reservation(cls, reservation):
        for o in cls.iter_create_occurrences(reservation.start_dt, reservation.end_dt, reservation.repetition):
            o.reservation = reservation

    @classmethod
    def create_series(cls, start, end, repetition):
        return list(cls.iter_create_occurrences(start, end, repetition))

    @classmethod
    def iter_create_occurrences(cls, start, end, repetition):
        for start in cls.iter_start_time(start, end, repetition):
            end = datetime.combine(start.date(), end.time())
            yield ReservationOccurrence(start_dt=start, end_dt=end)

    @staticmethod
    def iter_start_time(start, end, repetition):
        from indico.modules.rb.models.reservations import RepeatFrequency

        repeat_frequency, repeat_interval = repetition

        if repeat_frequency == RepeatFrequency.NEVER:
            return [start]

        elif repeat_frequency == RepeatFrequency.DAY:
            if repeat_interval != 1:
                raise IndicoError(u'Unsupported interval')
            return rrule.rrule(rrule.DAILY, dtstart=start, until=end)

        elif repeat_frequency == RepeatFrequency.WEEK:
            if repeat_interval <= 0:
                raise IndicoError(u'Unsupported interval')
            return rrule.rrule(rrule.WEEKLY, dtstart=start, until=end, interval=repeat_interval)

        elif repeat_frequency == RepeatFrequency.MONTH:
            if repeat_interval == 0:
                raise IndicoError(u'Unsupported interval')
            position = int(ceil(start.day / 7.0))
            if position == 5:
                # The fifth weekday of the month will always be the last one
                position = -1
            return rrule.rrule(rrule.MONTHLY, dtstart=start, until=end, byweekday=start.weekday(),
                               bysetpos=position, interval=repeat_interval)

        raise IndicoError(u'Unexpected frequency {}'.format(repeat_frequency))

    @staticmethod
{'fraction_non_alphanumeric': 0.0817577030812325, 'fraction_numerical': 0.004026610644257703, 'mean_word_length': 2.9288170563961486, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6608399', 'n_tokens_mistral': 3453, 'n_tokens_neox': 3256, 'n_words': 883}