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()
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
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()
class MarkTicketWorking(TicketAction): """Mark this ticket as working. """ action_name = 'mark_working' label = pgettext("verb", "Start") required_states = 'new talk opened' needs_site = True
# """ # 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
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")
# 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))
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)
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
class MarkTicketOpened(TicketAction): """Mark this ticket as open. """ action_name = 'mark_opened' label = pgettext("verb", "Open") required_states = 'talk new closed'
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)
class Meta: app_label = 'tickets' verbose_name = pgettext("Ticketing", "Site") verbose_name_plural = pgettext("Ticketing", "Sites") abstract = dd.is_abstract_model(__name__, 'Site')
# 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
class Meta: app_label = 'tickets' verbose_name = pgettext("Ticketing", "Site") verbose_name_plural = pgettext("Ticketing", "Sites")
class MarkTicketStarted(TicketAction): """Mark this ticket as started. """ action_name = 'mark_started' label = pgettext("verb", "Start") required_states = 'new talk opened'