|
1 | 1 | import json
|
2 |
| -import uuid |
3 | 2 |
|
4 |
| -import django_rq |
5 | 3 | from django.conf import settings
|
6 | 4 | from django.contrib import admin
|
7 | 5 | from django.contrib.auth.models import User
|
8 | 6 | from django.contrib.contenttypes.fields import GenericForeignKey
|
9 | 7 | from django.contrib.contenttypes.models import ContentType
|
10 | 8 | from django.core.cache import cache
|
11 |
| -from django.core.validators import MinValueValidator, ValidationError |
| 9 | +from django.core.validators import ValidationError |
12 | 10 | from django.db import models
|
13 | 11 | from django.http import HttpResponse, QueryDict
|
14 | 12 | from django.urls import reverse
|
15 |
| -from django.urls.exceptions import NoReverseMatch |
16 | 13 | from django.utils import timezone
|
17 | 14 | from django.utils.formats import date_format
|
18 | 15 | from django.utils.translation import gettext as _
|
|
22 | 19 | from extras.conditions import ConditionSet
|
23 | 20 | from extras.constants import *
|
24 | 21 | from extras.utils import FeatureQuery, image_upload
|
25 |
| -from netbox.config import get_config |
26 |
| -from netbox.constants import RQ_QUEUE_DEFAULT |
27 | 22 | from netbox.models import ChangeLoggedModel
|
28 | 23 | from netbox.models.features import (
|
29 | 24 | CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin,
|
30 | 25 | )
|
31 | 26 | from utilities.querysets import RestrictedQuerySet
|
32 |
| -from utilities.rqworker import get_queue_for_model |
33 | 27 | from utilities.utils import render_jinja2
|
34 | 28 |
|
35 | 29 | __all__ = (
|
36 | 30 | 'ConfigRevision',
|
37 | 31 | 'CustomLink',
|
38 | 32 | 'ExportTemplate',
|
39 | 33 | 'ImageAttachment',
|
40 |
| - 'JobResult', |
41 | 34 | 'JournalEntry',
|
42 | 35 | 'SavedFilter',
|
43 | 36 | 'Webhook',
|
@@ -583,193 +576,6 @@ def get_kind_color(self):
|
583 | 576 | return JournalEntryKindChoices.colors.get(self.kind)
|
584 | 577 |
|
585 | 578 |
|
586 |
| -class JobResult(models.Model): |
587 |
| - """ |
588 |
| - This model stores the results from running a user-defined report. |
589 |
| - """ |
590 |
| - name = models.CharField( |
591 |
| - max_length=255 |
592 |
| - ) |
593 |
| - obj_type = models.ForeignKey( |
594 |
| - to=ContentType, |
595 |
| - related_name='job_results', |
596 |
| - verbose_name='Object types', |
597 |
| - limit_choices_to=FeatureQuery('jobs'), |
598 |
| - help_text=_("The object type to which this job result applies"), |
599 |
| - on_delete=models.CASCADE, |
600 |
| - ) |
601 |
| - created = models.DateTimeField( |
602 |
| - auto_now_add=True |
603 |
| - ) |
604 |
| - scheduled = models.DateTimeField( |
605 |
| - null=True, |
606 |
| - blank=True |
607 |
| - ) |
608 |
| - interval = models.PositiveIntegerField( |
609 |
| - blank=True, |
610 |
| - null=True, |
611 |
| - validators=( |
612 |
| - MinValueValidator(1), |
613 |
| - ), |
614 |
| - help_text=_("Recurrence interval (in minutes)") |
615 |
| - ) |
616 |
| - started = models.DateTimeField( |
617 |
| - null=True, |
618 |
| - blank=True |
619 |
| - ) |
620 |
| - completed = models.DateTimeField( |
621 |
| - null=True, |
622 |
| - blank=True |
623 |
| - ) |
624 |
| - user = models.ForeignKey( |
625 |
| - to=User, |
626 |
| - on_delete=models.SET_NULL, |
627 |
| - related_name='+', |
628 |
| - blank=True, |
629 |
| - null=True |
630 |
| - ) |
631 |
| - status = models.CharField( |
632 |
| - max_length=30, |
633 |
| - choices=JobResultStatusChoices, |
634 |
| - default=JobResultStatusChoices.STATUS_PENDING |
635 |
| - ) |
636 |
| - data = models.JSONField( |
637 |
| - null=True, |
638 |
| - blank=True |
639 |
| - ) |
640 |
| - job_id = models.UUIDField( |
641 |
| - unique=True |
642 |
| - ) |
643 |
| - |
644 |
| - objects = RestrictedQuerySet.as_manager() |
645 |
| - |
646 |
| - class Meta: |
647 |
| - ordering = ['-created'] |
648 |
| - |
649 |
| - def __str__(self): |
650 |
| - return str(self.job_id) |
651 |
| - |
652 |
| - def delete(self, *args, **kwargs): |
653 |
| - super().delete(*args, **kwargs) |
654 |
| - |
655 |
| - rq_queue_name = get_config().QUEUE_MAPPINGS.get(self.obj_type.model, RQ_QUEUE_DEFAULT) |
656 |
| - queue = django_rq.get_queue(rq_queue_name) |
657 |
| - job = queue.fetch_job(str(self.job_id)) |
658 |
| - |
659 |
| - if job: |
660 |
| - job.cancel() |
661 |
| - |
662 |
| - def get_absolute_url(self): |
663 |
| - try: |
664 |
| - return reverse(f'extras:{self.obj_type.model}_result', args=[self.pk]) |
665 |
| - except NoReverseMatch: |
666 |
| - return None |
667 |
| - |
668 |
| - def get_status_color(self): |
669 |
| - return JobResultStatusChoices.colors.get(self.status) |
670 |
| - |
671 |
| - @property |
672 |
| - def duration(self): |
673 |
| - if not self.completed: |
674 |
| - return None |
675 |
| - |
676 |
| - start_time = self.started or self.created |
677 |
| - |
678 |
| - if not start_time: |
679 |
| - return None |
680 |
| - |
681 |
| - duration = self.completed - start_time |
682 |
| - minutes, seconds = divmod(duration.total_seconds(), 60) |
683 |
| - |
684 |
| - return f"{int(minutes)} minutes, {seconds:.2f} seconds" |
685 |
| - |
686 |
| - def start(self): |
687 |
| - """ |
688 |
| - Record the job's start time and update its status to "running." |
689 |
| - """ |
690 |
| - if self.started is not None: |
691 |
| - return |
692 |
| - |
693 |
| - # Start the job |
694 |
| - self.started = timezone.now() |
695 |
| - self.status = JobResultStatusChoices.STATUS_RUNNING |
696 |
| - JobResult.objects.filter(pk=self.pk).update(started=self.started, status=self.status) |
697 |
| - |
698 |
| - # Handle webhooks |
699 |
| - self.trigger_webhooks(event=EVENT_JOB_START) |
700 |
| - |
701 |
| - def terminate(self, status=JobResultStatusChoices.STATUS_COMPLETED): |
702 |
| - """ |
703 |
| - Mark the job as completed, optionally specifying a particular termination status. |
704 |
| - """ |
705 |
| - valid_statuses = JobResultStatusChoices.TERMINAL_STATE_CHOICES |
706 |
| - if status not in valid_statuses: |
707 |
| - raise ValueError(f"Invalid status for job termination. Choices are: {', '.join(valid_statuses)}") |
708 |
| - |
709 |
| - # Mark the job as completed |
710 |
| - self.status = status |
711 |
| - self.completed = timezone.now() |
712 |
| - JobResult.objects.filter(pk=self.pk).update(status=self.status, completed=self.completed) |
713 |
| - |
714 |
| - # Handle webhooks |
715 |
| - self.trigger_webhooks(event=EVENT_JOB_END) |
716 |
| - |
717 |
| - @classmethod |
718 |
| - def enqueue_job(cls, func, name, obj_type, user, schedule_at=None, interval=None, *args, **kwargs): |
719 |
| - """ |
720 |
| - Create a JobResult instance and enqueue a job using the given callable |
721 |
| -
|
722 |
| - Args: |
723 |
| - func: The callable object to be enqueued for execution |
724 |
| - name: Name for the JobResult instance |
725 |
| - obj_type: ContentType to link to the JobResult instance obj_type |
726 |
| - user: User object to link to the JobResult instance |
727 |
| - schedule_at: Schedule the job to be executed at the passed date and time |
728 |
| - interval: Recurrence interval (in minutes) |
729 |
| - """ |
730 |
| - rq_queue_name = get_queue_for_model(obj_type.model) |
731 |
| - queue = django_rq.get_queue(rq_queue_name) |
732 |
| - status = JobResultStatusChoices.STATUS_SCHEDULED if schedule_at else JobResultStatusChoices.STATUS_PENDING |
733 |
| - job_result: JobResult = JobResult.objects.create( |
734 |
| - name=name, |
735 |
| - status=status, |
736 |
| - obj_type=obj_type, |
737 |
| - scheduled=schedule_at, |
738 |
| - interval=interval, |
739 |
| - user=user, |
740 |
| - job_id=uuid.uuid4() |
741 |
| - ) |
742 |
| - |
743 |
| - if schedule_at: |
744 |
| - queue.enqueue_at(schedule_at, func, job_id=str(job_result.job_id), job_result=job_result, **kwargs) |
745 |
| - else: |
746 |
| - queue.enqueue(func, job_id=str(job_result.job_id), job_result=job_result, **kwargs) |
747 |
| - |
748 |
| - return job_result |
749 |
| - |
750 |
| - def trigger_webhooks(self, event): |
751 |
| - rq_queue_name = get_config().QUEUE_MAPPINGS.get('webhook', RQ_QUEUE_DEFAULT) |
752 |
| - rq_queue = django_rq.get_queue(rq_queue_name, is_async=False) |
753 |
| - |
754 |
| - # Fetch any webhooks matching this object type and action |
755 |
| - webhooks = Webhook.objects.filter( |
756 |
| - **{f'type_{event}': True}, |
757 |
| - content_types=self.obj_type, |
758 |
| - enabled=True |
759 |
| - ) |
760 |
| - |
761 |
| - for webhook in webhooks: |
762 |
| - rq_queue.enqueue( |
763 |
| - "extras.webhooks_worker.process_webhook", |
764 |
| - webhook=webhook, |
765 |
| - model_name=self.obj_type.model, |
766 |
| - event=event, |
767 |
| - data=self.data, |
768 |
| - timestamp=str(timezone.now()), |
769 |
| - username=self.user.username |
770 |
| - ) |
771 |
| - |
772 |
| - |
773 | 579 | class ConfigRevision(models.Model):
|
774 | 580 | """
|
775 | 581 | An atomic revision of NetBox's configuration.
|
|
0 commit comments