예제 #1
0
class UploadController(dd.Model):
    
    class Meta(object):
        abstract = True
        
    def get_upload_area(self):
        return UploadAreas.general

    if dd.is_installed("uploads"):

        show_uploads = dd.ShowSlaveTable(
            'uploads.UploadsByController',
            button_text=u"🖿")  # u"\u1F5BF"
예제 #2
0
def set_plausibility_actions(sender, **kw):
    """Installs the :class:`UpdateProblemsByController` action on every
    model for which there is at least one Checker

    """
    for m in list(get_checkable_models().keys()):
        assert m is not Problem
        m.define_action(check_plausibility=UpdateProblemsByController(m))
        m.define_action(fix_problems=FixProblemsByController(m))
        if False:
            # don't add it automatically because appdev might prefer
            # to show it in a detail_layout:
            m.define_action(show_problems=dd.ShowSlaveTable(
                ProblemsByOwner, icon_name='bell', combo_group="plausibility"))
예제 #3
0
파일: models.py 프로젝트: TonisPiip/xl
class Event(Component, Ended, Assignable, TypedPrintable, Mailable, Postable):
    """A **calendar entry** is a lapse of time to be visualized in a
    calendar.

    .. attribute:: start_date
    .. attribute:: start_time
    .. attribute:: end_date
    .. attribute:: end_time

        These four fields define the duration of this entry.
        Only :attr:`start_date` is mandatory.

        If :attr:`end_date` is the same as :attr:`start_date`, then it
        is preferrable to leave it empty.

    .. attribute:: summary

         A one-line descriptive text.

    .. attribute:: description

         A longer descriptive text.

    .. attribute:: user

         The responsible user.

    .. attribute:: assigned_to

        Another user who is expected to take responsibility for this
        event.

        See :attr:`lino.modlib.users.mixins.Assignable.assigned_to`.

    .. attribute:: event_type

         The type of this event. Every calendar event should have this
         field pointing to a given :class:`EventType`, which holds
         extended configurable information about this event.

    .. attribute:: state

        The state of this entry. The state can change according to
        rules defined by the workflow, that's why we sometimes refer
        to it as the life cycle.

    .. attribute:: when_html

         Shows the date and time of the event with a link that opens
         all events on that day (:class:`EventsByDay
         <lino_xl.lib.cal.ui.EventsByDay>`).

    .. attribute:: show_conflicting

         A :class:`ShowSlaveTable <lino.core.actions.ShowSlaveTable>`
         button which opens the :class:`ConflictingEvents
         <lino_xl.lib.cal.ui.ConflictingEvents>` table for this event.

    """
    class Meta:
        app_label = 'cal'
        abstract = dd.is_abstract_model(__name__, 'Event')
        # abstract = True
        verbose_name = _("Calendar entry")
        verbose_name_plural = _("Calendar entries")
        # verbose_name = pgettext("cal", "Event")
        # verbose_name_plural = pgettext("cal", "Events")

    update_guests = UpdateGuests()
    update_events = UpdateEventsByEvent()

    event_type = models.ForeignKey('cal.EventType', blank=True, null=True)

    transparent = models.BooleanField(
        _("Transparent"), default=False, help_text=_("""\
Indicates that this Event shouldn't prevent other Events at the same time."""))
    room = dd.ForeignKey('cal.Room', null=True, blank=True)  # iCal:LOCATION
    priority = models.ForeignKey(Priority, null=True, blank=True)
    state = EventStates.field(
        default=EventStates.suggested.as_callable)  # iCal:STATUS
    all_day = ExtAllDayField(_("all day"))

    move_next = MoveEventNext()

    show_conflicting = dd.ShowSlaveTable(ConflictingEvents)

    def strftime(self):
        if not self.start_date:
            return ''
        d = self.start_date.strftime(settings.SITE.date_format_strftime)
        if self.start_time:
            t = self.start_time.strftime(
                settings.SITE.time_format_strftime)
            return "%s %s" % (d, t)
        else:
            return d

    def __str__(self):
        if self.pk:
            s = self._meta.verbose_name + " #" + str(self.pk)
        else:
            s = _("Unsaved %s") % self._meta.verbose_name
        if self.summary:
            s += " " + self.summary
        when = self.strftime()
        if when:
            s += " (%s)" % when
        return s

    def has_conflicting_events(self):
        """Whether this event has any conflicting events.
        
        This is roughly equivalent to asking whether
        :meth:`get_conflicting_events()` returns more than 0 events.

        Except when this event's type tolerates more than one events
        at the same time.

        """
        qs = self.get_conflicting_events()
        if qs is None:
            return False
        if self.event_type is not None:
            if qs.filter(event_type__all_rooms=True).count() > 0:
                return True
            n = self.event_type.max_conflicting - 1
        else:
            n = 0
        return qs.count() > n

    def get_conflicting_events(self):
        """
        Return a QuerySet of Events that conflict with this one.
        Must work also when called on an unsaved instance.
        May return None to indicate an empty queryset.
        Applications may override this to add specific conditions.
        """
        if self.transparent:
            return
        if self.state.transparent:
            return
        # return False
        # Event = dd.resolve_model('cal.Event')
        # ot = ContentType.objects.get_for_model(RecurrentEvent)
        qs = self.__class__.objects.filter(transparent=False)
        end_date = self.end_date or self.start_date
        flt = Q(start_date=self.start_date, end_date__isnull=True)
        flt |= Q(end_date__isnull=False,
                 start_date__lte=self.start_date, end_date__gte=end_date)
        if end_date == self.start_date:
            if self.start_time and self.end_time:
                # the other starts before me and ends after i started
                c1 = Q(start_time__lte=self.start_time,
                       end_time__gt=self.start_time)
                # the other ends after me and started before i ended
                c2 = Q(end_time__gte=self.end_time,
                       start_time__lt=self.end_time)
                # the other is full day
                c3 = Q(end_time__isnull=True, start_time__isnull=True)
                flt &= (c1 | c2 | c3)
        qs = qs.filter(flt)

        # saved events don't conflict with themselves:
        if self.id is not None:
            qs = qs.exclude(id=self.id)

        # automatic events never conflict with other generated events
        # of same owner. Rule needed for update_events.
        if self.auto_type:
            qs = qs.exclude(
                # auto_type=self.auto_type,
                auto_type__isnull=False,
                owner_id=self.owner_id, owner_type=self.owner_type)

        # transparent events (cancelled or omitted) usually don't
        # cause a conflict with other events (e.g. a holiday), except
        # if the other event has the same owner (because a cancelled
        # course lesson should not tolerate another lesson on the same
        # date).
        qs = qs.filter(
            Q(state__in=EventStates.filter(transparent=False)) | Q(
                owner_id=self.owner_id, owner_type=self.owner_type))

        if self.room is None:
            # a non-holiday event without room conflicts with a
            # holiday event
            if self.event_type is None or not self.event_type.all_rooms:
                qs = qs.filter(event_type__all_rooms=True)
        else:
            # other event in the same room
            c1 = Q(room=self.room)
            # other event locks all rooms (e.g. holidays)
            # c2 = Q(room__isnull=False, event_type__all_rooms=True)
            c2 = Q(event_type__all_rooms=True)
            qs = qs.filter(c1 | c2)
        if self.user is not None:
            if self.event_type is not None:
                if self.event_type.locks_user:
                    # c1 = Q(event_type__locks_user=False)
                    # c2 = Q(user=self.user)
                    # qs = qs.filter(c1|c2)
                    qs = qs.filter(user=self.user, event_type__locks_user=True)
        # qs = Event.objects.filter(flt,owner_type=ot)
        # if we.start_date.month == 7:
            # print 20131011, self, we.start_date, qs.count()
        # print 20131025, qs.query
        return qs

    def is_fixed_state(self):
        return self.state.fixed
        # return self.state in EventStates.editable_states

    def is_user_modified(self):
        return self.state != EventStates.suggested

    def after_ui_save(self, ar, cw):
        super(Event, self).after_ui_save(ar, cw)
        self.update_guests.run_from_code(ar)

    def suggest_guests(self):
        """Yield the list of Guest instances to be added to this Event.  This
        method is called from :meth:`update_guests`.

        """
        if self.owner:
            for obj in self.owner.suggest_cal_guests(self):
                yield obj

    def get_event_summary(event, ar):
        """How this event should be summarized in contexts where possibly
        another user is looking (i.e. currently in invitations of
        guests, or in the extensible calendar panel).

        """
        # from django.utils.translation import ugettext as _
        s = event.summary
        # if event.owner_id:
        #     s += " ({0})".format(event.owner)
        if event.user is not None and event.user != ar.get_user():
            if event.access_class == AccessClasses.show_busy:
                s = _("Busy")
            s = event.user.username + ': ' + unicode(s)
        elif settings.SITE.project_model is not None \
                and event.project is not None:
            s += " " + unicode(_("with")) + " " + unicode(event.project)
        if event.state:
            s = ("(%s) " % unicode(event.state)) + s
        n = event.guest_set.all().count()
        if n:
            s = ("[%d] " % n) + s
        return s

    def before_ui_save(self, ar, **kw):
        """Mark the event as "user modified" by setting a default state.
        This is important because EventGenerators may not modify any
        user-modified Events.

        """
        # logger.info("20130528 before_ui_save")
        if self.state is EventStates.suggested:
            self.state = EventStates.draft
        return super(Event, self).before_ui_save(ar, **kw)

    def on_create(self, ar):
        self.event_type = ar.user.event_type or \
            settings.SITE.site_config.default_event_type
        self.start_date = settings.SITE.today()
        self.start_time = timezone.now().time()
        # 20130722 e.g. CreateClientEvent sets it explicitly
        if self.assigned_to is None:
            self.assigned_to = ar.subst_user
        super(Event, self).on_create(ar)

    # def on_create(self,ar):
        # self.start_date = settings.SITE.today()
        # self.start_time = datetime.datetime.now().time()
        # ~ # default user is almost the same as for UserAuthored
        # ~ # but we take the *real* user, not the "working as"
        # if self.user_id is None:
            # u = ar.user
            # if u is not None:
                # self.user = u
        # super(Event,self).on_create(ar)

    def get_postable_recipients(self):
        """return or yield a list of Partners"""
        if self.project:
            if isinstance(self.project, dd.plugins.cal.partner_model):
                yield self.project
        for g in self.guest_set.all():
            yield g.partner
        # if self.user.partner:
            # yield self.user.partner

    def get_mailable_type(self):
        return self.event_type

    def get_mailable_recipients(self):
        if self.project:
            if isinstance(self.project, dd.plugins.cal.partner_model):
                yield ('to', self.project)
        for g in self.guest_set.all():
            yield ('to', g.partner)
        if self.user.partner:
            yield ('cc', self.user.partner)

    # def get_mailable_body(self,ar):
        # return self.description

    @dd.displayfield(_("When"))
    def when_text(self, ar):
        txt = when_text(self.start_date, self.start_time)
        if self.end_date and self.end_date != self.start_date:
            txt += "-" + when_text(self.end_date, self.end_time)
        return txt

    @dd.displayfield(_("When"))
    # def linked_date(self, ar):
    def when_html(self, ar):
        if ar is None:
            return ''
        EventsByDay = settings.SITE.modules.cal.EventsByDay
        txt = when_text(self.start_date, self.start_time)
        return EventsByDay.as_link(ar, self.start_date, txt)

    @dd.displayfield(_("Link URL"))
    def url(self, ar):
        return 'foo'

    @dd.virtualfield(dd.DisplayField(_("Reminder")))
    def reminder(self, request):
        return False
    # reminder.return_type = dd.DisplayField(_("Reminder"))

    def get_calendar(self):
        """
        Returns the Calendar which contains this event,
        or None if no subscription is found.
        Needed for ext.ensible calendar panel.

        The default implementation returns None.
        Override this if your app uses Calendars.
        """
        # for sub in Subscription.objects.filter(user=ar.get_user()):
            # if sub.contains_event(self):
                # return sub
        return None

    @dd.virtualfield(models.ForeignKey('cal.Calendar'))
    def calendar(self, ar):
        return self.get_calendar()

    def get_print_language(self):
        # if settings.SITE.project_model is not None and self.project:
        if self.project:
            return self.project.get_print_language()
        if self.user:
            return self.user.language
        return settings.SITE.get_default_language()

    @classmethod
    def get_default_table(cls):
        return OneEvent

    @classmethod
    def on_analyze(cls, lino):
        cls.DISABLED_AUTO_FIELDS = dd.fields_list(cls, "summary")
        super(Event, cls).on_analyze(lino)

    def auto_type_changed(self, ar):
        """When the number has changed, we must update the summary."""
        if self.auto_type:
            self.summary = self.owner.update_cal_summary(self.auto_type)
