Esempio n. 1
0
def inject_summary_fields(sender, **kw):
    SiteSummary = rt.models.working.SiteSummary
    UserSummary = rt.models.working.UserSummary
    WorkSite = rt.models.tickets.Site
    Ticket = dd.plugins.working.ticket_model
    for t in ReportingTypes.get_list_items():
        k = t.name + '_hours'
        dd.inject_field(SiteSummary, k,
                        dd.DurationField(t.text, null=True, blank=True))
        dd.inject_field(UserSummary, k,
                        dd.DurationField(t.text, null=True, blank=True))
        dd.inject_field(Ticket, k,
                        dd.DurationField(t.text, null=True, blank=True))

        def make_getter(t):
            k = t.name + '_hours'

            def getter(obj, ar):
                qs = SiteSummary.objects.filter(master=obj, year__isnull=True)
                d = qs.aggregate(**{k: models.Sum(k)})
                n = d[k]
                return n

            return getter

        dd.inject_field(
            WorkSite, k,
            dd.VirtualField(dd.DurationField(t.text), make_getter(t)))

    if False:  # removed 20181211 because useless
        for ts in TicketStates.get_list_items():
            k = ts.get_summary_field()
            if k is not None:
                dd.inject_field(SiteSummary, k, models.IntegerField(ts.text))

                def make_getter(ts):
                    k = ts.get_summary_field()

                    def getter(obj, ar):
                        if ar is None:
                            return ''
                        qs = SiteSummary.objects.filter(master=obj)
                        d = qs.aggregate(**{k: models.Sum(k)})
                        n = d[k]
                        if n == 0:
                            return ''
                        sar = rt.models.tickets.TicketsBySite.request(
                            obj, param_values=dict(state=ts, show_active=None))
                        # n = sar.get_total_count()
                        url = ar.renderer.request_handler(sar)
                        if url is None:
                            return str(n)
                        return E.a(str(n), href='javascript:' + url)

                    return getter

                dd.inject_field(
                    WorkSite, k,
                    dd.VirtualField(dd.DisplayField(ts.text), make_getter(ts)))
Esempio n. 2
0
    def setup_columns(cls):
        def w(ut):
            def func(fld, obj, ar):
                if isinstance(ut.role, obj):
                    return "☑"
                return ""

            return func

        names = []
        for ut in UserTypes.get_list_items():
            name = "ut" + ut.value
            # vf = dd.VirtualField(
            #     models.BooleanField(str(ut.value)), w(ut))
            vf = dd.VirtualField(dd.DisplayField(str(ut.value)), w(ut))
            cls.add_virtual_field(name, vf)
            names.append(name + ":3")
        # cls.column_names = "name:20 description:40 " + ' '.join(names)
        cls.column_names = "name:20 " + ' '.join(names)
