Beispiel #1
0
    def unused_send_individual_email(self):
        """"""
        if not self.user.email:
            # debug level because we don't want to see this message
            # every 10 seconds:
            dd.logger.debug("User %s has no email address", self.user)
            return
        # dd.logger.info("20151116 %s %s", ar.bound_action, ar.actor)
        # ar = ar.spawn_request(renderer=dd.plugins.bootstrap3.renderer)
        # sar = BaseRequest(
        #     # user=self.user, renderer=dd.plugins.bootstrap3.renderer)
        #     user=self.user, renderer=settings.SITE.kernel.text_renderer)
        # tpl = dd.plugins.notify.email_subject_template
        # subject = tpl.format(obj=self)
        if self.owner is None:
            subject = str(self)
        else:
            subject = pgettext("notification",
                               "{} in {}").format(self.message_type,
                                                  self.owner)
        subject = settings.EMAIL_SUBJECT_PREFIX + subject
        # template = rt.get_template('notify/body.eml')
        # context = dict(obj=self, E=E, rt=rt, ar=sar)
        # body = template.render(**context)

        template = rt.get_template('notify/individual.eml')
        context = dict(obj=self, E=E, rt=rt)
        body = template.render(**context)

        sender = settings.SERVER_EMAIL
        rt.send_email(subject, sender, body, [self.user.email])
        self.sent = timezone.now()
        self.save()
Beispiel #2
0
class MarkTicketTalk(TicketAction):
    """Mark this ticket as talk.
    """
    label = pgettext("verb", "Talk")
    required_states = "new opened working sleeping ready"
    action_name = 'mark_talk'
    needs_site = True
Beispiel #3
0
    def send_individual_email(self):
        """"""
        if not self.user.email:
            # debug level because we don't want to see this message
            # every 10 seconds:
            dd.logger.debug("User %s has no email address", self.user)
            return
        # dd.logger.info("20151116 %s %s", ar.bound_action, ar.actor)
        # ar = ar.spawn_request(renderer=dd.plugins.bootstrap3.renderer)
        # sar = BaseRequest(
        #     # user=self.user, renderer=dd.plugins.bootstrap3.renderer)
        #     user=self.user, renderer=settings.SITE.kernel.text_renderer)
        # tpl = dd.plugins.notify.email_subject_template
        # subject = tpl.format(obj=self)
        if self.owner is None:
            subject = str(self)
        else:
            subject = pgettext("notification", "{} in {}").format(
                self.message_type, self.owner)
        subject = settings.EMAIL_SUBJECT_PREFIX + subject
        # template = rt.get_template('notify/body.eml')
        # context = dict(obj=self, E=E, rt=rt, ar=sar)
        # body = template.render(**context)

        template = rt.get_template('notify/individual.eml')
        context = dict(obj=self, E=E, rt=rt)
        body = template.render(**context)

        sender = settings.SERVER_EMAIL
        rt.send_email(subject, sender, body, [self.user.email])
        self.sent = timezone.now()
        self.save()
Beispiel #4
0
class MarkTicketWorking(TicketAction):
    """Mark this ticket as working.
    """
    action_name = 'mark_working'
    label = pgettext("verb", "Start")
    required_states = 'new talk opened'
    needs_site = True
Beispiel #5
0
    #     """
    #     self.required_roles |= set(args)

class MessageTypes(dd.ChoiceList):
    verbose_name = _("Message Type")
    verbose_name_plural = _("Message Types")
    item_class = MessageType

    # @classmethod
    # def register_type(cls, name, *args, **kwargs):
    #     cls.add_item_lazy(name, *args, **kwargs)


add = MessageTypes.add_item
add('system', _("System event"))
add('change', pgettext("message type", "Change"))
# add('300', _("Action"), 'action')
# add('300', _("Warning"), 'warning')
# add('400', _("Note"), 'note')
# add('900', _("Notification"), 'notification')



