def get_daily_field(cls, pc): Event = rt.models.cal.Event def func(fld, obj, ar): # obj is a DailyPlannerRow instance mi = ar.master_instance if mi is None: # e.g. when using DailySlave from dashboard. mi = cls.calendar_view.get_row_by_pk(ar, 0) qs = cls.get_calendar_entries(ar, obj) qs = qs.filter(event_type__planner_column=pc) qs = qs.filter(start_date=mi.date) # pv = ar.param_values # qs = Event.calendar_param_filter(qs, pv) # current_day = pv.get('date', dd.today()) # if current_day: # qs = qs.filter(start_date=current_day) # if obj is cls.model.HEADER_ROW: # qs = qs.filter(start_time__isnull=True) # else: # get_plannable_entries # if obj.start_time: # qs = qs.filter(start_time__gte=obj.start_time, # start_time__isnull=False) # if obj.end_time: # qs = qs.filter(start_time__lt=obj.end_time, # start_time__isnull=False) qs = qs.order_by('start_time') chunks = [e.obj2href(ar, cls.get_calview_div(e, ar)) for e in qs] return E.p(*join_elems(chunks)) return dd.VirtualField(dd.HtmlBox(pc.text), func)
class SignInWithSocialAuth(SignIn): # 20171207 nice as an example of a action dialog window with a # HtmlBox, but we don't currently use it. parameters = dict( social_auth_links=dd.HtmlBox( # _("Other authentications"), default=E.div(*settings.SITE.get_social_auth_links())), # social_auth_links=dd.Constant( # settings.SITE.get_social_auth_links), # social=social_auth_field(), username=dd.CharField(_("Username")), password=dd.PasswordField(_("Password"), blank=True) ) # params_layout = dd.ActionParamsLayout(""" params_layout = dd.Panel(""" username password social_auth_links """, label_align="left", window_size=(60,10))
def get_weekly_field(cls, week_day): def func(fld, obj, ar): # obj is a Plannable instance qs = cls.get_calendar_entries(ar, obj) delta_days = int(ar.rqdata.get('mk', 0) or 0) if ar.rqdata else ar.master_instance.pk # current_day = dd.today() + timedelta(days=delta_days) delta_days += int(week_day.value) - dd.today().weekday() - 1 today = dd.today(delta_days) # current_week_day = current_day + \ # timedelta(days=int(week_day.value) - current_day.weekday() - 1) qs = qs.filter(start_date=today) qs = qs.order_by('start_time') if obj is cls.model.HEADER_ROW: chunks = obj.get_header_chunks(ar, qs, today) else: chunks = obj.get_weekly_chunks(ar, qs, today) return E.table(E.tr(E.td(E.div(*join_elems(chunks)))), CLASS="fixed-table") return dd.VirtualField(dd.HtmlBox(week_day.text), func)
def get_weekday_field(cls, week_day): Event = rt.models.cal.Event def func(fld, obj, ar): # obj is a Plannable instance qs = Event.objects.all() qs = Event.calendar_param_filter(qs, ar.param_values) delta_days = int(ar.rqdata.get('mk', 0) or 0) if ar.rqdata else ar.master_instance.pk # current_day = dd.today() + timedelta(days=delta_days) current_day = dd.today(delta_days) current_week_day = current_day + \ timedelta(days=int(week_day.value) - current_day.weekday() - 1) qs = qs.filter(start_date=current_week_day) qs = qs.order_by('start_time') chunks = obj.get_weekly_chunks(ar, qs, current_week_day) return E.table(E.tr(E.td(E.div(*join_elems(chunks)))), CLASS="fixed-table") return dd.VirtualField(dd.HtmlBox(week_day.text), func)
def w(pc): verbose_name = pc.text def func(fld, week, ar): pv = ar.param_values if pv is None: return qs = Event.objects.all() qs = Event.calendar_param_filter(qs, pv) offset = int(ar.rqdata.get('mk', 0) or 0) if ar.rqdata else ar.master_instance.pk today = dd.today() current_date = dd.today(offset) target_day = week[int(pc.value) - 1] qs = qs.filter(start_date=target_day) qs = qs.order_by('start_time') chunks = [ E.p(e.obj2href(ar, e.colored_calendar_fmt(pv))) for e in qs ] pk = date2pk(target_day) daily, weekly, monthly = make_link_funcs(ar) daily_link = daily(Day(pk), str(target_day.day)) if target_day == today: daily_link = E.b(daily_link) header_items = [daily_link] header_items = gen_insert_button(cls, header_items, Event, ar, target_day) header = E.div(*header_items, align="center", CLASS="header") return E.table( E.tr(E.td(*[header, E.div(*join_elems(chunks))])), CLASS="fixed-table cal-month-cell {} {} {}".format( "current-month" if current_date.month == target_day.month else "other-month", "current-day" if target_day == today else "", "cal-in-past" if target_day < today else "")) return dd.VirtualField(dd.HtmlBox(verbose_name), func)
def w(pc, verbose_name): def func(fld, obj, ar): # obj is the DailyPlannerRow instance pv = ar.param_values qs = Event.objects.filter(event_type__planner_column=pc) qs = Event.calendar_param_filter(qs, pv) current_day = pv.get('date', dd.today()) if current_day: qs = qs.filter(start_date=current_day) if obj.start_time: qs = qs.filter(start_time__gte=obj.start_time, start_time__isnull=False) if obj.end_time: qs = qs.filter(start_time__lt=obj.end_time, start_time__isnull=False) if not obj.start_time and not obj.end_time: qs = qs.filter(start_time__isnull=True) qs = qs.order_by('start_time') chunks = [ e.obj2href(ar, e.colored_calendar_fmt(pv)) for e in qs ] return E.p(*join_elems(chunks)) return dd.VirtualField(dd.HtmlBox(verbose_name), func)
class Enrolment(UserAuthored, Certifiable, DatePeriod): """An **enrolment** is when a given pupil plans to participate in a given course. .. attribute:: course_area .. attribute:: course .. attribute:: pupil .. attribute:: request_date .. attribute:: start_date .. attribute:: end_date .. attribute:: state One of :class:`lino_xl.lib.courses.choicelists.EnrolmentStates`. .. attribute:: places .. attribute:: option .. attribute:: remark .. attribute:: confirmation_details .. attribute:: pupil_info Virtual HtmlBox field showing the name and address of the participant. """ invoiceable_date_field = 'request_date' workflow_state_field = 'state' class Meta: app_label = 'courses' abstract = dd.is_abstract_model(__name__, 'Enrolment') verbose_name = _("Enrolment") verbose_name_plural = _('Enrolments') unique_together = ('course', 'pupil') course_area = CourseAreas.field(blank=True, editable=False) quick_search_fields = pupil_name_fields #~ teacher = models.ForeignKey(Teacher) course = dd.ForeignKey('courses.Course') pupil = dd.ForeignKey(pupil_model, related_name="enrolments_by_pupil") request_date = models.DateField(_("Date of request"), default=dd.today) state = EnrolmentStates.field( default=EnrolmentStates.requested.as_callable) places = models.PositiveIntegerField( pgettext("in a course", "Places used"), help_text=("The number of participants in this enrolment."), default=1) option = dd.ForeignKey('products.Product', verbose_name=_("Option"), related_name='enrolments_by_option', blank=True, null=True) remark = models.CharField(_("Remark"), max_length=200, blank=True) confirmation_details = dd.RichTextField( _("Confirmation details"), blank=True, # format="html" ) submit_insert = ConfirmedSubmitInsert() @dd.chooser() def course_choices(cls, course_area, request_date): dd.logger.info("20160714 course_choices %s", course_area) if request_date is None: request_date = dd.today() flt = Q(enrolments_until__isnull=True) flt |= Q(enrolments_until__gte=request_date) qs = rt.models.courses.Course.objects.filter(flt) if course_area: qs = qs.filter(line__course_area=course_area) return qs @dd.chooser() def pupil_choices(cls, course): Pupil = dd.resolve_model(pupil_model) return Pupil.objects.all() def create_pupil_choice(self, text): """ Called when an unknown pupil name was given. Try to auto-create it. """ Pupil = dd.resolve_model(pupil_model) kw = parse_name(text) if len(kw) != 2: raise ValidationError( "Cannot find first and last names in %r to \ auto-create pupil", text) p = Pupil(**kw) p.full_clean() p.save() return p @dd.chooser() def option_choices(cls, course): if not course.line or not course.line.options_cat: return [] Product = rt.modules.products.Product return Product.objects.filter(cat=course.line.options_cat) def get_confirm_veto(self, ar): """Called from :class:`ConfirmEnrolment <lino_xl.lib.courses.workflows.ConfirmEnrolment>`. If this returns something else than `None`, then the enrolment won't be confirmed and the return value will be displayed to the user. """ if self.course.max_places is None: return # no veto. unlimited places. free = self.course.get_free_places(self.request_date) if free <= 0: return _("No places left in %s") % self.course #~ return _("Confirmation not implemented") def is_guest_for(self, event): """Return `True` if the pupil of this enrolment should be invited to the given event. """ return self.state.uses_a_place def full_clean(self, *args, **kwargs): if self.course and self.course.line: self.course_area = self.course.line.course_area super(Enrolment, self).full_clean(*args, **kwargs) def get_print_templates(self, bm, action): return [self.state.name + bm.template_ext] def __str__(self): return "%s / %s" % (self.course, self.pupil) def get_print_language(self): return self.pupil.language def get_body_template(self): """Overrides :meth:`lino.core.model.Model.get_body_template`.""" return self.course.line.body_template def get_excerpt_title(self): return self.course.line.get_excerpt_title() @dd.virtualfield(dd.HtmlBox(_("Participant"))) def pupil_info(self, ar): if ar is None: return '' elems = [ ar.obj2html(self.pupil, self.pupil.get_full_name(nominative=True)) ] elems += [', '] elems += join_elems(list(self.pupil.address_location_lines()), sep=', ') return E.p(*elems)
class Course(Reservation, Duplicable): """A Course is a group of pupils that regularily meet with a given teacher in a given room to speak about a given subject. The subject of a course is expressed by the :class:`Line`. Notes about automatic event generation: - When an automatically generated event is to be moved to another date, e.g. because it falls into a vacation period, then you simply change it's date. Lino will automatically adapt all subsequent events. - Marking an automatically generated event as "Cancelled" will not create a replacement event. .. attribute:: enrolments_until .. attribute:: max_places Available places. The maximum number of participants to allow in this course. .. attribute:: free_places Number of free places. .. attribute:: requested Number of requested places. .. attribute:: confirmed Number of confirmed places. """ class Meta: app_label = 'courses' abstract = dd.is_abstract_model(__name__, 'Course') verbose_name = _("Activity") verbose_name_plural = _('Activities') line = models.ForeignKey('courses.Line') teacher = models.ForeignKey(teacher_model, verbose_name=_("Instructor"), blank=True, null=True) #~ room = models.ForeignKey(Room,blank=True,null=True) slot = models.ForeignKey(Slot, blank=True, null=True) description = dd.BabelTextField(_("Description"), blank=True) remark = models.TextField(_("Remark"), blank=True) quick_search_fields = 'name line__name line__topic__name' state = CourseStates.field(default=CourseStates.draft.as_callable) max_places = models.PositiveIntegerField( pgettext("in a course", "Available places"), help_text=("Maximum number of participants"), blank=True, null=True) name = models.CharField(_("Designation"), max_length=100, blank=True) enrolments_until = models.DateField(_("Enrolments until"), blank=True, null=True) def on_duplicate(self, ar, master): self.state = CourseStates.draft super(Course, self).on_duplicate(ar, master) @classmethod def get_registrable_fields(cls, site): for f in super(Course, cls).get_registrable_fields(site): yield f yield 'line' yield 'teacher' yield 'name' yield 'enrolments_until' def __str__(self): if self.name: return self.name if self.room is None: return "%s (%s)" % (self.line, dd.fds(self.start_date)) # Note that we cannot use super() with # python_2_unicode_compatible return "%s (%s %s)" % (self.line, dd.fds(self.start_date), self.room) def get_detail_action(self, ar): """Custom :meth:`get_detail_action <lino.core.model.Model.get_detail_action>` because the detail_layout to use depends on the course's line's `course_area`. """ if self.line_id: area = self.line.course_area if area: table = rt.actors.resolve(area.courses_table) a = table.detail_action if ar is None or a.get_view_permission(ar.get_user().profile): return a return None return super(Course, self).get_detail_action(ar) def update_cal_from(self, ar): """Note: if recurrency is weekly or per_weekday, actual start may be later than self.start_date """ # if self.state in (CourseStates.draft, CourseStates.cancelled): # if self.state == CourseStates.cancelled: # ar.info("No start date because state is %s", self.state) # return None return self.start_date def update_cal_event_type(self): return self.line.event_type def update_cal_summary(self, i): label = dd.babelattr(self.line.event_type, 'event_label') return "%s %d" % (label, i) def suggest_cal_guests(self, event): """Look up enrolments of this course and suggest them as guests.""" # logger.info("20140314 suggest_guests") Guest = rt.modules.cal.Guest Enrolment = rt.models.courses.Enrolment if self.line is None: return gr = self.line.guest_role if gr is None: return # fkw = dict(course=self) # states = (EnrolmentStates.requested, EnrolmentStates.confirmed) # fkw.update(state__in=states) qs = Enrolment.objects.filter(course=self).order_by( *dd.plugins.courses.pupil_name_fields) for obj in qs: if obj.is_guest_for(event): yield Guest(event=event, partner=obj.pupil, role=gr) def full_clean(self, *args, **kw): if self.line_id is not None: if self.id is None: descs = dd.field2kw(self.line, 'description') descs = dd.babelkw('description', **descs) for k, v in descs.items(): setattr(self, k, v) if self.every_unit is None: self.every_unit = self.line.every_unit if self.every is None: self.every = self.line.every # if self.enrolments_until is None: # self.enrolments_until = self.start_date # if self.id is not None: # if self.enrolments_until is None: # qs = self.get_existing_auto_events() # if qs.count(): # self.enrolments_until = qs[0].start_date super(Course, self).full_clean(*args, **kw) def before_auto_event_save(self, event): """ Sets room and start_time for automatic events. This is a usage example for :meth:`EventGenerator.before_auto_event_save <lino_xl.lib.cal.models.EventGenerator.before_auto_event_save>`. """ #~ logger.info("20131008 before_auto_event_save") assert not settings.SITE.loading_from_dump assert event.owner == self #~ event = instance if event.is_user_modified(): return #~ if event.is_fixed_state(): return #~ course = event.owner #~ event.project = self event.course = self event.room = self.room if self.slot: event.start_time = self.slot.start_time event.end_time = self.slot.end_time else: event.start_time = self.start_time event.end_time = self.end_time # @dd.displayfield(_("Info")) # def info(self, ar): # if ar is None: # return '' # return ar.obj2html(self) def get_overview_elems(self, ar): elems = [] elems.append(ar.obj2html(self)) if self.teacher_id: elems.append(" / ") elems.append(ar.obj2html(self.teacher)) return elems #~ @dd.displayfield(_("Where")) #~ def where_text(self,ar): # ~ return unicode(self.room) # .company.city or self.company) @dd.displayfield(_("Events")) def events_text(self, ar=None): return ', '.join([ dd.plugins.courses.day_and_month(e.start_date) for e in self.events_by_course.order_by('start_date') ]) @property def events_by_course(self): ct = rt.modules.contenttypes.ContentType.objects.get_for_model( self.__class__) return rt.modules.cal.Event.objects.filter(owner_type=ct, owner_id=self.id) def get_places_sum(self, today=None, **flt): Enrolment = rt.models.courses.Enrolment PeriodEvents = rt.modules.system.PeriodEvents qs = Enrolment.objects.filter(course=self, **flt) rng = DatePeriodValue(today or dd.today(), None) qs = PeriodEvents.active.add_filter(qs, rng) # logger.info("20160502 %s", qs.query) res = qs.aggregate(models.Sum('places')) # logger.info("20140819 %s", res) return res['places__sum'] or 0 def get_free_places(self, today=None): return self.max_places - self.get_used_places(today) def get_used_places(self, today=None): states = EnrolmentStates.filter(uses_a_place=True) return self.get_places_sum(today, state__in=states) # @dd.displayfield(_("Free places"), max_length=5) @dd.virtualfield(models.IntegerField(_("Free places"))) def free_places(self, ar=None): if not self.max_places: return None # _("Unlimited") return self.get_free_places() @dd.virtualfield(models.IntegerField(_("Requested"))) def requested(self, ar): return self.get_places_sum(state=EnrolmentStates.requested) # pv = dict(start_date=dd.today()) # pv.update(state=EnrolmentStates.requested) # return rt.actors.courses.EnrolmentsByCourse.request( # self, param_values=pv) @dd.virtualfield(models.IntegerField(_("Confirmed"))) def confirmed(self, ar): return self.get_places_sum(state=EnrolmentStates.confirmed) # pv = dict(start_date=dd.today()) # pv.update(state=EnrolmentStates.confirmed) # return rt.actors.courses.EnrolmentsByCourse.request( # self, param_values=pv) @dd.requestfield(_("Enrolments")) def enrolments(self, ar): return self.get_enrolments(start_date=dd.today()) def get_enrolments(self, **pv): # pv = dict(start_date=sd, end_date=dd.today()) return rt.actors.courses.EnrolmentsByCourse.request(self, param_values=pv) @dd.virtualfield(dd.HtmlBox(_("Presences"))) def presences_box(self, ar): # not finished if ar is None: return '' pv = ar.param_values # if not pv.start_date or not pv.end_date: # return '' events = self.events_by_course.order_by('start_date') events = rt.modules.system.PeriodEvents.started.add_filter(events, pv) return "TODO: copy logic from presence_sheet.wk.html"
class Enrolment(UserAuthored, Certifiable, DateRange): # invoiceable_date_field = 'request_date' workflow_state_field = 'state' allow_cascaded_copy = 'course' manager_roles_required = dd.login_required() class Meta: app_label = 'courses' abstract = dd.is_abstract_model(__name__, 'Enrolment') verbose_name = _("Enrolment") verbose_name_plural = _('Enrolments') unique_together = ('course', 'pupil') course_area = ActivityLayouts.field(blank=True, editable=False) quick_search_fields = pupil_name_fields #~ teacher = dd.ForeignKey(Teacher) course = dd.ForeignKey('courses.Course') pupil = dd.ForeignKey(pupil_model, verbose_name=_("Participant"), related_name="enrolments_by_pupil") request_date = models.DateField(_("Date of request"), default=dd.today) state = EnrolmentStates.field(default='requested') places = models.PositiveIntegerField( pgettext("in a course", "Places used"), help_text=("The number of participants in this enrolment."), default=1) option = dd.ForeignKey('products.Product', verbose_name=_("Option"), related_name='enrolments_by_option', blank=True, null=True) remark = models.CharField(_("Remark"), max_length=200, blank=True) confirmation_details = dd.RichTextField( _("Confirmation details"), blank=True, # format="html" ) submit_insert = ConfirmedSubmitInsert() @dd.chooser() def course_choices(cls, course_area, request_date): # dd.logger.info("20160714 course_choices %s", course_area) if request_date is None: request_date = dd.today() flt = Q(enrolments_until__isnull=True) flt |= Q(enrolments_until__gte=request_date) qs = rt.models.courses.Course.objects.filter(flt) flt = Q(max_date__isnull=True) flt |= Q(max_date__gte=request_date) qs = qs.filter(flt) if course_area: qs = qs.filter(line__course_area=course_area) enrollable_states = CourseStates.filter(is_exposed=True) qs = qs.filter(state__in=enrollable_states) return qs @dd.chooser() def pupil_choices(cls, course): Pupil = dd.resolve_model(pupil_model) return Pupil.objects.all() def create_pupil_choice(self, text): """ Called when an unknown pupil name was given. Try to auto-create it. """ def create_pupil_choice(self, text): Pupil = dd.resolve_model(pupil_model) return Pupil.create_from_choice(text) # kw = parse_name(text) # if len(kw) != 2: # raise ValidationError( # "Cannot find first and last names in %r to \ # auto-create pupil", text) # p = Pupil(**kw) # p.full_clean() # p.save() # return p @dd.chooser() def option_choices(cls, course): if not course.line or not course.line.options_cat: return [] Product = rt.models.products.Product return Product.objects.filter(cat=course.line.options_cat) def get_overview_elems(self, ar): if self.course_id: return [self.course.obj2href(ar)] return [self.obj2href(ar)] def get_confirm_veto(self, ar): """Called from :class:`ConfirmEnrolment <lino_xl.lib.courses.workflows.std.ConfirmEnrolment>`. If this returns something else than `None`, then the enrolment won't be confirmed and the return value will be displayed to the user. """ if self.course_id is None or self.course.max_places is None: return # no veto. unlimited places. free = self.course.get_free_places(self.request_date) if free <= 0: return _("No places left in %s") % self.course #~ return _("Confirmation not implemented") def get_guest_role(self): if self.course.line: return self.course.line.guest_role or settings.SITE.site_config.pupil_guestrole return settings.SITE.pupil_guestrole def make_guest_for(self, event): if not self.state.uses_a_place: return gr = self.get_guest_role() if gr is not None: return rt.models.cal.Guest(event=event, partner=self.pupil, role=gr) # def is_guest_for(self, event): # """Return `True` if the pupil of this enrolment should be invited to # the given event. # """ # return self.state.uses_a_place def full_clean(self, *args, **kwargs): if self.course_id and self.course.line: self.course_area = self.course.line.course_area super(Enrolment, self).full_clean(*args, **kwargs) def get_print_templates(self, bm, action): return [self.state.name + bm.template_ext] def __str__(self): if self.course_id and self.pupil_id: return "%s / %s" % (self.course, self.pupil) return "%s / %s" % (self.course_id, self.pupil_id) def get_print_language(self): return self.pupil.language def get_body_template(self): """Overrides :meth:`lino.core.model.Model.get_body_template`.""" return self.course.line.body_template def get_excerpt_title(self): return self.course.line.get_excerpt_title() @dd.virtualfield(dd.HtmlBox(_("Participant"))) def pupil_info(self, ar): txt = self.pupil.get_full_name(nominative=True) if ar is None: elems = [txt] else: elems = [ar.obj2html(self.pupil, txt)] elems += [', '] elems += join_elems(list(self.pupil.address_location_lines()), sep=', ') return E.p(*elems)
class Excerpt(TypedPrintable, UserAuthored, Controllable, mixins.ProjectRelated, ContactRelated, Mailable, Postable): """A printable document that describes some aspect of the current situation. .. attribute:: excerpt_type The type of this excerpt (ForeignKey to :class:`ExcerptType`). .. attribute:: owner The object being printed by this excerpt. See :attr:`Controllable.owner <lino.modlib.gfks.mixins.Controllable.owner>`. .. attribute:: company The optional company of the :attr:`recipient` of this excerpt. See :attr:`ContactRelated.company <lino_xl.lib.contacts.mixins.ContactRelated.company>`. .. attribute:: contact_person The optional contact person of the :attr:`recipient` of this excerpt. See :attr:`ContactRelated.contact_person <lino_xl.lib.contacts.mixins.ContactRelated.contact_person>`. .. attribute:: recipient The recipient of this excerpt. See :attr:`ContactRelated.recipient <lino_xl.lib.contacts.mixins.ContactRelated.recipient>` .. attribute:: language The language used for printing this excerpt. .. attribute:: date .. attribute:: time .. method:: get_address_html See :meth:`lino_xl.lib.contacts.mixins.ContactRelated.get_address_html`. Return the address of the :attr:`recipient` of this excerpt. """ manager_roles_required = dd.login_required(OfficeStaff) # manager_level_field = 'office_level' allow_cascaded_delete = "owner" class Meta: app_label = 'excerpts' abstract = dd.is_abstract_model(__name__, 'Excerpt') verbose_name = _("Excerpt") verbose_name_plural = _("Excerpts") excerpt_type = dd.ForeignKey('excerpts.ExcerptType') body_template_content = BodyTemplateContentField(_("Body template")) language = dd.LanguageField() # if dd.is_installed('outbox'): # mails_by_owner = dd.ShowSlaveTable('outbox.MailsByController') def get_body_template(self): """Return the body template to use for this excerpt.""" owner = self.owner # owner is None e.g. if is a broken GFK if owner is not None: assert self.__class__ is not owner.__class__ tplname = owner.get_body_template() if tplname: return tplname return self.excerpt_type.body_template def get_body_template_filename(self): tplname = self.get_body_template() if not tplname: return None mc = self.excerpt_type.content_type.model_class() tplgroup = mc.get_template_group() return rt.find_config_file(tplname, tplgroup) def get_body_template_name(self): tplname = self.get_body_template() if not tplname: return None mc = self.excerpt_type.content_type.model_class() tplgroup = mc.get_template_group() return tplgroup + '/' + tplname def disabled_fields(self, ar): rv = super(Excerpt, self).disabled_fields(ar) rv = rv | set(['excerpt_type', 'project']) if self.build_time: rv |= self.PRINTABLE_FIELDS return rv def __str__(self): if self.build_time: return naturaltime(self.build_time) # return _("%(owner)s (printed %(time)s)") % dict( # owner=self.owner, time=naturaltime(self.build_time)) return _("Unprinted %s #%s") % (self._meta.verbose_name, self.pk) def get_mailable_type(self): return self.excerpt_type def get_mailable_subject(self): return six.text_type(self.owner) # .get_mailable_subject() def get_template_groups(self): ptype = self.get_printable_type() if ptype is None: raise Exception("20140520 Must have excerpt_type.") grp = ptype.content_type.model_class().get_template_group() return [grp, 'excerpts'] def filename_root(self): # mainly because otherwise we would need to move files around on # existing sites et = self.excerpt_type if et is None or not et.certifying: return super(Excerpt, self).filename_root() assert et.certifying o = self.owner if o is None: return super(Excerpt, self).filename_root() name = o._meta.app_label + '.' + o.__class__.__name__ if not et.primary: name += '.' + str(et.pk) name += '-' + str(o.pk) return name def get_print_templates(self, bm, action): """Overrides :meth:`lino.modlib.printing.mixins.Printable.get_print_templates`. When printing an excerpt, the controlling database objects gets a chance to decide which template to use. """ et = self.excerpt_type if et is not None and et.certifying: if isinstance(self.owner, Certifiable): tpls = self.owner.get_excerpt_templates(bm) if tpls is not None: return tpls return super(Excerpt, self).get_print_templates(bm, action) # ptype = self.get_printable_type() # # raise Exception("20150710 %s" % self.owner) # if ptype is not None and ptype.template: # return [ptype.template] # # return [bm.get_default_template(self)] # return [dd.plugins.excerpts.get_default_template(bm, self.owner)] # def get_recipient(self): # rec = super(Excerpt, self).get_recipient() # if rec is None and hasattr(self.owner, 'recipient'): # return self.owner.recipient # return rec # recipient = property(get_recipient) def get_printable_type(self): return self.excerpt_type def get_print_language(self): return self.language def unused_on_create(self, ar): # replaced by signal below super(Excerpt, self).on_create(ar) if not self.owner_id: if self.project: self.owner = self.project self.language = self.owner.get_print_language() @dd.chooser() def excerpt_type_choices(cls, owner): # logger.info("20150702 %s", owner) qs = rt.modules.excerpts.ExcerptType.objects.order_by('name') if owner is None: # e.g. when choosing on the *parameter* field # return qs.filter(content_type__isnull=True) return qs.filter() ct = ContentType.objects.get_for_model(owner.__class__) return qs.filter(content_type=ct) @property def date(self): "Used in templates" if self.build_time: return self.build_time.date() return dd.today() @property def time(self): "Used in templates" if self.build_time: return self.build_time.time() return timezone.now() @dd.virtualfield(dd.HtmlBox(_("Preview"))) def preview(self, ar): if ar is None: return '' with translation.override(self.get_print_language()): ctx = self.get_printable_context(ar) return ar.html_text(ctx['body']) # return '<div class="htmlText">%s</div>' % ctx['body'] def get_printable_context(self, ar=None, **kw): """Adds a series of names to the context used when rendering printable documents. Extends :meth:`lino.core.model.Model.get_printable_context`. """ if self.owner is not None: kw = self.owner.get_printable_context(ar, **kw) kw = super(Excerpt, self).get_printable_context(**kw) kw.update(obj=self.owner) body = '' if self.excerpt_type_id is not None: etype = self.excerpt_type if etype.backward_compat: kw.update(this=self.owner) tplname = self.get_body_template_name() if tplname and ar is not None: body = settings.SITE.plugins.jinja.render_jinja( ar, tplname, kw) # env = settings.SITE.plugins.jinja.renderer.jinja_env # template = env.get_template(tplname) # # logger.info("body template %s (%s)", tplname, template) # body = ar.render_jinja(template, **kw) # # logger.info("20160311 body template %s (%s) -> %s", # # tplname, template, body) kw.update(body=body) return kw @classmethod def on_analyze(cls, site): cls.PRINTABLE_FIELDS = dd.fields_list( cls, "project excerpt_type " "body_template_content " "company contact_person language " "user build_method") super(Excerpt, cls).on_analyze(site)
class Mail(UserAuthored, Printable, UploadController, mixins.ProjectRelated, Controllable): class Meta: verbose_name = _("Outgoing Mail") verbose_name_plural = _("Outgoing Mails") send_mail = SendMail() date = models.DateField(verbose_name=_("Date"), help_text=""" The official date to be printed on the document. """) subject = models.CharField( _("Subject"), max_length=200, blank=True, # null=True ) body = dd.RichTextField(_("Body"), blank=True, format='html') #~ type = dd.ForeignKey(MailType,null=True,blank=True) #~ sender = dd.ForeignKey(settings.SITE.user_model, #~ verbose_name=_("Sender")) #~ related_name='outmails_by_sender', #~ blank=True,null=True) sent = models.DateTimeField(null=True, editable=False) def on_create(self, ar): self.date = settings.SITE.today() super(Mail, self).on_create(ar) #~ def disabled_fields(self,ar): #~ if not self.owner.post_as_attachment: #~ return ['body'] #~ return [] #~ @classmethod #~ def get_model_actions(self,table): #~ for x in super(Mail,self).get_model_actions(table): yield x #~ yield 'send_mail',SendMail() def get_print_language(self): if self.user is not None: return self.user.language return super(Mail, self).get_print_language() def __str__(self): return u'%s #%s' % (self._meta.verbose_name, self.pk) def get_recipients(self, rr): #~ recs = [] recs = [ str(r) for r in Recipient.objects.filter(mail=self, type=RecipientTypes.to) ] return ', '.join(recs) recipients = dd.VirtualField(dd.HtmlBox(_("Recipients")), get_recipients) def get_row_permission(self, ar, state, ba): """ Mails may not be edited after they have been sent. """ if self.sent and not ba.action.readonly: #~ logger.info("20120920 Mail.get_row_permission()") return False return super(Mail, self).get_row_permission(ar, state, ba)
# def preview(obj, ar): return obj.html or obj.text def spam(obj): """Checks if the message is spam or not """ if obj.subject.startswith("*****SPAM*****"): return True else: return False dd.inject_field('django_mailbox.Message', 'preview', dd.VirtualField(dd.HtmlBox(_("Preview")), preview)) dd.inject_field('django_mailbox.Message', 'ticket', dd.ForeignKey('tickets.Ticket', blank=True, null=True)) dd.update_field('django_mailbox.Message', 'from_header', format="plain") from .ui import * @dd.schedule_often(10) def get_new_mail(): for mb in rt.models.django_mailbox.Mailbox.objects.filter(active=True): mails = mb.get_new_mail() for mail in mails: if spam(mail): mail.spam = True
def get_monthly_field(cls, wd): Events = rt.models.cal.Events def func(fld, obj, ar): # obj is the first day of the week to show # pv = ar.param_values today = dd.today() # if pv is None: # return qs = cls.get_calendar_entries(ar, None) # qs = Event.objects.all() # qs = Event.calendar_param_filter(qs, pv) mi = ar.master_instance if mi is None: return target_day = cls.get_row_by_pk(ar, obj.pk + int(wd.value) - 1) current_month = mi.date.month nav = mi.planner # offset = ar.master_instance.pk # offset = int(ar.rqdata.get('mk', 0) or 0) if ar.rqdata else ar.master_instance.pk # current_date = dd.today(offset) # pk = offset + int(wd.value) - 1 # target_day = cls.get_row_by_pk(ar, pk) # if target_day is None: # return # target_day = week[int(wd.value)-1] qs = qs.filter(start_date=target_day.date) qs = qs.order_by('start_time') chunks = [ E.p(e.obj2href(ar, cls.get_calview_div(e, ar))) for e in qs ] # pk = date2pk(target_day) # nav.daily_view # sar = ar.spawn_request(actor=actor, param_values=ar.param_values) # rnd = settings.SITE.kernel.default_renderer # def func(day, text): # # day.navigation_mode = actor.navigation_mode # return rnd.ar2button(sar, day, text, style="", icon_name=None, title=str(day)) # daily = nav.daily_button_func(ar) daily_link = daily(target_day, str(target_day.date.day)) if target_day.date == today: daily_link = E.b(daily_link) # header_items = [daily_link] # header_items = Event.gen_insert_button(cls, header_items, ar, target_day) header_items = [daily_link] btn = ar.gen_insert_button(Events, start_date=target_day.date) if btn: header_items.append(btn) header = E.div(*header_items, align="center", CLASS="header") return E.table( E.tr(E.td(*[header, E.div(*join_elems(chunks))])), CLASS="fixed-table cal-month-cell {} {} {}".format( "current-month" if current_month == target_day.date.month else "other-month", "current-day" if target_day.date == today else "", "cal-in-past" if target_day.date < today else "")) return dd.VirtualField(dd.HtmlBox(wd.text), func)
class Excerpt(TypedPrintable, UserAuthored, Controllable, mixins.ProjectRelated, ContactRelated, Mailable, Postable): manager_roles_required = dd.login_required(OfficeStaff) # manager_level_field = 'office_level' allow_cascaded_delete = "owner" class Meta: app_label = 'excerpts' abstract = dd.is_abstract_model(__name__, 'Excerpt') verbose_name = _("Excerpt") verbose_name_plural = _("Excerpts") excerpt_type = dd.ForeignKey('excerpts.ExcerptType') body_template_content = BodyTemplateContentField(_("Body template")) language = dd.LanguageField() # if dd.is_installed('outbox'): # mails_by_owner = dd.ShowSlaveTable('outbox.MailsByController') def get_body_template(self): """Return the body template to use for this excerpt.""" owner = self.owner # owner is None e.g. if is a broken GFK if owner is not None: assert self.__class__ is not owner.__class__ tplname = owner.get_body_template() if tplname: return tplname return self.excerpt_type.body_template def get_body_template_filename(self): tplname = self.get_body_template() if not tplname: return None mc = self.excerpt_type.content_type.model_class() tplgroup = mc.get_template_group() return rt.find_config_file(tplname, tplgroup) def get_body_template_name(self): tplname = self.get_body_template() if not tplname: return None mc = self.excerpt_type.content_type.model_class() tplgroup = mc.get_template_group() return tplgroup + '/' + tplname def disabled_fields(self, ar): rv = super(Excerpt, self).disabled_fields(ar) rv = rv | set(['excerpt_type', 'project']) if self.build_time: rv |= self.PRINTABLE_FIELDS return rv def __str__(self): if self.build_time: return str(naturaltime(self.build_time)) # return _("%(owner)s (printed %(time)s)") % dict( # owner=self.owner, time=naturaltime(self.build_time)) return gettext("Unprinted %s #%s") % (self._meta.verbose_name, self.pk) def get_mailable_type(self): return self.excerpt_type def get_mailable_subject(self): return str(self.owner) # .get_mailable_subject() def get_template_groups(self): ptype = self.get_printable_type() if ptype is None: raise Exception("20140520 Must have excerpt_type.") grp = ptype.content_type.model_class().get_template_group() return [grp, u'excerpts'] def filename_root(self): # mainly because otherwise we would need to move files around on # existing sites et = self.excerpt_type if et is None or not et.certifying: return super(Excerpt, self).filename_root() assert et.certifying o = self.owner if o is None: return super(Excerpt, self).filename_root() name = o._meta.app_label + '.' + o.__class__.__name__ if not et.primary: name += '.' + str(et.pk) name += '-' + str(o.pk) return name def get_print_templates(self, bm, action): """When printing a certifying excerpt, the controlling database object gets a chance to decide which template to use. Overrides :meth:`lino.modlib.printing.Printable.get_print_templates`. """ et = self.excerpt_type if et is not None and et.certifying: if isinstance(self.owner, Certifiable): tpls = self.owner.get_excerpt_templates(bm) if tpls is not None: # print("20190506 Excerpt.get_print_templates()", tpls) return tpls return super(Excerpt, self).get_print_templates(bm, action) # ptype = self.get_printable_type() # # raise Exception("20150710 %s" % self.owner) # if ptype is not None and ptype.template: # return [ptype.template] # # return [bm.get_default_template(self)] # return [dd.plugins.excerpts.get_default_template(bm, self.owner)] # def get_recipient(self): # rec = super(Excerpt, self).get_recipient() # if rec is None and hasattr(self.owner, 'recipient'): # return self.owner.recipient # return rec # recipient = property(get_recipient) def get_printable_type(self): return self.excerpt_type def get_print_language(self): return self.language @dd.chooser() def excerpt_type_choices(cls, owner): # logger.info("20150702 %s", owner) qs = rt.models.excerpts.ExcerptType.objects.order_by('name') if owner is None: # e.g. when choosing on the *parameter* field # return qs.filter(content_type__isnull=True) return qs.filter() ct = ContentType.objects.get_for_model(owner.__class__) return qs.filter(content_type=ct) @property def date(self): "Used in templates" if self.build_time: return self.build_time.date() return dd.today() @property def time(self): "Used in templates" if self.build_time: return self.build_time.time() return timezone.now() @dd.virtualfield(dd.HtmlBox(_("Preview"))) def preview(self, ar): if ar is None or self.owner is None: # body templates should not need to test whether obj is defined return '' lang = self.get_print_language() or \ settings.SITE.DEFAULT_LANGUAGE.django_code with translation.override(lang): ctx = self.get_printable_context(ar) return ar.html_text(ctx['body']) def before_printable_build(self, bm): super(Excerpt, self).before_printable_build(bm) if self.owner is not None: return self.owner.before_printable_build(bm) def get_printable_context(self, ar=None, **kw): """Adds a series of names to the context used when rendering printable documents. Extends :meth:`lino.core.model.Model.get_printable_context`. """ if self.owner is not None: kw = self.owner.get_printable_context(ar, **kw) kw = super(Excerpt, self).get_printable_context(**kw) kw.update(obj=self.owner) kw.update(excerpt=self) body = '' # logger.info("20180114 get_printable_context() %s", self) if self.excerpt_type_id is not None: etype = self.excerpt_type if etype.backward_compat: kw.update(this=self.owner) tplname = self.get_body_template_name() # logger.info("20180114 tplname is %s -", # tplname) if tplname and ar is not None: body = settings.SITE.plugins.jinja.render_jinja( ar, tplname, kw) # env = settings.SITE.plugins.jinja.renderer.jinja_env # template = env.get_template(tplname) # # logger.info("body template %s (%s)", tplname, template) # body = ar.render_jinja(template, **kw) # logger.info("20160311 body template %s -> %s", # tplname, body) kw.update(body=body) return kw @classmethod def on_analyze(cls, site): cls.PRINTABLE_FIELDS = dd.fields_list( cls, "project excerpt_type " "body_template_content " "company contact_person language " "user build_method") super(Excerpt, cls).on_analyze(site)
class Poll(UserAuthored, mixins.CreatedModified, Referrable): """A series of questions.""" class Meta: app_label = 'polls' abstract = dd.is_abstract_model(__name__, 'Poll') verbose_name = _("Poll") verbose_name_plural = _("Polls") ordering = ['ref'] title = models.CharField(_("Heading"), max_length=200) details = models.TextField(_("Details"), blank=True) default_choiceset = models.ForeignKey('polls.ChoiceSet', null=True, blank=True, related_name='polls', verbose_name=_("Default Choiceset")) default_multiple_choices = models.BooleanField(_("Allow multiple choices"), default=False) questions_to_add = models.TextField( _("Questions to add"), help_text=_("Paste text for questions to add. " "Every non-empty line will create one question."), blank=True) state = PollStates.field(default=PollStates.draft.as_callable) workflow_state_field = 'state' def __unicode__(self): return self.ref or self.title def after_ui_save(self, ar, cw): if self.questions_to_add: # print "20150203 self.questions_to_add", self, # self.questions_to_add q = None qkw = dict() number = 1 for ln in self.questions_to_add.splitlines(): ln = ln.strip() if ln: if ln.startswith('#'): q.details = ln[1:] q.save() continue elif ln.startswith('='): q = Question(poll=self, title=ln[1:], is_heading=True, **qkw) number = 1 else: q = Question(poll=self, title=ln, number=str(number), **qkw) number += 1 q.full_clean() q.save() qkw.update(seqno=q.seqno + 1) self.questions_to_add = '' self.save() # save again because we modified afterwards super(Poll, self).after_ui_save(ar, cw) @dd.virtualfield(dd.HtmlBox(_("Result"))) def result(self, ar): return E.div(*tuple(get_poll_result(self)))
class Course(Reservation, Duplicable, Printable): class Meta: app_label = 'courses' abstract = dd.is_abstract_model(__name__, 'Course') verbose_name = _("Activity") verbose_name_plural = _('Activities') # verbose_name = _("Event") # verbose_name_plural = _('Events') site_field_name = 'room' # line = dd.ForeignKey('courses.Line', null=True, blank=True) line = dd.ForeignKey('courses.Line') teacher = dd.ForeignKey( teacher_model, verbose_name=teacher_label, blank=True, null=True) #~ room = dd.ForeignKey(Room,blank=True,null=True) slot = dd.ForeignKey(Slot, blank=True, null=True) description = dd.BabelTextField(_("Description"), blank=True) remark = models.TextField(_("Remark"), blank=True) quick_search_fields = 'name line__name line__topic__name' state = CourseStates.field(default='draft') max_places = models.PositiveIntegerField( pgettext("in a course", "Available places"), help_text=("Maximum number of participants"), blank=True, null=True) name = models.CharField(_("Designation"), max_length=100, blank=True) enrolments_until = models.DateField( _("Enrolments until"), blank=True, null=True) print_presence_sheet = PrintPresenceSheet(show_in_bbar=False) print_presence_sheet_html = PrintPresenceSheet( show_in_bbar=False, build_method='weasy2html', label=format_lazy(u"{}{}",_("Presence sheet"), _(" (HTML)"))) @dd.displayfield(_("Print")) def print_actions(self, ar): if ar is None: return '' elems = [] elems.append(ar.instance_action_button( self.print_presence_sheet)) elems.append(ar.instance_action_button( self.print_presence_sheet_html)) return E.p(*join_elems(elems, sep=", ")) def on_duplicate(self, ar, master): self.state = CourseStates.draft super(Course, self).on_duplicate(ar, master) @classmethod def add_param_filter( cls, qs, lookup_prefix='', show_exposed=None, **kwargs): qs = super(Course, cls).add_param_filter(qs, **kwargs) exposed_states = CourseStates.filter(is_exposed=True) fkw = dict() fkw[lookup_prefix + 'state__in'] = exposed_states if show_exposed == dd.YesNo.no: qs = qs.exclude(**fkw) elif show_exposed == dd.YesNo.yes: qs = qs.filter(**fkw) return qs @classmethod def get_registrable_fields(cls, site): for f in super(Course, cls).get_registrable_fields(site): yield f yield 'line' yield 'teacher' yield 'name' yield 'enrolments_until' def __str__(self): if self.name: return self.name if self.line_id is None: line = self._meta.verbose_name else: line = self.line if self.room is None: return "%s (%s)" % (line, dd.fds(self.start_date)) # Note that we cannot use super() with # python_2_unicode_compatible return "%s (%s %s)" % (line, dd.fds(self.start_date),self.room) def get_detail_action(self, ar): """Custom :meth:`get_detail_action <lino.core.model.Model.get_detail_action>` because the detail_layout to use depends on the course's line's `course_area`. """ if self.line_id: area = self.line.course_area if area: table = rt.models.resolve(area.courses_table) ba = table.detail_action ba = ba.action.defining_actor.detail_action # if ar is None or ba.get_row_permission(ar, self, None): # return ba if ar is None or ba.get_view_permission(ar.get_user().user_type): return ba return None return super(Course, self).get_detail_action(ar) def update_cal_from(self, ar): """Note: if recurrency is weekly or per_weekday, actual start may be later than self.start_date """ # if self.state in (CourseStates.draft, CourseStates.cancelled): # if self.state == CourseStates.cancelled: # ar.info("No start date because state is %s", self.state) # return None return self.start_date def update_cal_event_type(self): return self.line.event_type def update_cal_summary(self, et, i): if self.every_unit == Recurrencies.once: return self.name or str(self.line) return "%s %s" % (dd.babelattr(et, 'event_label'), i) def get_events_user(self): """The user of generated events is not the course manager (author) but the teacher. """ if self.teacher: return self.teacher.get_as_user() or self.user return self.user def suggest_cal_guests(self, event): """Look up enrolments of this course and suggest them as guests.""" # logger.info("20140314 suggest_guests") Enrolment = rt.models.courses.Enrolment if self.line is None: return gr = self.line.guest_role if gr is None: return # fkw = dict(course=self) # states = (EnrolmentStates.requested, EnrolmentStates.confirmed) # fkw.update(state__in=states) qs = Enrolment.objects.filter(course=self).order_by( *[f.name for f in Enrolment.quick_search_fields]) for obj in qs: # if obj.is_guest_for(event): g = obj.make_guest_for(event) if g is not None: yield g def full_clean(self, *args, **kw): if self.line_id is not None: if self.id is None: descs = dd.field2kw(self.line, 'description') descs = dd.babelkw('description', **descs) for k, v in descs.items(): setattr(self, k, v) if self.every_unit is None: self.every_unit = self.line.every_unit if self.every is None: self.every = self.line.every # if self.room is None: # self.room = self.line.room # if self.enrolments_until is None: # self.enrolments_until = self.start_date # if self.id is not None: # if self.enrolments_until is None: # qs = self.get_existing_auto_events() # if qs.count(): # self.enrolments_until = qs[0].start_date super(Course, self).full_clean(*args, **kw) def before_auto_event_save(self, event): """ Set room and start_time/end_time for automatic events. """ assert not settings.SITE.loading_from_dump assert event.owner == self event.course = self event.room = self.room if self.slot: event.start_time = self.slot.start_time event.end_time = self.slot.end_time else: event.start_time = self.start_time event.end_time = self.end_time super(Course, self).before_auto_event_save(event) # @dd.displayfield(_("Info")) # def info(self, ar): # if ar is None: # return '' # return ar.obj2html(self) def get_overview_elems(self, ar): elems = [] # elems.append(ar.obj2html(self)) elems.append(self.obj2href(ar)) if self.teacher_id: elems.append(" / ") # elems.append(ar.obj2html(self.teacher)) elems.append(self.teacher.obj2href(ar)) return elems # @classmethod # def on_analyze(cls, site): # super(Course, cls).on_analyze(site) # dd.update_field(Course, 'detail_link', # default=site.models.courses.Course._meta.verbose_name) #~ @dd.displayfield(_("Where")) #~ def where_text(self,ar): # ~ return unicode(self.room) # .company.city or self.company) @dd.displayfield(_("Calendar entries")) def events_text(self, ar=None): return ', '.join([ day_and_month(e.start_date) for e in self.events_by_course().order_by('start_date')]) def events_by_course(self, **kwargs): ct = rt.models.contenttypes.ContentType.objects.get_for_model( self.__class__) kwargs.update(owner_type=ct, owner_id=self.id) return rt.models.cal.Event.objects.filter(**kwargs) def get_places_sum(self, today=None, **flt): Enrolment = rt.models.courses.Enrolment PeriodEvents = rt.models.system.PeriodEvents qs = Enrolment.objects.filter(course=self, **flt) # see voga.projects.roger.tests.test_max_places if today is None: rng = DateRangeValue(dd.today(), None) qs = PeriodEvents.active.add_filter(qs, rng) else: qs = PeriodEvents.active.add_filter(qs, today) # logger.info("20160502 %s", qs.query) res = qs.aggregate(models.Sum('places')) # logger.info("20140819 %s", res) return res['places__sum'] or 0 def get_free_places(self, today=None): if not self.max_places: return None # _("Unlimited") return self.max_places - self.get_used_places(today) def get_used_places(self, today=None): states = EnrolmentStates.filter(uses_a_place=True) return self.get_places_sum(today, state__in=states) # @dd.displayfield(_("Free places"), max_length=5) @dd.virtualfield(models.IntegerField(_("Free places"))) def free_places(self, ar=None): # if not self.max_places: # return None # _("Unlimited") return self.get_free_places() @dd.virtualfield(models.IntegerField(_("Requested"))) def requested(self, ar): return self.get_places_sum(state=EnrolmentStates.requested) # pv = dict(start_date=dd.today()) # pv.update(state=EnrolmentStates.requested) # return rt.models.courses.EnrolmentsByCourse.request( # self, param_values=pv) @dd.virtualfield(models.IntegerField(_("Confirmed"))) def confirmed(self, ar): return self.get_places_sum(state=EnrolmentStates.confirmed) # pv = dict(start_date=dd.today()) # pv.update(state=EnrolmentStates.confirmed) # return rt.models.courses.EnrolmentsByCourse.request( # self, param_values=pv) @dd.virtualfield(models.IntegerField(_("Trying"))) def trying(self, ar): return self.get_places_sum(state=EnrolmentStates.trying) @dd.requestfield(_("Enrolments")) def enrolments(self, ar): return self.get_enrolments(start_date=dd.today()) def get_enrolments(self, **pv): # pv = dict(start_date=sd, end_date=dd.today()) return rt.models.courses.EnrolmentsByCourse.request( self, param_values=pv) @dd.virtualfield(dd.HtmlBox(_("Presences"))) def presences_box(self, ar): # not finished if ar is None: return '' pv = ar.param_values # if not pv.start_date or not pv.end_date: # return '' events = self.events_by_course().order_by('start_date') events = rt.models.system.PeriodEvents.started.add_filter(events, pv) return "TODO: copy logic from presence_sheet.wk.html"