Add periodic exchange task management API
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Successful in 3m16s
CI/CD Pipeline / Run Tests (push) Successful in 3m26s
CI/CD Pipeline / Telegram Notify Success (push) Failing after 1m29s
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m44s
CI/CD Pipeline / Code Quality Checks (pull_request) Successful in 20m19s
CI/CD Pipeline / Telegram Notify Success (pull_request) Failing after 1m34s
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Successful in 3m16s
CI/CD Pipeline / Run Tests (push) Successful in 3m26s
CI/CD Pipeline / Telegram Notify Success (push) Failing after 1m29s
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m44s
CI/CD Pipeline / Code Quality Checks (pull_request) Successful in 20m19s
CI/CD Pipeline / Telegram Notify Success (pull_request) Failing after 1m34s
This commit is contained in:
@@ -1,9 +1,53 @@
|
||||
"""Сериализаторы приложения обмена данными."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from apps.exchange.models import ExchangeConnection
|
||||
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
def validate_exchange_copy_payload(attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Проверить совместимость параметров запуска копирования."""
|
||||
mode = attrs["mode"]
|
||||
table = attrs.get("table")
|
||||
tables = attrs.get("tables")
|
||||
|
||||
if mode == "single" and not table:
|
||||
raise serializers.ValidationError(
|
||||
{"table": "Для mode=single нужно указать table"}
|
||||
)
|
||||
|
||||
if mode == "selected" and not tables:
|
||||
raise serializers.ValidationError(
|
||||
{"tables": "Для mode=selected нужно указать tables"}
|
||||
)
|
||||
|
||||
if mode != "single" and table:
|
||||
raise serializers.ValidationError(
|
||||
{"table": "Поле table допустимо только для mode=single"}
|
||||
)
|
||||
|
||||
if mode != "selected" and tables:
|
||||
raise serializers.ValidationError(
|
||||
{"tables": "Поле tables допустимо только для mode=selected"}
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
def get_periodic_task_payload(task: PeriodicTask) -> dict[str, Any]:
|
||||
"""Извлечь payload exchange-задачи из kwargs django_celery_beat."""
|
||||
try:
|
||||
kwargs = json.loads(task.kwargs or "{}")
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
payload = kwargs.get("payload")
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
|
||||
class ExchangeConnectionSerializer(serializers.ModelSerializer):
|
||||
"""Сериализатор подключения без выдачи пароля в ответах."""
|
||||
|
||||
@@ -61,28 +105,257 @@ class ExchangeCopyRequestSerializer(serializers.Serializer):
|
||||
truncate_before_copy = serializers.BooleanField(default=True)
|
||||
|
||||
def validate(self, attrs):
|
||||
mode = attrs["mode"]
|
||||
table = attrs.get("table")
|
||||
tables = attrs.get("tables")
|
||||
return validate_exchange_copy_payload(attrs)
|
||||
|
||||
if mode == "single" and not table:
|
||||
|
||||
class ExchangePeriodicTaskSerializer(serializers.ModelSerializer):
|
||||
"""Сериализатор периодической задачи обмена."""
|
||||
|
||||
schedule_type = serializers.SerializerMethodField()
|
||||
interval_every = serializers.SerializerMethodField()
|
||||
interval_period = serializers.SerializerMethodField()
|
||||
crontab_minute = serializers.SerializerMethodField()
|
||||
crontab_hour = serializers.SerializerMethodField()
|
||||
crontab_day_of_week = serializers.SerializerMethodField()
|
||||
crontab_day_of_month = serializers.SerializerMethodField()
|
||||
crontab_month_of_year = serializers.SerializerMethodField()
|
||||
crontab_timezone = serializers.SerializerMethodField()
|
||||
mode = serializers.SerializerMethodField()
|
||||
table = serializers.SerializerMethodField()
|
||||
tables = serializers.SerializerMethodField()
|
||||
truncate_before_copy = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = PeriodicTask
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"enabled",
|
||||
"schedule_type",
|
||||
"interval_every",
|
||||
"interval_period",
|
||||
"crontab_minute",
|
||||
"crontab_hour",
|
||||
"crontab_day_of_week",
|
||||
"crontab_day_of_month",
|
||||
"crontab_month_of_year",
|
||||
"crontab_timezone",
|
||||
"mode",
|
||||
"table",
|
||||
"tables",
|
||||
"truncate_before_copy",
|
||||
"last_run_at",
|
||||
"total_run_count",
|
||||
"date_changed",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_schedule_type(self, obj: PeriodicTask) -> str | None:
|
||||
if obj.interval_id:
|
||||
return "interval"
|
||||
if obj.crontab_id:
|
||||
return "crontab"
|
||||
return None
|
||||
|
||||
def get_interval_every(self, obj: PeriodicTask) -> int | None:
|
||||
return obj.interval.every if obj.interval_id else None
|
||||
|
||||
def get_interval_period(self, obj: PeriodicTask) -> str | None:
|
||||
return obj.interval.period if obj.interval_id else None
|
||||
|
||||
def get_crontab_minute(self, obj: PeriodicTask) -> str | None:
|
||||
return obj.crontab.minute if obj.crontab_id else None
|
||||
|
||||
def get_crontab_hour(self, obj: PeriodicTask) -> str | None:
|
||||
return obj.crontab.hour if obj.crontab_id else None
|
||||
|
||||
def get_crontab_day_of_week(self, obj: PeriodicTask) -> str | None:
|
||||
return obj.crontab.day_of_week if obj.crontab_id else None
|
||||
|
||||
def get_crontab_day_of_month(self, obj: PeriodicTask) -> str | None:
|
||||
return obj.crontab.day_of_month if obj.crontab_id else None
|
||||
|
||||
def get_crontab_month_of_year(self, obj: PeriodicTask) -> str | None:
|
||||
return obj.crontab.month_of_year if obj.crontab_id else None
|
||||
|
||||
def get_crontab_timezone(self, obj: PeriodicTask) -> str | None:
|
||||
if not obj.crontab_id:
|
||||
return None
|
||||
timezone = obj.crontab.timezone
|
||||
return str(timezone) if timezone is not None else None
|
||||
|
||||
def get_mode(self, obj: PeriodicTask) -> str | None:
|
||||
return get_periodic_task_payload(obj).get("mode")
|
||||
|
||||
def get_table(self, obj: PeriodicTask) -> str | None:
|
||||
return get_periodic_task_payload(obj).get("table")
|
||||
|
||||
def get_tables(self, obj: PeriodicTask) -> list[str] | None:
|
||||
return get_periodic_task_payload(obj).get("tables")
|
||||
|
||||
def get_truncate_before_copy(self, obj: PeriodicTask) -> bool | None:
|
||||
return get_periodic_task_payload(obj).get("truncate_before_copy")
|
||||
|
||||
|
||||
class ExchangePeriodicTaskUpsertSerializer(serializers.Serializer):
|
||||
"""Входные данные для создания и изменения периодической задачи обмена."""
|
||||
|
||||
name = serializers.CharField(max_length=200, required=False)
|
||||
description = serializers.CharField(required=False, allow_blank=True)
|
||||
enabled = serializers.BooleanField(required=False)
|
||||
|
||||
schedule_type = serializers.ChoiceField(
|
||||
choices=["interval", "crontab"],
|
||||
required=False,
|
||||
)
|
||||
interval_every = serializers.IntegerField(min_value=1, required=False)
|
||||
interval_period = serializers.ChoiceField(
|
||||
choices=[choice[0] for choice in IntervalSchedule.PERIOD_CHOICES],
|
||||
required=False,
|
||||
)
|
||||
crontab_minute = serializers.CharField(max_length=64, required=False)
|
||||
crontab_hour = serializers.CharField(max_length=64, required=False)
|
||||
crontab_day_of_week = serializers.CharField(max_length=64, required=False)
|
||||
crontab_day_of_month = serializers.CharField(max_length=64, required=False)
|
||||
crontab_month_of_year = serializers.CharField(max_length=64, required=False)
|
||||
|
||||
mode = serializers.ChoiceField(
|
||||
choices=["all", "single", "selected"],
|
||||
required=False,
|
||||
)
|
||||
table = serializers.CharField(required=False)
|
||||
tables = serializers.ListField(
|
||||
child=serializers.CharField(),
|
||||
required=False,
|
||||
allow_empty=False,
|
||||
)
|
||||
truncate_before_copy = serializers.BooleanField(required=False)
|
||||
|
||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
if not self.instance and "name" not in attrs:
|
||||
raise serializers.ValidationError({"name": "Обязательное поле."})
|
||||
|
||||
schedule_type = self._resolve_schedule_type(attrs)
|
||||
if not schedule_type:
|
||||
raise serializers.ValidationError(
|
||||
{"table": "Для mode=single нужно указать table"}
|
||||
{"schedule_type": "Нужно указать тип расписания."}
|
||||
)
|
||||
|
||||
if mode == "selected" and not tables:
|
||||
raise serializers.ValidationError(
|
||||
{"tables": "Для mode=selected нужно указать tables"}
|
||||
)
|
||||
|
||||
if mode != "single" and table:
|
||||
raise serializers.ValidationError(
|
||||
{"table": "Поле table допустимо только для mode=single"}
|
||||
)
|
||||
|
||||
if mode != "selected" and tables:
|
||||
raise serializers.ValidationError(
|
||||
{"tables": "Поле tables допустимо только для mode=selected"}
|
||||
)
|
||||
payload = self._build_payload(attrs)
|
||||
schedule = self._build_schedule(attrs, schedule_type)
|
||||
|
||||
attrs["payload"] = validate_exchange_copy_payload(payload)
|
||||
attrs["schedule"] = schedule
|
||||
return attrs
|
||||
|
||||
def _resolve_schedule_type(self, attrs: dict[str, Any]) -> str | None:
|
||||
if "schedule_type" in attrs:
|
||||
return attrs["schedule_type"]
|
||||
|
||||
if self.instance and self.instance.interval_id:
|
||||
return "interval"
|
||||
|
||||
if self.instance and self.instance.crontab_id:
|
||||
return "crontab"
|
||||
|
||||
return None
|
||||
|
||||
def _build_payload(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
current_payload = (
|
||||
get_periodic_task_payload(self.instance) if self.instance else {}
|
||||
)
|
||||
mode = attrs.get("mode", current_payload.get("mode", "all"))
|
||||
truncate_before_copy = attrs.get(
|
||||
"truncate_before_copy",
|
||||
current_payload.get("truncate_before_copy", True),
|
||||
)
|
||||
|
||||
if "mode" in attrs and attrs["mode"] != "single" and "table" not in attrs:
|
||||
table = None
|
||||
else:
|
||||
table = attrs.get("table", current_payload.get("table"))
|
||||
|
||||
if "mode" in attrs and attrs["mode"] != "selected" and "tables" not in attrs:
|
||||
tables = None
|
||||
else:
|
||||
tables = attrs.get("tables", current_payload.get("tables"))
|
||||
|
||||
return {
|
||||
"mode": mode,
|
||||
"table": table,
|
||||
"tables": tables,
|
||||
"truncate_before_copy": truncate_before_copy,
|
||||
}
|
||||
|
||||
def _build_schedule(
|
||||
self,
|
||||
attrs: dict[str, Any],
|
||||
schedule_type: str,
|
||||
) -> dict[str, Any]:
|
||||
if schedule_type == "interval":
|
||||
return self._build_interval_schedule(attrs)
|
||||
return self._build_crontab_schedule(attrs)
|
||||
|
||||
def _build_interval_schedule(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
current_schedule = self.instance.interval if self.instance else None
|
||||
interval_every = attrs.get(
|
||||
"interval_every",
|
||||
current_schedule.every if current_schedule else None,
|
||||
)
|
||||
interval_period = attrs.get(
|
||||
"interval_period",
|
||||
current_schedule.period if current_schedule else None,
|
||||
)
|
||||
|
||||
errors = {}
|
||||
if interval_every is None:
|
||||
errors["interval_every"] = "Обязательное поле для interval."
|
||||
if interval_period is None:
|
||||
errors["interval_period"] = "Обязательное поле для interval."
|
||||
if errors:
|
||||
raise serializers.ValidationError(errors)
|
||||
|
||||
return {
|
||||
"type": "interval",
|
||||
"every": interval_every,
|
||||
"period": interval_period,
|
||||
}
|
||||
|
||||
def _build_crontab_schedule(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
current_schedule = self.instance.crontab if self.instance else None
|
||||
fields = {
|
||||
"minute": attrs.get(
|
||||
"crontab_minute",
|
||||
current_schedule.minute if current_schedule else None,
|
||||
),
|
||||
"hour": attrs.get(
|
||||
"crontab_hour",
|
||||
current_schedule.hour if current_schedule else None,
|
||||
),
|
||||
"day_of_week": attrs.get(
|
||||
"crontab_day_of_week",
|
||||
current_schedule.day_of_week if current_schedule else None,
|
||||
),
|
||||
"day_of_month": attrs.get(
|
||||
"crontab_day_of_month",
|
||||
current_schedule.day_of_month if current_schedule else None,
|
||||
),
|
||||
"month_of_year": attrs.get(
|
||||
"crontab_month_of_year",
|
||||
current_schedule.month_of_year if current_schedule else None,
|
||||
),
|
||||
}
|
||||
|
||||
errors = {
|
||||
f"crontab_{field_name}": "Обязательное поле для crontab."
|
||||
for field_name, value in fields.items()
|
||||
if value is None
|
||||
}
|
||||
if errors:
|
||||
raise serializers.ValidationError(errors)
|
||||
|
||||
return {
|
||||
"type": "crontab",
|
||||
**fields,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user