class MailModes(dd.ChoiceList):
    verbose_name = _("Notification mode")
    verbose_name_plural = _("Notification modes")
    
add = MailModes.add_item
add('silent', _("Silent"), 'silent')
add('never', _("No mails"), 'never')
# add('immediately', _("Immediately"), 'immediately')  # obsolete
Beispiel #6
0
 class Meta:
     app_label = 'cal'
     abstract = dd.is_abstract_model(__name__, 'Event')
     #~ abstract = True
     verbose_name = pgettext("cal", "Event")
     verbose_name_plural = pgettext("cal", "Events")
Beispiel #7
0
#                 verbose_name=lng.name, blank=True), cef_level_getter(lng))
#         dd.inject_field(
#             'avanti.Client', 'cef_level_'+lng.prefix, fld)

#     def fc(**kwargs):
#         return (**kwargs)

from lino.api import _, pgettext
from lino_xl.lib.clients.choicelists import ClientStates
ClientStates.default_value = 'coached'
ClientStates.clear()
add = ClientStates.add_item
add('05', _("Incoming"), 'incoming')
add('07', _("Informed"), 'informed')
add('10', _("Newcomer"), 'newcomer')
add('15', pgettext("client state", "Equal"), 'equal')  # Gleichgestellt
add('20', pgettext("client state", "Registered"), 'coached')
add('25', _("Inactive"), 'inactive')
add('30', _("Ended"), 'former')
add('40', _("Abandoned"), 'refused')

# alias
# ClientStates.coached = ClientStates.newcomer

# @dd.receiver(dd.pre_analyze)
# def add_merge_action(sender, **kw):
#     apps = sender.modules
#     for m in (apps.avanti.Client, apps.contacts.Person,
#               apps.contacts.Company):
#         m.define_action(merge_row=dd.MergeAction(m))
Beispiel #8
0
class TicketStates(dd.Workflow):

    # verbose_name = _("Ticket state")
    verbose_name_plural = _("Ticket states")
    item_class = TicketState
    column_names = "value name text button_text active"
    active = models.BooleanField(_("Active"), default=False)
    show_in_todo = models.BooleanField(_("To do"), default=False)
    required_roles = dd.login_required(dd.SiteStaff)
    # max_length = 3

add = TicketStates.add_item

add('10', _("New"), 'new', active=True, show_in_todo=True)
add('15', _("Talk"), 'talk', active=True)
add('20', pgettext("ticket state", "Open"), 'opened', active=True, show_in_todo=True)
# add('21', _("Sticky"), 'sticky', active=True)
# add('22', _("Started"), 'started', active=True, show_in_todo=True)
add('22', _("Working"), 'working', active=True, show_in_todo=True)
add('30', _("Sleeping"), 'sleeping')
add('40', _("Ready"), 'ready', active=True)
add('50', _("Closed"), 'closed')
add('60', _("Refused"), 'cancelled')
# TicketStates.default_value = 'new'


if settings.SITE.use_new_unicode_symbols:

    TicketStates.new.button_text =u"📥"  # INBOX TRAY (U+1F4E5)
    TicketStates.talk.button_text =u"🗪"  # TWO SPEECH BUBBLES (U+1F5EA)
    TicketStates.opened.button_text = u"☉"  # SUN (U+2609)	
Beispiel #9
0
from lino.utils.xmlgen.html import E
from lino.utils import join_elems

class MessageTypes(dd.ChoiceList):
    """
    The list of possible choices for the `message_type` field
    of a :class:`Message`.
    """
    verbose_name = _("Message Type")
    verbose_name_plural = _("Message Types")


add = MessageTypes.add_item
add('100', _("System event"), 'system')
add('200', pgettext("message type", "Change"), 'change')
add('300', _("Action"), 'action')
# add('300', _("Warning"), 'warning')
# add('400', _("Note"), 'note')
# add('900', _("Notification"), 'notification')



