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"
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"))
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)
""", 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"),
# -*- 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("""
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 []
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)