예제 #4
0
    """,
                                    window_size=(40, 'auto'))


class PartnersByInvoiceRecipient(SalesRules):
    help_text = _("Show partners having this as invoice recipient.")
    details_of_master_template = _("%(master)s used as invoice recipient")
    button_text = "♚"  # 265A
    master_key = 'invoice_recipient'
    column_names = "partner partner__id partner__address_column *"
    window_size = (80, 20)


dd.inject_action(
    'contacts.Partner',
    show_invoice_partners=dd.ShowSlaveTable(PartnersByInvoiceRecipient))


class Tariff(BabelDesignated):
    class Meta(object):
        app_label = 'invoicing'
        abstract = dd.is_abstract_model(__name__, 'Tariff')
        verbose_name = _("Flatrate")
        verbose_name_plural = _("Flatrates")

    # allow_cascaded_delete = 'product'

    # product = dd.OneToOneField('products.Product', primary_key=True)

    number_of_events = models.IntegerField(
        _("Number of events"),
예제 #5
0
# -*- coding: UTF-8 -*-
# Copyright 2017-2020 Rumma & Ko Ltd
# License: GNU Affero General Public License v3 (see file COPYING for details)

from lino.api import dd, rt, _
from lino.utils import join_words
from lino.mixins import Hierarchical

from lino_xl.lib.contacts.models import *
from lino.modlib.comments.mixins import Commentable

Partner.define_action(show_problems=dd.ShowSlaveTable(
    'checkdata.ProblemsByOwner', icon_name='bell', combo_group="checkdata"))


class PartnerDetail(PartnerDetail):

    main = """
    overview #address_box:60 contact_box:30
    bottom_box
    """

    # address_box = dd.Panel("""
    # name_box
    # country #region city zip_code:10
    # addr1
    # #street_prefix street:25 street_no street_box
    # #addr2
    # """)  # , label=_("Address"))

    contact_box = dd.Panel("""
예제 #6
0
    master = 'ledger.Account'

    @classmethod
    def get_data_rows(cls, ar, **flt):
        account = ar.master_instance
        if account is None:
            return []
        if not account.clearable:
            return []
        flt.update(cleared=False, account=account)
        # ignore trade_type to avoid overriding account
        ar.param_values.trade_type = None
        return super(DebtsByAccount, cls).get_data_rows(ar, **flt)


dd.inject_action('ledger.Account', due=dd.ShowSlaveTable(DebtsByAccount))


class DebtsByPartner(ExpectedMovements):
    master = 'contacts.Partner'
    #~ column_names = 'due_date debts payments balance'

    @classmethod
    def get_dc(cls, ar=None):
        return DEBIT

    @classmethod
    def get_data_rows(cls, ar, **flt):
        partner = ar.master_instance
        if partner is None:
            return []
예제 #7
0
class Event(Component, Ended, Assignable, TypedPrintable, Mailable, Postable, Publishable):
    class Meta:
        app_label = 'cal'
        abstract = dd.is_abstract_model(__name__, 'Event')
        # abstract = True
        verbose_name = _("Calendar entry")
        verbose_name_plural = _("Calendar entries")
        # verbose_name = pgettext("cal", "Event")
        # verbose_name_plural = pgettext("cal", "Events")

    publisher_location = "cal"
    publisher_page_template = "cal/event.pub.html"

    update_guests = UpdateGuests()
    update_events = UpdateEntriesByEvent()
    show_today = ShowEntriesByDay('start_date')

    event_type = dd.ForeignKey('cal.EventType', blank=True, null=True)

    transparent = models.BooleanField(_("Transparent"), default=False)
    room = dd.ForeignKey('cal.Room', null=True, blank=True)
    # priority = dd.ForeignKey(Priority, null=True, blank=True)
    state = EntryStates.field(default='suggested')
    all_day = ExtAllDayField(_("all day"))

    move_next = MoveEntryNext()

    show_conflicting = dd.ShowSlaveTable(ConflictingEvents)
    allow_merge_action = False

    @classmethod
    def setup_parameters(cls, params):
        super(Event, cls).setup_parameters(params)
        params.update(
            # user=dd.ForeignKey(settings.SITE.user_model,
            #                    verbose_name=_("Managed by"),
            #                    blank=True, null=True),
            # event_type=dd.ForeignKey('cal.EventType', blank=True, null=True),
            # room=dd.ForeignKey('cal.Room', blank=True, null=True),
            # project=dd.ForeignKey(settings.SITE.project_model, blank=True, null=True),
            # assigned_to=dd.ForeignKey(settings.SITE.user_model,
            #                           verbose_name=_("Assigned to"),
            #                           blank=True, null=True),
            # state=EntryStates.field(blank=True),
            # unclear = models.BooleanField(_("Unclear events"))
            observed_event=EventEvents.field(blank=True),
            show_appointments=dd.YesNo.field(_("Appointments"), blank=True),
            presence_guest=dd.ForeignKey(
                dd.plugins.cal.partner_model, verbose_name=_("Guest"),
                blank=True, null=True)
        )
        # if settings.SITE.project_model:
        #     params['project'].help_text = format_lazy(
        #         _("Show only entries having this {project}."),
        #         project=settings.SITE.project_model._meta.verbose_name)

    @classmethod
    def get_simple_parameters(cls):
        for p in super(Event, cls).get_simple_parameters():
            yield p
        yield 'state'
        yield 'room'
        yield 'event_type'

    @classmethod
    def get_request_queryset(cls, ar, **filter):
        qs = super(Event, cls).get_request_queryset(ar, **filter)
        # print("20200430 cal.Event.get_request_queryset", ar.param_values.project, qs.query)
        # return cls.calendar_param_filter(qs, ar.param_values)
        pv = ar.param_values

        # if pv.user:
        #     qs = qs.filter(user=pv.user)
        # if pv.assigned_to:
        #     qs = qs.filter(assigned_to=pv.assigned_to)
        #
        # if settings.SITE.project_model is not None and pv.project:
        #     qs = qs.filter(project=pv.project)

        if pv.event_type:
            # qs = qs.filter(event_type=pv.event_type)
            pass
        else:
            if pv.show_appointments == dd.YesNo.yes:
                qs = qs.filter(event_type__is_appointment=True)
            elif pv.show_appointments == dd.YesNo.no:
                qs = qs.filter(event_type__is_appointment=False)

        # if pv.state:
        #     qs = qs.filter(state=pv.state)

        # if pv.room:
        #     qs = qs.filter(room=pv.room)
        #
        if pv.observed_event == EventEvents.stable:
            qs = qs.filter(state__in=set(EntryStates.filter(fixed=True)))
        elif pv.observed_event == EventEvents.pending:
            qs = qs.filter(state__in=set(EntryStates.filter(fixed=False)))

        # multi-day entries should appear on each day
        if pv.start_date:
            c1 = Q(start_date__gte=pv.start_date)
            c2 = Q(start_date__lt=pv.start_date, end_date__isnull=False, end_date__gte=pv.start_date)
            qs = qs.filter(c1|c2)
        if pv.end_date:
            c1 = Q(end_date__isnull=True, start_date__lte=pv.end_date)
            c2 = Q(end_date__isnull=False, end_date__lte=pv.end_date)
            qs = qs.filter(c1|c2)
            qs = qs.filter(Q(end_date__isnull=True, start_date__lte=pv.end_date)|Q(end_date__lte=pv.end_date))
        if pv.presence_guest:
            qs = qs.filter(Q(guest__partner=pv.presence_guest)|Q(event_type__all_rooms=True))
        return qs

    def strftime(self):
        if not self.start_date:
            return ''
        d = self.start_date.strftime(settings.SITE.date_format_strftime)
        if self.start_time:
            t = self.start_time.strftime(
                settings.SITE.time_format_strftime)
            return "%s %s" % (d, t)
        else:
            return d

    def get_diplay_color(self):
        if self.room:
            return self.room.display_color

    def __str__(self):
        if self.summary:
            s = self.summary
        elif self.event_type:
            s = str(self.event_type)
        elif self.pk:
            s = self._meta.verbose_name + " #" + str(self.pk)
        else:
            s = _("Unsaved %s") % self._meta.verbose_name
        when = self.strftime()
        if when:
            s = "{} ({})".format(s, when)
        # u = self.user
        # if u is None and self.room:
        #     u = self.room
        # if u is None:
        #     return '%s object (%s)' % (self.__class__.__name__, self.pk)
        # u = u.initials or u.username or str(u)
        # s = "{} ({})".format(s, u)
        return s
        # if e.start_time:
        #     t = str(e.start_time)[:5]
        # else:
        #     t = str(e.event_type)
        # u = e.user
        # if u is None:
        #     return "{} {}".format(t, e.room) if e.room else t
        # u = u.initials or u.username or str(u)
        # return "{} {}".format(t, u)

    def duration_veto(obj):
        if obj.end_date is None:
            return
        et = obj.event_type
        if et is None:
            return
        duration = obj.end_date - obj.start_date
        # print (20161222, duration.days, et.max_days)
        if et.max_days and duration.days > et.max_days:
            return _(
                "Event lasts {0} days but only {1} are allowed.").format(
                    duration.days, et.max_days)

    def full_clean(self, *args, **kw):
        super(Event, self).full_clean(*args, **kw)
        et = self.event_type
        if et is not None and et.default_duration is not None:
            assert isinstance(et.default_duration, Duration)
            dt = self.get_datetime('start')
            if dt is not None and self.end_time is None:
                self.set_datetime('end', dt + et.default_duration)
            else:
                dt = self.get_datetime('end')
                if dt is not None and self.start_time is None:
                    self.set_datetime('start', dt - et.default_duration)
        if et and et.max_days == 1:
            # avoid "Abandoning with 297 unsaved instances" when migrating data
            # that was created before the current rules
            self.end_date = None
        msg = self.duration_veto()
        if msg is not None:
            raise ValidationError(str(msg))

    def start_time_changed(self, ar):
        et = self.event_type
        start_time = self.get_datetime('start')
        if start_time is not None \
                and et is not None and et.default_duration is not None:
            dt = start_time + et.default_duration
            self.set_datetime('end', dt)
            # self.end_time = str(self.start_time + et.default_duration)

    # removed because this behaviour is irritating
    # def end_time_changed(self, ar):
    #     et = self.event_type
    #     end_time = self.get_datetime('end', 'start')
    #     if end_time is not None \
    #             and et is not None and et.default_duration is not None:
    #         dt = end_time - et.default_duration
    #         self.set_datetime('start', dt)
    #         # self.start_time = str(self.end_time - et.default_duration)

    def get_change_observers(self, ar=None):
        # implements ChangeNotifier
        if not self.is_user_modified():
            return
        for x in super(Event, self).get_change_observers(ar):
            yield x
        for u in (self.user, self.assigned_to):
            if u is not None:
                yield (u, u.mail_mode)

    def has_conflicting_events(self):
        qs = self.get_conflicting_events()
        if qs is None:
            return False
        if self.event_type is not None:
            if self.event_type.transparent:
                return False
            # holidays (all room events) conflict also with events
            # whose type otherwise would allow conflicting events
            if qs.filter(event_type__all_rooms=True).count() > 0:
                return True
            n = self.event_type.max_conflicting - 1
        else:
            n = 0
        # date = self.start_date
        # if date.day == 9 and date.month == 3:
        #     dd.logger.info("20171130 has_conflicting_events() %s", qs.query)
        return qs.count() > n

    def get_conflicting_events(self):
        if self.transparent:
            return
        # if self.event_type is not None and self.event_type.transparent:
        #     return
        # return False
        # Event = dd.resolve_model('cal.Event')
        # ot = ContentType.objects.get_for_model(RecurrentEvent)
        qs = self.__class__.objects.filter(transparent=False)
        qs = qs.exclude(event_type__transparent=True)

        # if self.state.transparent:
        #     # cancelled entries are basically transparent to all
        #     # others. Except if they have an owner, in which case we
        #     # wouldn't want Lino to put another automatic entry at
        #     # that date.
        #     if self.owner_id is None:
        #         return
        #     qs = qs.filter(
        #         owner_id=self.owner_id, owner_type=self.owner_type)

        end_date = self.end_date or self.start_date
        flt = Q(start_date=self.start_date, end_date__isnull=True)
        flt |= Q(end_date__isnull=False,
                 start_date__lte=self.start_date, end_date__gte=end_date)
        if end_date == self.start_date:
            if self.start_time and self.end_time:
                # the other starts before me and ends after i started
                c1 = Q(start_time__lte=self.start_time,
                       end_time__gt=self.start_time)
                # the other ends after me and started before i ended
                c2 = Q(end_time__gte=self.end_time,
                       start_time__lt=self.end_time)
                # the other is full day
                c3 = Q(end_time__isnull=True, start_time__isnull=True)
                flt &= (c1 | c2 | c3)
        qs = qs.filter(flt)

        # saved events don't conflict with themselves:
        if self.id is not None:
            qs = qs.exclude(id=self.id)

        # automatic events never conflict with other generated events
        # of same owner. Rule needed for update_events.
        if self.auto_type:
            qs = qs.exclude(
                # auto_type=self.auto_type,
                auto_type__isnull=False,
                owner_id=self.owner_id, owner_type=self.owner_type)

        # transparent events (cancelled or omitted) usually don't
        # cause a conflict with other events (e.g. a holiday). But a
        # cancelled course lesson should not tolerate another lesson
        # of the same course on the same date.
        ntstates = EntryStates.filter(transparent=False)
        if self.owner_id is None:
            if self.state.transparent:
                return
            qs = qs.filter(state__in=ntstates)
        else:
            if self.state.transparent:
                qs = qs.filter(
                    owner_id=self.owner_id, owner_type=self.owner_type)
            else:
                qs = qs.filter(
                    Q(state__in=ntstates) | Q(
                        owner_id=self.owner_id, owner_type=self.owner_type))

        if self.room is None:
            # an entry that needs a room but doesn't yet have one,
            # conflicts with any all-room entry (e.g. a holiday).  For
            # generated entries this list extends to roomed entries of
            # the same generator.

            if self.event_type is None or not self.event_type.all_rooms:
                if self.owner_id is None:
                    qs = qs.filter(event_type__all_rooms=True)
                else:
                    qs = qs.filter(
                        Q(event_type__all_rooms=True) | Q(
                            owner_id=self.owner_id, owner_type=self.owner_type))
        else:
            # other event in the same room
            c1 = Q(room=self.room)
            # other event locks all rooms (e.g. holidays)
            # c2 = Q(room__isnull=False, event_type__all_rooms=True)
            c2 = Q(event_type__all_rooms=True)
            qs = qs.filter(c1 | c2)
        if self.user is not None:
            if self.event_type is not None:
                if self.event_type.locks_user:
                    # c1 = Q(event_type__locks_user=False)
                    # c2 = Q(user=self.user)
                    # qs = qs.filter(c1|c2)
                    qs = qs.filter(user=self.user, event_type__locks_user=True)
        # qs = Event.objects.filter(flt,owner_type=ot)
        # if we.start_date.month == 7:
            # print 20131011, self, we.start_date, qs.count()
        # print 20131025, qs.query
        return qs

    def is_fixed_state(self):
        return self.state.fixed
        # return self.state in EntryStates.editable_states

    def is_user_modified(self):
        return self.state != EntryStates.suggested

    def before_ui_save(self, ar, cw):
        # logger.info("20130528 before_ui_save")
        if self.state is EntryStates.suggested:
            self.state = EntryStates.draft
        super(Event, self).before_ui_save(ar, cw)

    def on_create(self, ar):
        if self.event_type is None:
            # print("20200513", ar.user)
            self.event_type = ar.user.event_type or \
                settings.SITE.site_config.default_event_type
        self.start_date = self.start_date or settings.SITE.today()
        self.start_time = timezone.now().time()
        # see also Assignable.on_create()
        super(Event, self).on_create(ar)
        if not settings.SITE.loading_from_dump:
            # print("20190328 before_ui_save", self.is_user_modified())
            if isinstance(self.owner, RecurrenceSet):
                self.owner.before_auto_event_save(self)
            if isinstance(self.owner, EventGenerator):
                self.event_type = self.owner.update_cal_event_type()

    # def on_create(self,ar):
        # self.start_date = settings.SITE.today()
        # self.start_time = datetime.datetime.now().time()
        # ~ # default user is almost the same as for UserAuthored
        # ~ # but we take the *real* user, not the "working as"
        # if self.user_id is None:
            # u = ar.user
            # if u is not None:
                # self.user = u
        # super(Event,self).on_create(ar)

    def after_ui_save(self, ar, cw):
        super(Event, self).after_ui_save(ar, cw)
        self.update_guests.run_from_code(ar)

    def before_state_change(self, ar, old, new):
        super(Event, self).before_state_change(ar, old, new)
        if new.noauto:
            self.auto_type = None
        if new.guest_state and self.event_type_id and self.event_type.force_guest_states:
            for obj in self.guest_set.exclude(state=new.guest_state):
                obj.state = new.guest_state
                obj.full_clean()
                obj.save()

    def can_edit_guests_manually(self):
        # print("20200901", self.state, self.state.fill_guests)
        if self.state.fill_guests:
            if self.event_type and self.event_type.fill_presences:
                return False
        # print("20200901 yes")
        return True

    def suggest_guests(self):
        done = set()
        for o in (self.owner, self.project):
            if isinstance(o, EventGenerator):
                if o in done:
                    continue
                done.add(o)
                for obj in o.suggest_cal_guests(self):
                    yield obj

    summary_project_template = _("for {project}")
    summary_show_user = True

    def get_event_summary(self, ar):
        # from django.utils.translation import ugettext as _
        s = self.summary
        if self.owner is not None:
            s = "{} {}".format(self.owner, s)

        u = self.user
        if u is not None and u != ar.get_user():
            if self.access_class == AccessClasses.show_busy:
                s = _("Busy")
            if self.summary_show_user:
                s = "{} {}".format(u.initials or u.username, s)

        # n = event.guest_set.all().count()
        # if n:
        #     s = ("[%d] " % n) + s

        if self.state.button_text:
            yield str(self.state.button_text) + " "
        yield s
        if self.project is not None:
            # s += " " + "{} {}".format(self.summary_project_template, self.project).strip()
            yield " " + self.summary_project_template.format(project=self.project)

    def get_postable_recipients(self):
        """return or yield a list of Partners"""
        if self.project:
            if isinstance(self.project, dd.plugins.cal.partner_model):
                yield self.project
        for g in self.guest_set.all():
            yield g.partner
        # if self.user.partner:
            # yield self.user.partner

    def get_mailable_type(self):
        return self.event_type

    def get_mailable_recipients(self):
        if self.project:
            if isinstance(self.project, dd.plugins.cal.partner_model):
                yield ('to', self.project)
        for g in self.guest_set.all():
            yield ('to', g.partner)
        if self.user.partner:
            yield ('cc', self.user.partner)

    # def get_mailable_body(self,ar):
        # return self.description

    @dd.displayfield(_("When"), sortable_by=['start_date', 'start_time'])
    def when_text(self, ar):
        txt = when_text(self.start_date, self.start_time)
        if self.end_date and self.end_date != self.start_date:
            txt += "-" + when_text(self.end_date, self.end_time)
        return txt

    @dd.displayfield(_("When"), sortable_by=['start_date', 'start_time'])
    def when_html(self, ar):
        if ar is None:
            return ''
        txt = when_text(self.start_date, self.start_time)
        if False:  # removed 20181106 because it is irritating and
                   # nobody uses it.
            return rt.models.cal.EntriesByDay.as_link(
                ar, self.start_date, txt)
        return self.obj2href(ar, txt)

    @dd.displayfield(_("Link URL"))
    def url(self, ar):
        return 'foo'

    @dd.virtualfield(dd.DisplayField(_("Reminder")))
    def reminder(self, request):
        return False
    # reminder.return_type = dd.DisplayField(_("Reminder"))

    def get_calendar(self):
        # for sub in Subscription.objects.filter(user=ar.get_user()):
            # if sub.contains_event(self):
                # return sub
        return None

    @dd.virtualfield(dd.ForeignKey('cal.Calendar'))
    def calendar(self, ar):
        return self.get_calendar()

    def get_print_language(self):
        # if settings.SITE.project_model is not None and self.project:
        if self.project:
            return self.project.get_print_language()
        if self.user:
            return self.user.language
        return settings.SITE.get_default_language()

    @classmethod
    def get_default_table(cls):
        return OneEvent

    @classmethod
    def on_analyze(cls, lino):
        cls.DISABLED_AUTO_FIELDS = dd.fields_list(cls, "summary")
        super(Event, cls).on_analyze(lino)

    def auto_type_changed(self, ar):
        if self.auto_type and self.owner:
            self.summary = self.owner.update_cal_summary(
                self.event_type, self.auto_type)