class MailModes(dd.ChoiceList):
    """How the system should send email notifications to a user.

    """
    verbose_name = _("Email notification mode")
    verbose_name_plural = _("Email notification modes")
    
add = MailModes.add_item
Beispiel #10
0
class MarkTicketOpened(TicketAction):
    """Mark this ticket as open.
    """
    action_name = 'mark_opened'
    label = pgettext("verb", "Open")
    required_states = 'talk new closed'
Beispiel #11
0
class Ticket(UserAuthored, mixins.CreatedModified, TimeInvestment, Votable,
             Starrable, Workable, Prioritized, Feasible, UploadController,
             mixins.Referrable):
    quick_search_fields = "summary description ref"
    workflow_state_field = 'state'
    create_session_on_create = True
    disable_author_assign = False

    class Meta:
        app_label = 'tickets'
        verbose_name = _("Ticket")
        verbose_name_plural = _('Tickets')
        abstract = dd.is_abstract_model(__name__, 'Ticket')

    # project = dd.DummyField()
    # project = dd.ForeignKey(
    #     'tickets.Project', blank=True, null=True,
    #     related_name="tickets_by_project")
    site = dd.ForeignKey(site_model,
                         blank=True,
                         null=True,
                         related_name="tickets_by_site")
    # topic = dd.ForeignKey('topics.Topic', blank=True, null=True)
    # nickname = models.CharField(_("Nickname"), max_length=50, blank=True)

    private = models.BooleanField(_("Private"), default=False)

    summary = models.CharField(pgettext("Ticket", "Summary"),
                               max_length=200,
                               blank=False,
                               help_text=_("Short summary of the problem."))
    description = dd.RichTextField(_("Description"), blank=True)
    upgrade_notes = dd.RichTextField(_("Resolution"),
                                     blank=True,
                                     format='plain')
    ticket_type = dd.ForeignKey('tickets.TicketType', blank=True, null=True)
    duplicate_of = dd.ForeignKey('self',
                                 blank=True,
                                 null=True,
                                 verbose_name=_("Duplicate of"),
                                 related_name="duplicated_tickets")

    end_user = dd.ForeignKey(end_user_model,
                             verbose_name=_("End user"),
                             blank=True,
                             null=True,
                             related_name="reported_tickets")
    state = TicketStates.field(default=TicketStates.as_callable('new'))
    # rating = Ratings.field(blank=True)
    deadline = models.DateField(verbose_name=_("Deadline"),
                                blank=True,
                                null=True)

    # deprecated fields:
    reported_for = dd.ForeignKey(
        milestone_model,
        related_name='tickets_reported',
        verbose_name='Reported for',
        blank=True,
        null=True,
        help_text=_("Milestone for which this ticket has been reported."))
    fixed_for = dd.ForeignKey(  # no longer used since 20150814
        milestone_model,
        related_name='tickets_fixed',
        verbose_name='Fixed for',
        blank=True,
        null=True,
        help_text=_("The milestone for which this ticket has been fixed."))
    reporter = dd.ForeignKey(settings.SITE.user_model,
                             blank=True,
                             null=True,
                             verbose_name=_("Reporter"))
    waiting_for = models.CharField(_("Waiting for"),
                                   max_length=200,
                                   blank=True)
    feedback = models.BooleanField(_("Feedback"), default=False)
    standby = models.BooleanField(_("Standby"), default=False)

    spawn_triggered = SpawnTicket(_("Spawn triggered ticket"),
                                  LinkTypes.triggers)
    # spawn_triggered = SpawnTicket("⚇", LinkTypes.triggers)  # "\u2687"
    # spawn_ticket = SpawnTicket("", LinkTypes.requires)  # "\u2687"

    fixed_since = models.DateTimeField(_("Fixed since"),
                                       blank=True,
                                       null=True,
                                       editable=False)
    # fixed_date = models.DateField(
    #     _("Fixed date"), blank=True, null=True)
    # fixed_time = models.TimeField(
    #     _("Fixed time"), blank=True, null=True)
    last_commenter = dd.ForeignKey(settings.SITE.user_model,
                                   related_name='tickets_last_commter',
                                   verbose_name=_("Commented Last"),
                                   blank=True,
                                   null=True,
                                   help_text=_("Last user to make a comment"))

    comments = GenericRelation('comments.Comment',
                               content_type_field='owner_type',
                               object_id_field='owner_id',
                               related_query_name="ticket")

    quick_assign_to_action = QuickAssignTo()

    @dd.displayfield(_("Assign to"))
    def quick_assign_to(self, ar):
        if ar is None:
            return ''
        elems = []
        found_existing = False
        if self.site and self.site.group:
            for m in self.site.group.members.all():
                kw = dict(action_param_values=dict(assign_to=m.user))
                u = m.user
                label = u.initials or u.username or str(u.pk)
                if m.user == self.assigned_to:
                    elems.append(label)
                    found_existing = True
                else:
                    elems.append(
                        ar.instance_action_button(self.quick_assign_to_action,
                                                  label=label,
                                                  request_kwargs=kw))
        if self.assigned_to_id and not found_existing:
            u = self.assigned_to
            label = u.initials or u.username or str(u.pk)
            elems.append(label + "!")
            # ticket is assigned to a user who is not member of the team
        return E.p(*join_elems(elems, sep=", "))

    @classmethod
    def get_user_queryset(cls, user):
        qs = super(Ticket, cls).get_user_queryset(user)
        if user.is_anonymous:
            qs = qs.filter(private=False, site__private=False)
        elif not user.user_type.has_required_roles([TicketsStaff]):
            qs = qs.filter(
                Q(site__group__members__user__id=user.pk) | Q(user=user)
                | Q(end_user=user) | Q(assigned_to=user)
                | Q(private=False, site__private=False))
        return qs

    def is_comment_private(self, comment, ar):
        if self.site_id and self.site.private:
            return True
        return self.private

    @classmethod
    def get_comments_filter(cls, user):
        if user.is_anonymous:
            return super(Ticket, cls).get_comments_filter(user)
        if user.user_type.has_required_roles([PrivateCommentsReader]):
            return None
        flt = Q(ticket__site__group__members__user__id=user.pk)
        flt |= Q(ticket__user=user) | Q(ticket__end_user=user)
        flt |= Q(ticket__assigned_to=user)
        flt |= Q(user=user) | Q(private=False)
        return flt

    # @classmethod
    # def add_comments_filter(cls, qs, user):
    #     # note that this requires the related_query_name of the GenericRelation
    #     if user.is_anonymous:
    #         qs = qs.filter(ticket__private=False, ticket__site__private=False).distinct()
    #     elif not user.user_type.has_required_roles([TicketsStaff]):
    #         qs = qs.filter(
    #             Q(ticket__site__group__members__user__id=user.pk) |
    #             Q(ticket__user=user) | Q(ticket__end_user=user)|
    #             Q(ticket__assigned_to=user)|
    #             Q(ticket__private=False, ticket__site__private=False)).distinct()
    #         # print(20191125, qs.query)
    #     return qs

    def get_rfc_description(self, ar):
        html = ''
        _ = gettext
        if self.description:
            # html += tostring(E.b(_("Description")))
            html += ar.parse_memo(self.description)
        if self.upgrade_notes:
            html += tostring(E.b(_("Resolution"))) + ": "
            html += ar.parse_memo(self.upgrade_notes)
        if self.duplicate_of_id:
            html += tostring(_("Duplicate of")) + " "
            html += tostring(self.duplicate_of.obj2href(ar))
        return html

    def full_clean(self):
        if self.id and self.duplicate_of_id == self.id:
            self.duplicate_of = None
        # print "20150523b on_create", self.reporter
        if not self.site_id:
            person = self.end_user or self.user.get_person()
            qs = rt.models.tickets.Site.objects.filter(contact_person=person)
            qs = qs.filter(state=SiteStates.active)
            qs = qs.filter(
                Q(end_date__isnull=True) | Q(end_date__lte=dd.today()))
            qs = qs.order_by('-id')
            # qs = rt.models.tickets.Subscription.objects.filter(
            #     user=user, primary=True)
            if qs.count():
                # self.site = qs[0].site
                self.site = qs.first()
        super(Ticket, self).full_clean()

    def get_change_owner(self):
        if self.site_id is not None:
            return self.site.group or self.site

    def on_worked(self, session):
        """This is automatically called when a work session has been created
        or modified.

        """
        if self.fixed_since is None and session.is_fixing and session.end_time:
            self.fixed_since = session.get_datetime('end')

        self.touch()
        self.full_clean()
        self.save()

    def get_comment_group(self):
        return self.site

    def on_commented(self, comment, ar, cw):
        """This is automatically called when a comment has been created"""
        self.last_commenter = comment.user
        self.touch()
        self.save()

    # def get_project_for_vote(self, vote):
    #     if self.project:
    #         return self.project
    #     qs = rt.models.tickets.Competence.objects.filter(user=vote.user)
    #     qs = qs.order_by('priority')
    #     if qs.count() > 0:
    #         return qs[0].project
    #     return rt.models.tickets.Project.objects.all()[0]

    def obj2href(self, ar, **kwargs):
        kwargs.update(title=self.summary)
        return ar.obj2html(self, "#{}".format(self.id), **kwargs)

    def disabled_fields(self, ar):
        rv = super(Ticket, self).disabled_fields(ar)
        # if self.project and not self.project.private:
        #     rv.add('private')
        if not ar.get_user().user_type.has_required_roles([Triager]):
            rv.add('user')
            # rv.add('fixed_since')
            # rv.add('fixed_date')
            # rv.add('fixed_time')
        return rv

    # def get_choices_text(self, request, actor, field):
    #     return "{0} ({1})".format(self, self.summary)

    def __str__(self):
        # if self.nickname:
        #     return "#{0} ({1})".format(self.id, self.nickname)
        if False and self.state.button_text:
            return "#{0} ({1} {2})".format(self.id, self.state.button_text,
                                           self.summary)
        return "#{0} ({1} {2})".format(self.id, self.state.button_text,
                                       self.summary)

    @dd.chooser()
    def reported_for_choices(cls, site):
        if not site:
            return []
        # return site.milestones_by_site.filter(reached__isnull=False)
        return site.milestones_by_site.all()

    @dd.chooser()
    def fixed_for_choices(cls, site):
        if not site:
            return []
        return site.milestones_by_site.all()

    # @profile
    def get_overview_elems(self, ar):
        """Overrides :meth:`lino.core.model.Model.get_overview_elems`.
        """
        elems = [ar.obj2html(self)]  # show full summary
        # elems += [' ({})'.format(self.state.button_text)]
        # elems += [' ', self.state.button_text, ' ']
        if self.user and self.user != ar.get_user():
            elems += [' ', _(" by "), self.user.obj2href(ar)]
        if self.end_user_id:
            elems += [' ', _("for"), ' ', self.end_user.obj2href(ar)]

        if dd.is_installed('votes'):
            qs = rt.models.votes.Vote.objects.filter(votable=self,
                                                     state=VoteStates.assigned)
            if qs.count() > 0:
                elems += [', ', _("assigned to"), ' ']
                elems += join_elems([vote.user.obj2href(ar) for vote in qs],
                                    sep=', ')
        elif getattr(self, "assigned_to", None):
            elems += [
                ", ",
                _("assigned to"), " ",
                self.assigned_to.obj2href(ar)
            ]

        return E.p(*forcetext(elems))
        # return E.p(*join_elems(elems, sep=', '))

        # if ar.actor.model is self.__class__:
        #     elems += [E.br(), _("{} state:").format(
        #         self._meta.verbose_name), ' ']
        #     elems += self.get_workflow_buttons(ar)
        # else:
        #     elems += [' (', str(self.state.button_text), ')']
        # return elems

    # def get_change_body(self, ar, cw):
    #     return tostring(E.p(
    #         _("{user} worked on [ticket {t}]").format(
    #             user=ar.get_user(), t=self.id)))

    def get_vote_raters(self):
        """"Yield the
        :meth:`lino_xl.lib.votes.mixins.Votable.get_vote_raters` for
        this ticket.  This is the author and (if set) the
        :attr:`end_user`.

        """
        if self.user:
            yield self.user
        if issubclass(settings.SITE.user_model,
                      dd.resolve_model(end_user_model)):
            if self.end_user:
                u = self.end_user.get_as_user()
                if u is not None:
                    yield u

    def is_workable_for(self, user):
        if self.standby or self.closed:
            return False
        if not self.state.active and not user.user_type.has_required_roles(
            [Triager]):
            return False
        return True

    @classmethod
    def quick_search_filter(cls, search_text, prefix=''):
        """
        To skip mixins.Referrable quick_search_filter
        """
        return super(mixins.Referrable,
                     cls).quick_search_filter(search_text, prefix)
