Exemplo n.º 1
0
class Product(mixins.BabelNamed):
    """A product is something you can sell or buy.

    .. attribute:: description
    .. attribute:: cat
    .. attribute:: delivery_unit

    

    """
    class Meta:
        app_label = 'products'
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        abstract = dd.is_abstract_model(__name__, 'Product')

    description = dd.BabelTextField(verbose_name=_("Long description"),
                                    blank=True,
                                    null=True)
    cat = models.ForeignKey(ProductCat,
                            verbose_name=_("Category"),
                            blank=True,
                            null=True)

    delivery_unit = DeliveryUnit.field(
        default=DeliveryUnit.piece.as_callable())

    if vat:
        vat_class = vat.VatClasses.field(blank=True)
    else:
        vat_class = dd.DummyField()
Exemplo n.º 2
0
class Concept(mixins.BabelNamed):
    """A word and its translation in different languages.
    """
    class Meta:
        verbose_name = _("Concept")
        verbose_name_plural = _("Concepts")

    abbr = dd.BabelCharField(_("Abbreviation"), max_length=30, blank=True)
    wikipedia = dd.BabelCharField(_("Wikipedia"), max_length=200, blank=True)

    definition = dd.BabelTextField(_("Definition"), blank=True)
    is_jargon_domain = models.BooleanField(
        _("Jargon domain"),
        help_text=
        _("Whether this concept designates a domain of specialized vocabulary."
          ),
        default=False)

    def summary_row(self, ar=None):
        if self.abbr:
            return [
                "%s (%s)" %
                (dd.babelattr(self, 'name'), dd.babelattr(self, 'abbr'))
            ]
        return [dd.babelattr(self, 'name')]
Exemplo n.º 3
0
class EventType(mixins.BabelNamed):

    class Meta:
        app_label = 'notes'
        verbose_name = pgettext_lazy(u"notes", u"Event Type")
        verbose_name_plural = _("Event Types")
    remark = models.TextField(verbose_name=_("Remark"), blank=True)
    body = dd.BabelTextField(_("Body"), blank=True, format='html')
Exemplo n.º 4
0
class EventType(mixins.BabelNamed):
    """
    A possible choice for :attr:`Note.event_type`.
    """
    class Meta:
        app_label = 'notes'
        verbose_name = pgettext_lazy(u"notes", u"Event Type")
        verbose_name_plural = _("Event Types")

    remark = models.TextField(verbose_name=_("Remark"), blank=True)
    body = dd.BabelTextField(_("Body"), blank=True, format='html')
Exemplo n.º 5
0
class Topic(StructuredReferrable, BabelNamed):

    ref_max_length = 5

    class Meta:
        app_label = 'topics'
        verbose_name = _("Topic")
        verbose_name_plural = _("Topics")
        abstract = dd.is_abstract_model(__name__, 'Topic')

    description_text = dd.BabelTextField(verbose_name=_("Long description"),
                                         blank=True,
                                         null=True)
Exemplo n.º 6
0
class Product(mixins.BabelNamed, mixins.Referrable):
    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        abstract = dd.is_abstract_model(__name__, 'Product')

    description = dd.BabelTextField(verbose_name=_("Long description"),
                                    blank=True,
                                    null=True)
    cat = models.ForeignKey(ProductCat,
                            verbose_name=_("Category"),
                            blank=True,
                            null=True)

    if vat:
        vat_class = vat.VatClasses.field(blank=True)
    else:
        vat_class = dd.DummyField()
Exemplo n.º 7
0
class Topic(BabelNamed, Referrable):
    """A topic is something somebody can be interested in.

    .. attribute:: ref
    .. attribute:: description
    

    """
    class Meta:
        app_label = 'topics'
        verbose_name = _("Topic")
        verbose_name_plural = _("Topics")
        abstract = dd.is_abstract_model(__name__, 'Topic')

    description = dd.BabelTextField(verbose_name=_("Long description"),
                                    blank=True,
                                    null=True)

    topic_group = dd.ForeignKey('topics.TopicGroup', blank=True, null=True)
