class _EntityEmailsSendType(JobType): id = JobType.generate_id('emails', 'entity_emails_send') verbose_name = _('Send entity emails') periodic = JobType.PSEUDO_PERIODIC def _execute(self, job): Status = EntityEmail.Status for email in EntityEmail.objects.exclude(is_deleted=True).filter( # status__in=[MAIL_STATUS_NOTSENT, MAIL_STATUS_SENDINGERROR], status__in=[Status.NOT_SENT, Status.SENDING_ERROR], ): email.send() # We have to implement it because it is a PSEUDO_PERIODIC JobType def next_wakeup(self, job, now_value): Status = EntityEmail.Status filter_mail = EntityEmail.objects.exclude(is_deleted=True).filter # if filter_mail(status=MAIL_STATUS_NOTSENT).exists(): if filter_mail(status=Status.NOT_SENT).exists(): return now_value # if filter_mail(status=MAIL_STATUS_SENDINGERROR).exists(): if filter_mail(status=Status.SENDING_ERROR).exists(): return now_value + timedelta(minutes=ENTITY_EMAILS_RETRY) return None
class _GenerateDocsType(JobType): id = JobType.generate_id('recurrents', 'generate_docs') verbose_name = _('Generate recurrent documents') periodic = JobType.PSEUDO_PERIODIC # TODO: we could add a field RecurrentGenerator.next_generation # => queries would be more efficient def _get_generators(self, now_value): return get_rgenerator_model().objects \ .filter(is_working=True) \ .filter(Q(last_generation__isnull=True, first_generation__lte=now_value, ) | Q(last_generation__isnull=False) ) def _execute(self, job): # TODO: test is_working VS delete it (see next_wakeup() && job refreshing too) for generator in self._get_generators(now()): last = generator.last_generation next_generation = (generator.first_generation if last is None else last + generator.periodicity.as_timedelta()) if next_generation <= now(): with atomic(): template = generator.template.get_real_entity() template.create_entity() generator.last_generation = next_generation generator.save() # TODO: with docs generate the last time ?? (but stats will be cleaned at # next run, even if nothing is generated...) # def get_stats(self, job): # count = JobResult.objects.filter(job=job, raw_errors__isnull=True).count() # # return [ungettext('%s entity has been successfully modified.', # '%s entities have been successfully modified.', # count # ) % count, # ] # We have to implement it because it is a PSEUDO_PERIODIC JobType def next_wakeup(self, job, now_value): wakeup = None for generator in self._get_generators(now_value): last = generator.last_generation if last is None: # We are sure that first_generation < now_value wakeup = now_value break recurrent_date = last + generator.periodicity.as_timedelta() wakeup = recurrent_date if not wakeup else min( wakeup, recurrent_date) return wakeup
def _create_invalid_job(self, user=None, status=Job.STATUS_WAIT): return Job.objects.create( user=user or self.user, type_id=JobType.generate_id('creme_core', 'invalid'), language='en', status=status, raw_data='[]', )
class _UserMessagesSendType(JobType): id = JobType.generate_id('assistants', 'usermessages_send') verbose_name = _(u'Send usermessages emails') periodic = JobType.PSEUDO_PERIODIC def _execute(self, job): UserMessage.send_mails(job) # We have to implement it because it is a PSEUDO_PERIODIC JobType def next_wakeup(self, job, now_value): if UserMessage.objects.filter(email_sent=False).exists(): return now_value
class _CampaignEmailsSendType(JobType): id = JobType.generate_id('emails', 'campaign_emails_send') verbose_name = _('Send emails from campaigns') periodic = JobType.PSEUDO_PERIODIC def _execute(self, job): for sending in EmailSending.objects.exclude( campaign__is_deleted=True, ).exclude( # state=SENDING_STATE_DONE, state=EmailSending.State.DONE, ).filter( # Q(type=SENDING_TYPE_IMMEDIATE) | Q(sending_date__lte=now()) Q(type=EmailSending.Type.IMMEDIATE) | Q(sending_date__lte=now())): # sending.state = SENDING_STATE_INPROGRESS sending.state = EmailSending.State.IN_PROGRESS sending.save() # if getattr(settings, 'REMOTE_STATS', False): # from creme.emails.utils.remoteutils import populate_minicreme #broken # populate_minicreme(sending) status = sending.send_mails() # TODO: move in send_mails() ??? # sending.state = status or SENDING_STATE_DONE sending.state = status or EmailSending.State.DONE sending.save() # We have to implement it because it is a PSEUDO_PERIODIC JobType def next_wakeup(self, job, now_value): qs = EmailSending.objects.exclude(campaign__is_deleted=True, ).exclude( state=EmailSending.State.DONE) # state=SENDING_STATE_DONE # if qs.filter(type=SENDING_TYPE_IMMEDIATE).exists(): if qs.filter(type=EmailSending.Type.IMMEDIATE).exists(): return now_value # dsending = qs.filter(type=SENDING_TYPE_DEFERRED).order_by('sending_date').first() dsending = qs.filter( type=EmailSending.Type.DEFERRED).order_by('sending_date').first() return dsending.sending_date if dsending is not None else None
class _CruditySynchronizeType(JobType): id = JobType.generate_id('crudity', 'synchronization') verbose_name = _('Synchronize externals data sent to Creme') periodic = JobType.PERIODIC def _execute(self, job): try: user = CremeUser.objects.get(pk=job.data['user']) except CremeUser.DoesNotExist: JobResult.objects.create( job=job, messages=[ gettext("The configured default user is invalid. " "Edit the job's configuration to fix it." ), ], ) user = CremeUser.objects.get_admin() from . import registry count = len(registry.crudity_registry.fetch(user)) JobResult.objects.create( job=job, messages=[ ngettext('There is {count} change', 'There are {count} changes', count ).format(count=count), ], ) def get_config_form_class(self, job): from .forms import CruditySynchronizeJobForm return CruditySynchronizeJobForm @property def results_bricks(self): from creme.creme_core.bricks import JobResultsBrick return [JobResultsBrick()]
class _ActiveSyncType(JobType): id = JobType.generate_id('activesync', 'synchronise') verbose_name = _(u'ActiveSync synchronisation') periodic = JobType.PERIODIC def _execute(self, job): for user in get_user_model().objects.all(): try: Synchronization(user).synchronize() except CremeActiveSyncError as e: JobResult.objects.create( job=job, messages=[ ugettext(u'An error occurred for the user «%s»') % user, ugettext(u'Original error: %s') % e, ], ) def get_description(self, job): return [ugettext(u'Synchronise data with the ActiveSync server')]
class _ComApproachesEmailsSendType(JobType): id = JobType.generate_id('commercial', 'com_approaches_emails_send') verbose_name = _('Send emails for commercials approaches') # It would be too difficult/inefficient to compute the next wake up, # so it is not PSEUDO_PERIODIC. periodic = JobType.PERIODIC # TODO: add a config form which stores the rules in job.data list_target_orga = [(REL_SUB_CUSTOMER_SUPPLIER, 30)] def _execute(self, job): from creme import persons from creme.opportunities import get_opportunity_model from creme.opportunities.constants import REL_SUB_TARGETS from .models import CommercialApproach Organisation = persons.get_organisation_model() Contact = persons.get_contact_model() Opportunity = get_opportunity_model() emails = [] get_ct = ContentType.objects.get_for_model ct_orga = get_ct(Organisation) ct_contact = get_ct(Contact) ct_opp = get_ct(Opportunity) now_value = now() managed_orga_ids = [ *Organisation.objects.filter(is_managed=True).values_list( 'id', flat=True), ] opp_filter = Opportunity.objects.filter EMAIL_SENDER = settings.EMAIL_SENDER for rtype, delay in self.list_target_orga: com_apps_filter = CommercialApproach.objects.filter( creation_date__gt=now_value - timedelta(days=delay), ).filter # TODO: are 'values_list' real optimizations here ?? # ==> remove them when CommercialApproach use real ForeignKey for orga in Organisation.objects.filter( is_managed=False, relations__type=rtype, relations__object_entity__in=managed_orga_ids, ): if com_apps_filter(entity_content_type=ct_orga, entity_id=orga.id).exists(): continue if com_apps_filter( entity_content_type=ct_contact, entity_id__in=orga.get_managers().values_list( 'id', flat=True), ).exists(): continue if com_apps_filter( entity_content_type=ct_contact, entity_id__in=orga.get_employees().values_list( 'id', flat=True), ).exists(): continue if com_apps_filter( entity_content_type=ct_opp, entity_id__in=opp_filter( relations__type=REL_SUB_TARGETS, relations__object_entity=orga, ).values_list('id', flat=True), ).exists(): continue emails.append( EmailMessage( gettext( '[CremeCRM] The organisation «{}» seems neglected' ).format(orga), gettext( "It seems you haven't created a commercial approach for " "the organisation «{orga}» since {delay} days."). format( orga=orga, delay=delay, ), EMAIL_SENDER, [orga.user.email], )) # TODO: factorise jobs which send emails if emails: try: with get_connection() as connection: connection.send_messages(emails) except Exception as e: JobResult.objects.create( job=job, messages=[ gettext('An error has occurred while sending emails'), gettext('Original error: {}').format(e), ], ) def get_description(self, job): return [ gettext( "For each customer organisation, an email is sent to its owner " "(ie: a Creme user), if there is no commercial approach since " "{} days linked to: the organisation, one of its managers/employees, " "or an Opportunity which targets this organisation.").format( self.list_target_orga[0][1]), gettext( "Hint: to create commercial approaches, activate the field " "«Is a commercial approach?» in the configuration of Activities' forms ; " "so when you create an Activity, if you check the box, some approaches " "will be created for participants, subjects & linked entities." ), gettext( "Hint: to see commercial approaches, activate the related block " "on detail-views."), ]
class TestJobType1(JobType): id = JobType.generate_id('creme_core', 'test1')