Esempio n. 3
0
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)
Esempio n. 4
0
class Event(Component, Ended, mixins.TypedPrintable, Mailable, Postable):
    """A calendar event is a lapse of time to be visualized in a calendar.

    .. attribute:: user

         The responsible user.

    .. attribute:: assigned_to

        This field is usually empty.  Setting it to another user means "I
        am not fully responsible for this event".  This will cause the
        other user to see this event in his :class:`MyAssignedEvents`
        table.

        This field is cleared when somebody calls :class:`TakeEvent` on
        the event.

    .. 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:: linked_date

         Shows the date and time of the event with a link that opens
         all events on that day (cal.EventsByDay)

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

    update_guests = UpdateGuests()

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

    assigned_to = dd.ForeignKey(settings.SITE.user_model,
                                verbose_name=_("Assigned to"),
                                related_name="cal_events_assigned",
                                blank=True,
                                null=True)

    move_next = MoveEventNext()

    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 __unicode__(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):
        qs = self.get_conflicting_events()
        if qs is None:
            return False
        if self.event_type is not None:
            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
        #~ 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)
        if self.id is not None:  # don't conflict with myself
            qs = qs.exclude(id=self.id)
        # generated events never conflict with other generated events
        # of same owner. Rule needed for update_events.
        if self.auto_type is not None:
            qs = qs.exclude(
                # auto_type=self.auto_type,
                owner_id=self.owner_id,
                owner_type=self.owner_type)
        if self.room is not None:
            # other event in the same room
            c1 = Q(room=self.room)
            # other event locks all rooms (e.h. holidays)
            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_create(self, ar):
        super(Event, self).after_ui_create(ar)
        self.update_guests.run_from_code(ar)

    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.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 = datetime.datetime.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, rt.modules.contacts.Partner):
                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, rt.modules.contacts.Partner):
                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

    def get_system_note_recipients(self, request, silent):
        if self.user != request.user:
            yield "%s <%s>" % (unicode(self.user), self.user.email)
        if silent:
            return
        for g in self.guest_set.all():
            if g.partner.email:
                yield "%s <%s>" % (unicode(g.partner), g.partner.email)

    @dd.displayfield(_("When"))
    def when_text(self, ar):
        if ar is None:
            return ''
        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
        # return ar.obj2html(self, txt)

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

    @dd.displayfield(_("When"))
    def linked_date(self, ar):
        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.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:
            return self.project.get_print_language()
        return self.user.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)
Esempio n. 5
0
def set_upload_shortcuts(sender, **kw):
    """This is the successor for `quick_upload_buttons`."""

    # remember that models might have been overridden.
    UploadType = sender.modules.uploads.UploadType

    for i in Shortcuts.items():

        def f(obj, ar):
            if obj is None or ar is None:
                return E.div()
            try:
                utype = UploadType.objects.get(shortcut=i)
            except UploadType.DoesNotExist:
                return E.div()
            items = []
            target = sender.modules.resolve(i.target)
            sar = ar.spawn_request(actor=target,
                                   master_instance=obj,
                                   known_values=dict(type=utype))
            # param_values=dict(pupload_type=et))
            n = sar.get_total_count()
            if n == 0:
                iar = target.insert_action.request_from(sar,
                                                        master_instance=obj)
                btn = iar.ar2button(
                    None,
                    _("Upload"),
                    icon_name="page_add",
                    title=_("Upload a file from your PC to the server."))
                items.append(btn)
            elif n == 1:
                after_show = ar.get_status()
                obj = sar.data_iterator[0]
                items.append(
                    sar.renderer.href_button(dd.build_media_url(obj.file.name),
                                             _("show"),
                                             target='_blank',
                                             icon_name='page_go',
                                             style="vertical-align:-30%;",
                                             title=_(
                                                 "Open the uploaded file in a "
                                                 "new browser window")))
                after_show.update(record_id=obj.pk)
                items.append(
                    sar.window_action_button(
                        sar.ah.actor.detail_action,
                        after_show,
                        _("Edit"),
                        icon_name='application_form',
                        title=_("Edit metadata of the uploaded file.")))
            else:
                obj = sar.sliced_data_iterator[0]
                items.append(
                    ar.obj2html(obj, pgettext("uploaded file", "Last")))

                btn = sar.renderer.action_button(obj,
                                                 sar,
                                                 sar.bound_action,
                                                 _("All {0} files").format(n),
                                                 icon_name=None)
                items.append(btn)

            return E.div(*join_elems(items, ', '))

        vf = dd.VirtualField(dd.DisplayField(i.text), f)
        dd.inject_field(i.model_spec, i.name, vf)
Esempio n. 6
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)
Esempio n. 7
0
def set_excerpts_actions(sender, **kw):
    """Installs (1) print management actions on models for which there is
    an excerpt type and (2) the excerpt shortcut fields defined in
    :class:`lino_xl.lib.excerpts.choicelists.Shortcuts`.

    """
    # logger.info("20140401 %s.set_attest_actions()", __name__)

    # in case ExcerptType is overridden
    ExcerptType = sender.modules.excerpts.ExcerptType
    Excerpt = sender.modules.excerpts.Excerpt

    try:
        etypes = [(obj, obj.content_type) for obj in ExcerptType.objects.all()]
    except (OperationalError, ProgrammingError, UnresolvedChoice) as e:
        dd.logger.debug("Failed to set excerpts actions : %s", e)
        # Happens e.g. when the database has not yet been migrated
        etypes = []

    for atype, ct in etypes:
        if ct is not None:
            m = ct.model_class()
            if m is not None:  # e.g. database contains types for
                # models that existed before but have
                # been removed
                an = atype.get_action_name()
                m.define_action(
                    **{an: CreateExcerpt(atype, six.text_type(atype))})
                # dd.logger.info("Added print action to %s", m)

                # if atype.certifying and not issubclass(m, Certifiable):
                #     m.define_action(
                #         clear_printed=ClearCache())

    # An attestable model must also inherit
    # :class:`lino.mixins.printable.BasePrintable` or some subclass
    # thereof.

    for i in Shortcuts.items():

        def f(obj, ar):
            if ar is None:
                return ''
            if obj is None:
                return E.div()
            try:
                et = ExcerptType.objects.get(shortcut=i)
            except ExcerptType.DoesNotExist:
                return E.div()
            items = []
            if True:
                sar = ar.spawn(ExcerptsByOwner,
                               master_instance=obj,
                               param_values=dict(excerpt_type=et))
                n = sar.get_total_count()
                if n > 0:
                    ex = sar.sliced_data_iterator[0]
                    items.append(ar.obj2html(ex, _("Last")))

                    ba = sar.bound_action
                    btn = sar.renderer.action_button(obj,
                                                     sar,
                                                     ba,
                                                     "%s (%d)" % (_("All"), n),
                                                     icon_name=None)
                    items.append(btn)

                ia = getattr(obj, et.get_action_name())
                btn = ar.instance_action_button(ia,
                                                _("Create"),
                                                icon_name=None)
                items.append(btn)

            else:
                ot = ContentType.objects.get_for_model(obj.__class__)
                qs = Excerpt.objects.filter(owner_id=obj.pk,
                                            owner_type=ot,
                                            excerpt_type=et)
                if qs.count() > 0:
                    ex = qs[0]
                    txt = ExcerptsByOwner.format_excerpt(ex)
                    items.append(ar.obj2html(ex, txt))
            return E.div(*join_elems(items, ', '))

        vf = dd.VirtualField(dd.DisplayField(i.text), f)
        dd.inject_field(i.model_spec, i.name, vf)