Exemplo n.º 8
0
class Page(Referrable, Hierarchical, Sequenced, Publishable):
    class Meta:
        verbose_name = _("Node")
        verbose_name_plural = _("Nodes")

    title = dd.BabelCharField(_("Title"), max_length=200, blank=True)
    body = dd.BabelTextField(_("Body"), blank=True, format='plain')
    raw_html = models.BooleanField(_("raw html"), default=False)

    @classmethod
    def get_dashboard_items(cls, user):
        obj = cls.get_by_ref('index', None)
        if obj is not None:
            yield obj

    def get_absolute_url(self, **kwargs):
        if self.ref:
            if self.ref != 'index':
                return dd.plugins.pages.build_plain_url(self.ref, **kwargs)
        return dd.plugins.pages.build_plain_url(**kwargs)

    def get_sidebar_caption(self):
        if self.title:
            return dd.babelattr(self, 'title')
        if self.ref == 'index':
            return str(_('Home'))
        if self.ref:
            return self.ref
        return str(self.id)

        #~ if self.ref or self.parent:
        #~ return self.ref
        #~ return unicode(_('Home'))

    def get_sidebar_item(self, request, other):
        kw = dict()
        add_user_language(kw, request)
        url = self.get_absolute_url(**kw)
        a = E.a(self.get_sidebar_caption(), href=url)
        if self == other:
            return E.li(a, **{'class': 'active'})
        return E.li(a)

    def get_sidebar_html(self, request):
        items = []
        #~ loop over top-level nodes
        for n in Page.objects.filter(parent__isnull=True).order_by('seqno'):
            #~ items += [li for li in n.get_sidebar_items(request,self)]
            items.append(n.get_sidebar_item(request, self))
            if self.is_parented(n):
                children = []
                for ch in n.children.order_by('seqno'):
                    children.append(ch.get_sidebar_item(request, self))
                if len(children):
                    items.append(E.ul(*children, **{'class': 'nav nav-list'}))

        e = E.ul(*items, **{'class': 'nav nav-list'})
        return tostring_pretty(e)

    def get_sidebar_menu(self, request):
        qs = Page.objects.filter(parent__isnull=True)
        #~ qs = self.children.all()
        yield ('/', 'index', str(_('Home')))
        #~ yield ('/downloads/', 'downloads', 'Downloads')
        #~ yield ('/about', 'about', 'About')
        #~ if qs is not None:
        for obj in qs:
            if obj.ref and obj.title:
                yield ('/' + obj.ref, obj.ref, dd.babelattr(obj, 'title'))