Beispiel #12
0
 class Meta:
     app_label = 'tickets'
     verbose_name = pgettext("Ticketing", "Site")
     verbose_name_plural = pgettext("Ticketing", "Sites")
     abstract = dd.is_abstract_model(__name__, 'Site')
Beispiel #13
0
    #     self.required_roles |= set(args)


class MessageTypes(dd.ChoiceList):
    verbose_name = _("Message Type")
    verbose_name_plural = _("Message Types")
    item_class = MessageType

    # @classmethod
    # def register_type(cls, name, *args, **kwargs):
    #     cls.add_item_lazy(name, *args, **kwargs)


add = MessageTypes.add_item
add('system', _("System event"))
add('change', pgettext("message type", "Change"))
# add('300', _("Action"), 'action')
# add('300', _("Warning"), 'warning')
# add('400', _("Note"), 'note')
# add('900', _("Notification"), 'notification')


class MailModes(dd.ChoiceList):
    verbose_name = _("Notification mode")
    verbose_name_plural = _("Notification modes")


add = MailModes.add_item
add('silent', _("Silent"), 'silent')
add('never', _("No mails"), 'never')
# add('immediately', _("Immediately"), 'immediately')  # obsolete
Beispiel #14
0
from lino.utils.xmlgen.html import E
from lino.utils import join_elems

class MessageTypes(dd.ChoiceList):
    """
    The list of possible choices for the `message_type` field
    of a :class:`Message`.
    """
    verbose_name = _("Message Type")
    verbose_name_plural = _("Message Types")


add = MessageTypes.add_item
add('100', _("System event"), 'system')
add('200', pgettext("message type", "Change"), 'change')
add('300', _("Action"), 'action')
# add('300', _("Warning"), 'warning')
# add('400', _("Note"), 'note')
# add('900', _("Notification"), 'notification')



class MailModes(dd.ChoiceList):
    """How the system should send email notifications to a user.

    """
    verbose_name = _("Email notification mode")
    verbose_name_plural = _("Email notification modes")
    
add = MailModes.add_item
Beispiel #15
0
 class Meta:
     app_label = 'tickets'
     verbose_name = pgettext("Ticketing", "Site")
     verbose_name_plural = pgettext("Ticketing", "Sites")
Beispiel #16
0
class MarkTicketStarted(TicketAction):
    """Mark this ticket as started.
    """
    action_name = 'mark_started'
    label = pgettext("verb", "Start")
    required_states = 'new talk opened'