示例#1
0
文件: ui.py 项目: forexblog/xl
    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)
示例#2
0
文件: actions.py 项目: gary-ops/lino
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))
示例#3
0
文件: ui.py 项目: forexblog/xl
    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)
示例#4
0
文件: ui.py 项目: einarfelix/xl
    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)
示例#5
0
文件: ui.py 项目: einarfelix/xl
        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)
示例#6
0
文件: ui.py 项目: einarfelix/xl
        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)
示例#7
0
文件: models.py 项目: TonisPiip/xl
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)
示例#8
0
文件: models.py 项目: TonisPiip/xl
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"
示例#9
0
文件: models.py 项目: forexblog/xl
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)
示例#10
0
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)
示例#11
0
文件: models.py 项目: forexblog/xl
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)
示例#12
0
#

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
示例#13
0
文件: ui.py 项目: forexblog/xl
    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)
示例#14
0
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)
示例#15
0
文件: models.py 项目: DarioGT/lino
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)))
示例#16
0
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"