Exemplo n.º 9
0
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"
Exemplo n.º 10
0
class Line(Referrable, Duplicable, ExcerptTitle):
    """An **activity line** (or **series**) groups courses into a
    configurable list of categories.

    We chose the word "line" instead of "series" because it has a
    plural form (not sure whether this idea was so cool).

    .. attribute:: name

        The designation of this activity line as seen by the user
        e.g. when selecting the line.

        One field for every :attr:`language <lino.core.site.Site.language>`.

    .. attribute:: excerpt_title

        The text to print as title in enrolments.

        See also
        :attr:`lino_xl.lib.excerpts.mixins.ExcerptTitle.excerpt_title`.

    .. attribute:: body_template

        The body template to use when printing an activity of this
        line.  Leave empty to use the site's default (defined by
        `body_template` on the
        :class:`lino_xl.lib.excerpts.models.ExcerptType` for
        :class:`Course`)

    .. attribute:: course_area

        Pointer to :class:`CourseAreas`.  This is used only when an
        application defines several variants of
        :class:`EnrolmentsByPupil`.

    """
    class Meta:
        app_label = 'courses'
        abstract = dd.is_abstract_model(__name__, 'Line')
        verbose_name = pgettext("singular form", "Activity line")
        verbose_name_plural = pgettext("plural form", 'Activity lines')

    # ref = dd.NullCharField(_("Reference"), max_length=30, unique=True)
    course_area = CourseAreas.field(default=CourseAreas.default.as_callable)
    # default=CourseAreas.get_lazy('default')
    topic = models.ForeignKey(Topic, blank=True, null=True)
    description = dd.BabelTextField(_("Description"), blank=True)

    every_unit = Recurrencies.field(_("Recurrency"),
                                    default=Recurrencies.weekly.as_callable,
                                    blank=True)  # iCal:DURATION
    every = models.IntegerField(_("Repeat every"), default=1)

    event_type = dd.ForeignKey(
        'cal.EventType',
        null=True,
        blank=True,
        help_text=_("The type of calendar events to be generated. "
                    "If this is empty, no calendar events will be generated."))

    fee = dd.ForeignKey('products.Product',
                        blank=True,
                        null=True,
                        verbose_name=_("Participation fee"),
                        related_name='lines_by_fee')

    guest_role = dd.ForeignKey(
        "cal.GuestRole",
        blank=True,
        null=True,
        verbose_name=_("Manage presences as"),
        help_text=_("The default guest role for particpants of events for "
                    "courses in this series. "
                    "Leave empty if you don't want any presence management."))

    options_cat = dd.ForeignKey('products.ProductCat',
                                verbose_name=_("Options category"),
                                related_name="courses_lines_by_options_cat",
                                blank=True,
                                null=True)

    fees_cat = dd.ForeignKey('products.ProductCat',
                             verbose_name=_("Fees category"),
                             related_name="courses_lines_by_fees_cat",
                             blank=True,
                             null=True)

    body_template = models.CharField(max_length=200,
                                     verbose_name=_("Body template"),
                                     blank=True,
                                     help_text="The body template to use when "
                                     "printing a course of this series. "
                                     "Leave empty to use the site's default.")

    def __str__(self):
        name = dd.babelattr(self, 'name')
        if self.ref:
            return "{0} ({1})".format(self.ref, name)
        return name
        # return "{0} #{1}".format(self._meta.verbose_name, self.pk)

    @dd.chooser()
    def fee_choices(cls, fees_cat):
        Product = rt.modules.products.Product
        if not fees_cat:
            return Product.objects.none()
        return Product.objects.filter(cat=fees_cat)

    @dd.chooser(simple_values=True)
    def body_template_choices(cls):
        return dd.plugins.jinja.list_templates(
            '.body.html', rt.models.courses.Enrolment.get_template_group(),
            'excerpts')
Exemplo n.º 11
0
class Page(mixins.Referrable, mixins.Hierarchical, mixins.Sequenced):
    """
    Deserves more documentation.
    """

    class Meta:
        verbose_name = _("Node")
        verbose_name_plural = _("Nodes")

    title = dd.BabelCharField(_("Title"), max_length=200, blank=True)
    body = dd.BabelTextField(_("Body"), blank=True, format='plain')

    raw_html = models.BooleanField(_("raw html"), default=False)

    def get_absolute_url(self):
        if self.ref:
            if self.ref != 'index':
                return PAGES.build_plain_url(self.ref)
        return PAGES.build_plain_url()

    def get_sidebar_caption(self):
        if self.title:
            return dd.babelattr(self, 'title')
        if self.ref == 'index':
            return unicode(_('Home'))
        if self.ref:
            return self.ref
        return str(self.id)

        #~ if self.ref or self.parent:
            #~ return self.ref
        #~ return unicode(_('Home'))

    def get_sidebar_item(self, request, other):
        a = E.a(self.get_sidebar_caption(), href=self.get_absolute_url())
        if self == other:
            return E.li(a, class_='active')
        return E.li(a)

    def get_sidebar_html(self, request):
        items = []
        #~ loop over top-level nodes
        for n in Page.objects.filter(parent__isnull=True).order_by('seqno'):
            #~ items += [li for li in n.get_sidebar_items(request,self)]
            items.append(n.get_sidebar_item(request, self))
            if self.is_parented(n):
                children = []
                for ch in n.children.order_by('seqno'):
                    children.append(ch.get_sidebar_item(request, self))
                if len(children):
                    items.append(E.ul(*children, class_='nav nav-list'))

        e = E.ul(*items, class_='nav nav-list')
        return E.tostring_pretty(e)

    def get_sidebar_menu(self, request):
        #~ qs = self.get_siblings()
        qs = Page.objects.filter(parent__isnull=True)
        #~ qs = self.children.all()
        yield ('/', 'index', unicode(_('Home')))
            #~ yield ('/downloads/', 'downloads', 'Downloads')
        #~ yield ('/about', 'about', 'About')
        #~ if qs is not None:
        for obj in qs:
            if obj.ref and obj.title:
                yield ('/' + obj.ref, obj.ref, dd.babelattr(obj, 'title'))
