From 0a1e73918d1c365065f76291e741bbd70fd3c407 Mon Sep 17 00:00:00 2001 From: Benbb96 Date: Sat, 19 Oct 2024 00:31:00 +0200 Subject: [PATCH 1/2] Refactor custom commands with modern syntax --- docs/configuration.rst | 2 +- .../commands/create_escalation_exclusions.py | 189 ++++++---------- .../commands/create_queue_permissions.py | 43 ++-- .../management/commands/escalate_tickets.py | 207 ++++++------------ helpdesk/management/commands/get_email.py | 7 +- 5 files changed, 152 insertions(+), 296 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 05d56a7e..3491c27a 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -38,7 +38,7 @@ Before django-helpdesk will be much use, you need to do some basic configuration 5. If you wish to exclude some days (eg, weekends) from escalation calculations, enter the dates manually via the Admin, or setup a cronjob to run a management command on a regular basis:: - 0 0 * * 0 /path/to/helpdesksite/manage.py create_escalation_exclusions --days saturday,sunday --escalate-verbosely + 0 0 * * 0 /path/to/helpdesksite/manage.py create_escalation_exclusions --days saturday sunday This will, on a weekly basis, create exclusions for the coming weekend. diff --git a/helpdesk/management/commands/create_escalation_exclusions.py b/helpdesk/management/commands/create_escalation_exclusions.py index 6d3e249f..44a04765 100644 --- a/helpdesk/management/commands/create_escalation_exclusions.py +++ b/helpdesk/management/commands/create_escalation_exclusions.py @@ -11,63 +11,7 @@ scripts/create_escalation_exclusion.py - Easy way to routinely add particular from datetime import date, timedelta from django.core.management.base import BaseCommand, CommandError -import getopt from helpdesk.models import EscalationExclusion, Queue -from optparse import make_option -import sys - - -class Command(BaseCommand): - - def __init__(self): - BaseCommand.__init__(self) - - self.option_list += ( - make_option( - '--days', '-d', - help='Days of week (monday, tuesday, etc)'), - make_option( - '--occurrences', '-o', - type='int', - default=1, - help='Occurrences: How many weeks ahead to exclude this day'), - make_option( - '--queues', '-q', - help='Queues to include (default: all). Use queue slugs'), - make_option( - '--escalate-verbosely', '-x', - action='store_true', - default=False, - dest='escalate-verbosely', - help='Display a list of dates excluded'), - ) - - def handle(self, *args, **options): - days = options['days'] - # optparse should already handle the `or 1` - occurrences = options['occurrences'] or 1 - verbose = False - queue_slugs = options['queues'] - queues = [] - - if options['escalate-verbosely']: - verbose = True - - if not (days and occurrences): - raise CommandError('One or more occurrences must be specified.') - - if queue_slugs is not None: - queue_set = queue_slugs.split(',') - for queue in queue_set: - try: - q = Queue.objects.get(slug__exact=queue) - except Queue.DoesNotExist: - raise CommandError("Queue %s does not exist." % queue) - queues.append(q) - - create_exclusions(days=days, occurrences=occurrences, - verbose=verbose, queues=queues) - day_names = { 'monday': 0, @@ -80,79 +24,70 @@ day_names = { } -def create_exclusions(days, occurrences, verbose, queues): - days = days.split(',') - for day in days: - day_name = day - day = day_names[day] - workdate = date.today() - i = 0 - while i < occurrences: - if day == workdate.weekday(): - if EscalationExclusion.objects.filter(date=workdate).count() == 0: - esc = EscalationExclusion( - name='Auto Exclusion for %s' % day_name, date=workdate) - esc.save() +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument( + '-d', + '--days', + nargs='*', + choices=list(day_names.keys()), + required=True, + help='Days of week (monday, tuesday, etc)' + ) + parser.add_argument( + '-o', + '--occurrences', + default=1, + type=int, + help='Occurrences: How many weeks ahead to exclude this day' + ) + parser.add_argument( + '-q', + '--queues', + nargs='*', + choices=list(Queue.objects.values_list('slug', flat=True)), + help='Queues to include (default: all). Use queue slugs' + ) + parser.add_argument( + '-x', + '--exclude-verbosely', + action='store_true', + default=False, + help='Display a list of dates excluded' + ) - if verbose: - print("Created exclusion for %s %s" % - (day_name, workdate)) + def handle(self, *args, **options): + days = options['days'] + occurrences = options['occurrences'] + verbose = options['exclude_verbosely'] + queue_slugs = options['queues'] + + if not (days and occurrences): + raise CommandError('One or more occurrences must be specified.') + + queues = [] + if queue_slugs is not None: + queues = Queue.objects.filter(slug__in=queue_slugs) + + for day_name in days: + day = day_names[day_name] + workdate = date.today() + i = 0 + while i < occurrences: + if day == workdate.weekday(): + if EscalationExclusion.objects.filter(date=workdate).count() == 0: + esc = EscalationExclusion.objects.create( + name=f'Auto Exclusion for {day_name}', + date=workdate + ) - for q in queues: - esc.queues.add(q) if verbose: - print(" - for queue %s" % q) + self.stdout.write(f"Created exclusion for {day_name} {workdate}") - i += 1 - workdate += timedelta(days=1) + for q in queues: + esc.queues.add(q) + if verbose: + self.stdout.write(f" - for queue {q}") - -def usage(): - print("Options:") - print(" --days, -d: Days of week (monday, tuesday, etc)") - print(" --occurrences, -o: Occurrences: How many weeks ahead to exclude this day") - print(" --queues, -q: Queues to include (default: all). Use queue slugs") - print(" --verbose, -v: Display a list of dates excluded") - - -if __name__ == '__main__': - # This script can be run from the command-line or via Django's manage.py. - try: - opts, args = getopt.getopt(sys.argv[1:], 'd:o:q:v', [ - 'days=', 'occurrences=', 'verbose', 'queues=']) - except getopt.GetoptError: - usage() - sys.exit(2) - - days = None - occurrences = 1 - verbose = False - queue_slugs = None - queues = [] - - for o, a in opts: - if o in ('-x', '--escalate-verbosely'): - verbose = True - if o in ('-d', '--days'): - days = a - if o in ('-q', '--queues'): - queue_slugs = a - if o in ('-o', '--occurrences'): - occurrences = int(a) or 1 - - if not (days and occurrences): - usage() - sys.exit(2) - - if queue_slugs is not None: - queue_set = queue_slugs.split(',') - for queue in queue_set: - try: - q = Queue.objects.get(slug__exact=queue) - except Queue.DoesNotExist: - print("Queue %s does not exist." % queue) - sys.exit(2) - queues.append(q) - - create_exclusions(days=days, occurrences=occurrences, - verbose=verbose, queues=queues) + i += 1 + workdate += timedelta(days=1) diff --git a/helpdesk/management/commands/create_queue_permissions.py b/helpdesk/management/commands/create_queue_permissions.py index 1da97851..fc0790cd 100644 --- a/helpdesk/management/commands/create_queue_permissions.py +++ b/helpdesk/management/commands/create_queue_permissions.py @@ -15,55 +15,54 @@ scripts/create_queue_permissions.py - from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from django.db.utils import IntegrityError from django.utils.translation import gettext_lazy as _ from helpdesk.models import Queue -from optparse import make_option class Command(BaseCommand): - def __init__(self): - BaseCommand.__init__(self) - - self.option_list += ( - make_option( - '--queues', '-q', - help='Queues to include (default: all). Use queue slugs'), + def add_arguments(self, parser): + parser.add_argument( + '-q', + '--queues', + nargs='*', + choices=list(Queue.objects.values_list('slug', flat=True)), + help='Queues to include (default: all). Use queue slugs' + ) + parser.add_argument( + '-x', + '--escalate-verbosely', + action='store_true', + default=False, + help='Display a list of dates excluded' ) def handle(self, *args, **options): queue_slugs = options['queues'] - queues = [] if queue_slugs is not None: - queue_set = queue_slugs.split(',') - for queue in queue_set: - try: - q = Queue.objects.get(slug__exact=queue) - except Queue.DoesNotExist: - raise CommandError("Queue %s does not exist." % queue) - queues.append(q) + queues = Queue.objects.filter(slug__in=queue_slugs) else: - queues = list(Queue.objects.all()) + queues = Queue.objects.all() # Create permissions for the queues, which may be all or not for q in queues: - self.stdout.write("Preparing Queue %s [%s]" % (q.title, q.slug)) + self.stdout.write(f"Preparing Queue {q} [{q.slug}]") if q.permission_name: self.stdout.write( - " .. already has `permission_name=%s`" % q.permission_name) + f" .. already has `permission_name={q.permission_name}`") basename = q.permission_name[9:] else: basename = q.generate_permission_name() self.stdout.write( - " .. generated `permission_name=%s`" % q.permission_name) + f" .. generated `permission_name={q.permission_name}`") q.save() self.stdout.write( - " .. checking permission codename `%s`" % basename) + f" .. checking permission codename `{basename}`") try: Permission.objects.create( diff --git a/helpdesk/management/commands/escalate_tickets.py b/helpdesk/management/commands/escalate_tickets.py index 82cb1948..ba7f15a9 100644 --- a/helpdesk/management/commands/escalate_tickets.py +++ b/helpdesk/management/commands/escalate_tickets.py @@ -8,168 +8,93 @@ scripts/escalate_tickets.py - Easy way to escalate tickets based on their age, designed to be run from Cron or similar. """ - from datetime import date, timedelta -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from django.db.models import Q from django.utils import timezone from django.utils.translation import gettext as _ -import getopt from helpdesk.lib import safe_template_context -from helpdesk.models import EscalationExclusion, FollowUp, Queue, Ticket, TicketChange -from optparse import make_option -import sys +from helpdesk.models import EscalationExclusion, Queue, Ticket class Command(BaseCommand): - - def __init__(self): - BaseCommand.__init__(self) - - self.option_list = ( - make_option( - '--queues', - help='Queues to include (default: all). Use queue slugs'), - make_option( - '--verboseescalation', - action='store_true', - default=False, - help='Display a list of dates excluded'), + def add_arguments(self, parser): + parser.add_argument( + '-q', + '--queues', + nargs='*', + choices=list(Queue.objects.values_list('slug', flat=True)), + help='Queues to include (default: all). Use queue slugs' + ) + parser.add_argument( + '-x', + '--escalate-verbosely', + action='store_true', + default=False, + help='Display escalated tickets' ) def handle(self, *args, **options): - verbose = False - queue_slugs = None - queues = [] - - if 'verboseescalation' in options: - verbose = True - if 'queues' in options: - queue_slugs = options['queues'] + verbose = options['escalate_verbosely'] + queue_slugs = options['queues'] + # Only include queues with escalation configured + queues = Queue.objects.filter(escalate_days__isnull=False).exclude(escalate_days=0) if queue_slugs is not None: - queue_set = queue_slugs.split(',') - for queue in queue_set: - try: - Queue.objects.get(slug__exact=queue) - except Queue.DoesNotExist: - raise CommandError("Queue %s does not exist." % queue) - queues.append(queue) - - escalate_tickets(queues=queues, verbose=verbose) - - -def escalate_tickets(queues, verbose): - """ Only include queues with escalation configured """ - queryset = Queue.objects.filter( - escalate_days__isnull=False).exclude(escalate_days=0) - if queues: - queryset = queryset.filter(slug__in=queues) - - for q in queryset: - last = date.today() - timedelta(days=q.escalate_days) - today = date.today() - workdate = last - - days = 0 - - while workdate < today: - if EscalationExclusion.objects.filter(date=workdate).count() == 0: - days += 1 - workdate = workdate + timedelta(days=1) - - req_last_escl_date = date.today() - timedelta(days=days) + queues = queues.filter(slug__in=queue_slugs) if verbose: - print("Processing: %s" % q) + self.stdout.write(f"Processing: {queues}") - Q_OPEN_STATUSES = Q() - for open_status in Ticket.OPEN_STATUSES: - Q_OPEN_STATUSES |= Q(status=open_status) - - for t in q.ticket_set.filter( - Q_OPEN_STATUSES - ).exclude( - priority=1 - ).filter( - Q(on_hold__isnull=True) | - Q(on_hold=False) - ).filter( - Q(last_escalation__lte=req_last_escl_date) | + for queue in queues: + last = date.today() - timedelta(days=queue.escalate_days) + today = date.today() + workdate = last + + days = 0 + + while workdate < today: + if not EscalationExclusion.objects.filter(date=workdate).exists(): + days += 1 + workdate = workdate + timedelta(days=1) + + req_last_escl_date = timezone.now() - timedelta(days=days) + + for ticket in queue.ticket_set.filter( + status__in=Ticket.OPEN_STATUSES + ).exclude( + priority=1 + ).filter( + Q(on_hold__isnull=True) | Q(on_hold=False) + ).filter( + Q(last_escalation__lte=req_last_escl_date) | Q(last_escalation__isnull=True, created__lte=req_last_escl_date) - ): + ): - t.last_escalation = timezone.now() - t.priority -= 1 - t.save() + ticket.last_escalation = timezone.now() + ticket.priority -= 1 + ticket.save() - context = safe_template_context(t) + context = safe_template_context(ticket) - t.send( - {'submitter': ('escalated_submitter', context), - 'ticket_cc': ('escalated_cc', context), - 'assigned_to': ('escalated_owner', context)}, - fail_silently=True, - ) - - if verbose: - print(" - Esclating %s from %s>%s" % ( - t.ticket, - t.priority + 1, - t.priority - ) + ticket.send( + {'submitter': ('escalated_submitter', context), + 'ticket_cc': ('escalated_cc', context), + 'assigned_to': ('escalated_owner', context)}, + fail_silently=True, ) - f = FollowUp( - ticket=t, - title='Ticket Escalated', - date=timezone.now(), - public=True, - comment=_('Ticket escalated after %s days' % q.escalate_days), - ) - f.save() + if verbose: + self.stdout.write(f" - Esclating {ticket.ticket} from {ticket.priority + 1}>{ticket.priority}") - tc = TicketChange( - followup=f, - field=_('Priority'), - old_value=t.priority + 1, - new_value=t.priority, - ) - tc.save() + followup = ticket.followup_set.create( + title=_('Ticket Escalated'), + public=True, + comment=_('Ticket escalated after %(nb)s days') % {'nb': queue.escalate_days}, + ) - -def usage(): - print("Options:") - print(" --queues: Queues to include (default: all). Use queue slugs") - print(" --verboseescalation: Display a list of dates excluded") - - -if __name__ == '__main__': - try: - opts, args = getopt.getopt( - sys.argv[1:], ['queues=', 'verboseescalation']) - except getopt.GetoptError: - usage() - sys.exit(2) - - verbose = False - queue_slugs = None - queues = [] - - for o, a in opts: - if o == '--verboseescalation': - verbose = True - if o == '--queues': - queue_slugs = a - - if queue_slugs is not None: - queue_set = queue_slugs.split(',') - for queue in queue_set: - try: - q = Queue.objects.get(slug__exact=queue) - except Queue.DoesNotExist: - print("Queue %s does not exist." % queue) - sys.exit(2) - queues.append(queue) - - escalate_tickets(queues=queues, verbose=verbose) + followup.ticketchange_set.create( + field=_('Priority'), + old_value=ticket.priority + 1, + new_value=ticket.priority, + ) diff --git a/helpdesk/management/commands/get_email.py b/helpdesk/management/commands/get_email.py index c16b5fb4..a0cb3726 100755 --- a/helpdesk/management/commands/get_email.py +++ b/helpdesk/management/commands/get_email.py @@ -16,9 +16,6 @@ from helpdesk.email import process_email class Command(BaseCommand): - def __init__(self): - BaseCommand.__init__(self) - help = 'Process django-helpdesk queues and process e-mails via POP3/IMAP or ' \ 'from a local mailbox directory as required, feeding them into the helpdesk.' @@ -39,8 +36,8 @@ class Command(BaseCommand): ) def handle(self, *args, **options): - quiet = options.get('quiet', False) - debug_to_stdout = options.get('debug_to_stdout', False) + quiet = options.get('quiet') + debug_to_stdout = options.get('debug_to_stdout') process_email(quiet=quiet, debug_to_stdout=debug_to_stdout) From 2c24fa9cae2bc517ca2e19b2459cd83d8001cedf Mon Sep 17 00:00:00 2001 From: Benbb96 Date: Sat, 19 Oct 2024 16:50:17 +0200 Subject: [PATCH 2/2] Improve help texts --- helpdesk/management/commands/create_escalation_exclusions.py | 4 ++-- helpdesk/management/commands/create_queue_permissions.py | 2 +- helpdesk/management/commands/escalate_tickets.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helpdesk/management/commands/create_escalation_exclusions.py b/helpdesk/management/commands/create_escalation_exclusions.py index 44a04765..0d217ccd 100644 --- a/helpdesk/management/commands/create_escalation_exclusions.py +++ b/helpdesk/management/commands/create_escalation_exclusions.py @@ -32,7 +32,7 @@ class Command(BaseCommand): nargs='*', choices=list(day_names.keys()), required=True, - help='Days of week (monday, tuesday, etc)' + help='Days of week (monday, tuesday, etc). Enter the days as space separated list.' ) parser.add_argument( '-o', @@ -46,7 +46,7 @@ class Command(BaseCommand): '--queues', nargs='*', choices=list(Queue.objects.values_list('slug', flat=True)), - help='Queues to include (default: all). Use queue slugs' + help='Queues to include (default: all). Enter the queues slug as space separated list.' ) parser.add_argument( '-x', diff --git a/helpdesk/management/commands/create_queue_permissions.py b/helpdesk/management/commands/create_queue_permissions.py index fc0790cd..b4d2bb0b 100644 --- a/helpdesk/management/commands/create_queue_permissions.py +++ b/helpdesk/management/commands/create_queue_permissions.py @@ -29,7 +29,7 @@ class Command(BaseCommand): '--queues', nargs='*', choices=list(Queue.objects.values_list('slug', flat=True)), - help='Queues to include (default: all). Use queue slugs' + help='Queues to include (default: all). Enter the queues slug as space separated list.' ) parser.add_argument( '-x', diff --git a/helpdesk/management/commands/escalate_tickets.py b/helpdesk/management/commands/escalate_tickets.py index ba7f15a9..bfeba548 100644 --- a/helpdesk/management/commands/escalate_tickets.py +++ b/helpdesk/management/commands/escalate_tickets.py @@ -24,7 +24,7 @@ class Command(BaseCommand): '--queues', nargs='*', choices=list(Queue.objects.values_list('slug', flat=True)), - help='Queues to include (default: all). Use queue slugs' + help='Queues to include (default: all). Enter the queues slug as space separated list.' ) parser.add_argument( '-x',