Source code for action_triggers.models

import re
import typing as _t

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.validators import MinValueValidator
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from action_triggers import conf
from action_triggers.enums import HTTPMethodChoices, SignalChoices

UserModel = get_user_model()


[docs] class BaseAction(models.Model): """Abstract model to represent an action.""" TIMEOUT_SETTING_KEY: str timeout_secs = models.FloatField( _("Timeout (seconds)"), validators=[MinValueValidator(0.0)], help_text=_( "The number of seconds to wait before the action is killed." ), blank=True, null=True, ) @property def timeout_respecting_max(self) -> _t.Union[float, None]: """Return the timeout respecting the maximum timeout setting. :return: The timeout respecting the maximum timeout setting. """ default_max = _t.cast( _t.Optional[float], settings.ACTION_TRIGGER_SETTINGS.get(self.TIMEOUT_SETTING_KEY), ) if default_max is None: return self.timeout_secs if self.timeout_secs is None: return default_max return min(self.timeout_secs, default_max)
[docs] class Meta: abstract = True
[docs] class ConfigQuerySet(models.QuerySet): """Custom queryset for the Config model."""
[docs] def active(self) -> "ConfigQuerySet": """Return only active configurations. :return: A queryset of active configurations :rtype: ConfigQuerySet """ return self.filter(active=True)
[docs] def for_signal(self, signal: SignalChoices) -> "ConfigQuerySet": """Return only configurations for the given signal. :param signal: The signal to filter by. :type signal: SignalChoices :return: A queryset of configurations for the given signal :rtype: ConfigQuerySet """ return self.filter(config_signals__signal=signal)
[docs] def for_model( self, model: _t.Union[models.Model, _t.Type[models.Model]], ) -> "ConfigQuerySet": """Return only configurations for the given model. :param model: The model to filter by. :type model: Union[models.Model, Type[models.Model]] :return: A queryset of configurations for the given model :rtype: ConfigQuerySet """ return self.filter( content_types=ContentType.objects.get_for_model(model) )
[docs] class Config(models.Model): """Model to represent the action triggers configuration.""" def _content_type_limit_choices_to() -> dict: # type: ignore[misc] return { "id__in": conf.get_content_type_choices().values_list( "id", flat=True ) } payload = models.JSONField(_("Payload"), blank=True, null=True) created_on = models.DateTimeField(_("Created on"), default=timezone.now) created_by = models.ForeignKey( UserModel, on_delete=models.SET_NULL, related_name="created_configs", verbose_name=_("Created by"), null=True, blank=True, ) active = models.BooleanField(_("Active"), default=True) content_types = models.ManyToManyField( ContentType, related_name="configs", verbose_name=_("Models"), help_text=_("Models to trigger actions on."), limit_choices_to=_content_type_limit_choices_to, ) objects = ConfigQuerySet.as_manager() # type: ignore class Meta: verbose_name = _("Configuration") verbose_name_plural = _("Configurations") db_table = conf.DB_TABLE_PREFIX + "config" def __str__(self) -> str: return f"Config {self.id}"
[docs] class Webhook(BaseAction): """Model to represent the webhook configuration.""" TIMEOUT_SETTING_KEY = "MAX_WEBHOOK_TIMEOUT" config = models.ForeignKey( Config, on_delete=models.CASCADE, related_name="webhooks", verbose_name=_("Configuration"), ) url = models.URLField(_("URL")) http_method = models.CharField( _("HTTP Method"), max_length=10, choices=HTTPMethodChoices.choices, default=HTTPMethodChoices.POST, ) headers = models.JSONField(_("Headers"), blank=True, null=True) class Meta: verbose_name = _("Webhook") verbose_name_plural = _("Webhooks") db_table = conf.DB_TABLE_PREFIX + "webhook" def __str__(self) -> str: url_repr = self.url[:25] if len(self.url) > 25: url_repr += "..." return f"(Webhook {self.id}) [{self.http_method}] {url_repr}"
[docs] def is_endpoint_whitelisted(self) -> bool: """Check if the webhook endpoint is whitelisted. :return: True if the endpoint is whitelisted, False otherwise. """ patterns = settings.ACTION_TRIGGERS.get( "whitelisted_webhook_endpoint_patterns", [".*"], ) for pattern in patterns: if re.match(pattern, self.url): return True else: return False
[docs] class MessageBrokerQueue(BaseAction): """Model to represent a message broker queue configuration.""" TIMEOUT_SETTING_KEY = "MAX_BROKER_TIMEOUT" config = models.ForeignKey( Config, on_delete=models.CASCADE, related_name="message_broker_queues", verbose_name=_("Configuration"), ) name = models.CharField( _("Name"), max_length=255, help_text=_("Corresponds to a queue config in the settings."), ) conn_details = models.JSONField( _("Connection Details"), blank=True, null=True, help_text=_("Connection details for the queue."), ) parameters = models.JSONField( _("Parameters"), blank=True, null=True, help_text=_("Additional parameters for the queue."), ) class Meta: verbose_name = _("Message Broker Queue") verbose_name_plural = _("Message Broker Queues") db_table = conf.DB_TABLE_PREFIX + "message_broker_queue" def __str__(self) -> str: return f"(Queue {self.id}) {self.name}"
[docs] class ConfigSignal(models.Model): """Model to represent the type of signals to trigger for.""" config = models.ForeignKey( Config, on_delete=models.CASCADE, related_name="config_signals", verbose_name=_("Configuration"), ) signal = models.CharField( _("Signal"), max_length=50, choices=SignalChoices.choices, ) class Meta: verbose_name = _("Configuration Signal") verbose_name_plural = _("Configuration Signals") db_table = conf.DB_TABLE_PREFIX + "config_signal" def __str__(self) -> str: return f"(ConfigSignal {self.id}) {self.signal}" def __repr__(self) -> str: return f"ConfigSignal({self.id}, {self.signal})"