Exemplo n.º 12
0
class Line(Referrable, Duplicable, ExcerptTitle, ContactRelated):
    class Meta:
        app_label = 'courses'
        abstract = dd.is_abstract_model(__name__, 'Line')
        verbose_name = pgettext("singular form", "Activity line")
        verbose_name_plural = pgettext("plural form", 'Activity lines')

    course_area = ActivityLayouts.field(default='default')
    # default=ActivityLayouts.get_lazy('default')
    topic = dd.ForeignKey(Topic, blank=True, null=True)
    description = dd.BabelTextField(_("Description"), blank=True)

    every_unit = Recurrencies.field(_("Recurrency"),
                                    default='weekly',
                                    blank=True)
    every = models.IntegerField(_("Repeat every"), default=1)

    event_type = dd.ForeignKey(
        'cal.EventType',
        null=True,
        blank=True,
        help_text=_(
            "The type of calendar entries to be generated. "
            "If this is empty, no calendar entries will be generated."))

    fee = dd.ForeignKey('products.Product',
                        blank=True,
                        null=True,
                        verbose_name=_("Attendance fee"),
                        related_name='lines_by_fee')

    guest_role = dd.ForeignKey(
        "cal.GuestRole",
        blank=True,
        null=True,
        verbose_name=_("Manage presences as"),
        help_text=_("The default guest role for particpants of "
                    "calendar entries for activities in this series. "
                    "Leave empty if you don't want any presences management."))

    options_cat = dd.ForeignKey('products.ProductCat',
                                verbose_name=_("Options category"),
                                related_name="courses_lines_by_options_cat",
                                blank=True,
                                null=True)

    fees_cat = dd.ForeignKey('products.ProductCat',
                             verbose_name=_("Fees category"),
                             related_name="courses_lines_by_fees_cat",
                             blank=True,
                             null=True)

    body_template = models.CharField(max_length=200,
                                     verbose_name=_("Body template"),
                                     blank=True,
                                     help_text="The body template to use when "
                                     "printing a course of this series. "
                                     "Leave empty to use the site's default.")

    def __str__(self):
        name = dd.babelattr(self, 'name')
        if self.ref:
            return "{0} ({1})".format(self.ref, name)
        return name
        # return "{0} #{1}".format(self._meta.verbose_name, self.pk)

    @dd.chooser()
    def fee_choices(cls, fees_cat):
        Product = rt.models.products.Product
        if not fees_cat:
            return Product.objects.none()
        return Product.objects.filter(cat=fees_cat)

    @dd.chooser(simple_values=True)
    def body_template_choices(cls):
        return dd.plugins.jinja.list_templates(
            '.body.html', rt.models.courses.Enrolment.get_template_group(),
            'excerpts')
Exemplo n.º 13
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))
        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):
        if self.line_id:
            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:
        # if self.line_id is None:
        #     return
        # gr = self.line.guest_role or settings.SITE.pupil_guestrole
        # 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:
            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):
        if cal is not 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)
Exemplo n.º 14
0
class PaymentTerm(mixins.BabelNamed, mixins.Referrable):
    """A convention on how an invoice should be paid.

    The following fields define the default value for `due_date`:

    .. attribute:: days

        Number of days to add to :attr:`voucher_date`.

    .. attribute:: months

        Number of months to add to :attr:`voucher_date`.

    .. attribute:: end_of_month

        Whether to move :attr:`voucher_date` to the end of month.

    .. attribute:: printed_text

        Used in :xfile:`sales/VatProductInvoice/trailer.html` as
        follows::

            {% if obj.payment_term.printed_text %}
            {{parse(obj.payment_term.printed_text)}}
            {% else %}
            {{_("Payment terms")}} : {{obj.payment_term}}
            {% endif %}

    The :attr:`printed_text` field is important when using
    **prepayments** or other more complex payment terms.  Lino uses a
    rather simple approach to handle prepayment invoices: only the
    global amount and the final due date is stored in the database,
    all intermediate amounts and due dates are just generated in the
    printable document. You just define one :class:`PaymentTerm
    <lino_cosi.lib.ledger.models.PaymentTerm>` row for each prepayment
    formula and configure your :attr:`printed_text` field. For
    example::

        Prepayment <b>30%</b> 
        ({{(obj.total_incl*30)/100}} {{obj.currency}})
        due on <b>{{fds(obj.due_date)}}</b>, remaining 
        {{obj.total_incl - (obj.total_incl*30)/100}} {{obj.currency}}
        due 10 days before delivery.

    """
    class Meta:
        app_label = 'ledger'
        verbose_name = _("Payment Term")
        verbose_name_plural = _("Payment Terms")

    days = models.IntegerField(_("Days"), default=0)
    months = models.IntegerField(_("Months"), default=0)
    end_of_month = models.BooleanField(_("End of month"), default=False)

    printed_text = dd.BabelTextField(_("Printed text"),
                                     blank=True,
                                     format='plain')

    def get_due_date(self, date1):
        assert isinstance(date1, datetime.date), \
            "%s is not a date" % date1
        d = date1 + relativedelta(months=self.months, days=self.days)
        if self.end_of_month:
            d = last_day_of_month(d)
        return d
Exemplo n.º 15
0
class Product(mixins.BabelNamed, Duplicable):

    class Meta:
        app_label = 'products'
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        abstract = dd.is_abstract_model(__name__, 'Product')

    description = dd.BabelTextField(
        verbose_name=_("Long description"),
        blank=True, null=True)
    cat = dd.ForeignKey(
        ProductCat, verbose_name=_("Category"),
        blank=True, null=True)

    delivery_unit = DeliveryUnits.field(default='piece')
    product_type = ProductTypes.field()
    vat_class = VatClasses.field(blank=True)

    @dd.chooser()
    def cat_choices(self, product_type):
        qs = rt.models.products.ProductCats.request().data_iterator
        if product_type is not None:
            qs = qs.filter(product_type=product_type)
        return qs

    @classmethod
    def get_product_choices(cls, partner):
        """Return a list of products (fees) that are allowed for the specified partner.
        """
        Product = cls
        qs = Product.objects.filter(product_type=ProductTypes.default)
        qs = qs.order_by('name')
        rules = PriceRule.objects.all()
        for pf in PriceFactors.get_list_items():
            rules = rules.filter(
                Q(**{pf.field_name: getattr(partner, pf.field_name)}) |
                Q(**{pf.field_name + '__isnull': True}))
        return [p for p in qs if rules.filter(product=p).count() > 0]
        # TODO: add rules condition as subquery to qs and return the query, not
        # the list

    @classmethod
    def get_rule_fee(cls, partner, event_type):
        if partner is None:
            return
        for rule in rt.models.products.PriceRule.objects.order_by('seqno'):
            ok = True
            for pf in PriceFactors.get_list_items():
                rv = getattr(rule, pf.field_name)
                if rv:
                    pv = getattr(partner, pf.field_name)
                    if pv != rv:
                        # print("20181128a {} != {}".format(rv, pv))
                        ok = False
            # if rule.tariff and rule.tariff != tariff:
            #     # print("20181128b {} != {}".format(rule.tariff, tariff))
            #     ok = False
            if rule.event_type and rule.event_type != event_type:
                # print("20181128c {} != {}".format(rule.event_type, event_type))
                ok = False

            if ok and rule.fee is not None:
                return rule.fee

    def full_clean(self):
        # print("20191210", self.name, self.vat_class)
        if self.product_type is None:
            if self.cat_id:
                self.product_type = self.cat.product_type or ProductTypes.default
            else:
                self.product_type = ProductTypes.default
        super(Product, self).full_clean()