Skip to content

Commit ffe88a5

Browse files
committed
Add v2 APIs for Project
We have added several APIs to help users easily manage Projects and objects related to each project, such as notifiers, rules, exporters, and URLs.
1 parent bd8b0d8 commit ffe88a5

31 files changed

+1386
-7
lines changed

promgen/filters.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,26 @@ class URLFilter(django_filters.rest_framework.FilterSet):
191191
choices=models.Probe.objects.values_list("module", "description").distinct(),
192192
help_text="Filter by exact probe scheme. Example: probe=http_2xx",
193193
)
194+
195+
196+
class ProjectFilterV2(django_filters.rest_framework.FilterSet):
197+
name = django_filters.CharFilter(
198+
field_name="name",
199+
lookup_expr="contains",
200+
help_text="Filter by project name containing a specific substring. Example: name=Example Project",
201+
)
202+
service = django_filters.CharFilter(
203+
field_name="service__name",
204+
lookup_expr="exact",
205+
help_text="Filter by exact service name. Example: service=Example Service",
206+
)
207+
shard = django_filters.CharFilter(
208+
field_name="shard__name",
209+
lookup_expr="exact",
210+
help_text="Filter by exact shard name. Example: shard=Example Shard",
211+
)
212+
owner = django_filters.CharFilter(
213+
field_name="owner__username",
214+
lookup_expr="exact",
215+
help_text="Filter by exact owner username. Example: owner=Example Owner",
216+
)

promgen/fixtures/testcases.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@
7070
pk: 2
7171
fields:
7272
name: another-project
73-
owner: 1
74-
service: 1
73+
owner: 2
74+
service: 2
7575
shard: 1
7676
farm: 1
7777
- model: promgen.host

promgen/rest_v2.py

Lines changed: 308 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from rest_framework.permissions import IsAuthenticated
1313
from rest_framework.response import Response
1414

15-
from promgen import filters, models, serializers
15+
from promgen import filters, models, serializers, signals
1616

1717
class RuleMixin:
1818
@extend_schema(
@@ -288,3 +288,310 @@ class URLViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.Gene
288288
lookup_value_regex = "[^/]+"
289289
lookup_field = "id"
290290
pagination_class = PromgenPagination
291+
292+
293+
@extend_schema_view(
294+
list=extend_schema(summary="List Projects", description="Retrieve a list of all projects."),
295+
retrieve=extend_schema(
296+
summary="Retrieve Project",
297+
description="Retrieve detailed information about a specific project.",
298+
),
299+
create=extend_schema(summary="Create Project", description="Create a new project."),
300+
update=extend_schema(summary="Update Project", description="Update an existing project."),
301+
partial_update=extend_schema(
302+
summary="Partially Update Project", description="Partially update an existing project."
303+
),
304+
destroy=extend_schema(summary="Delete Project", description="Delete an existing project."),
305+
)
306+
@extend_schema(tags=["Project"])
307+
class ProjectViewSet(NotifierMixin, RuleMixin, viewsets.ModelViewSet):
308+
queryset = models.Project.objects.prefetch_related("service", "shard", "farm")
309+
filterset_class = filters.ProjectFilterV2
310+
lookup_value_regex = "[^/]+"
311+
lookup_field = "id"
312+
pagination_class = PromgenPagination
313+
314+
def get_serializer_class(self):
315+
if self.action == "list":
316+
return serializers.ProjectRetrieveSerializer
317+
if self.action == "retrieve":
318+
return serializers.ProjectRetrieveSerializer
319+
if self.action == "create":
320+
return serializers.ProjectCreateSerializer
321+
if self.action == "update":
322+
return serializers.ProjectUpdateSerializer
323+
if self.action == "partial_update":
324+
return serializers.ProjectUpdateSerializer
325+
return None
326+
327+
@extend_schema(
328+
summary="List Exporters",
329+
description="Retrieve all exporters associated with the specified project.",
330+
responses=serializers.ExporterSerializer(many=True),
331+
)
332+
@action(detail=True, methods=["get"], pagination_class=None, filterset_class=None)
333+
def exporters(self, request, id):
334+
project = self.get_object()
335+
return Response(serializers.ExporterSerializer(project.exporter_set.all(), many=True).data)
336+
337+
@extend_schema(
338+
summary="List URLs",
339+
description="Retrieve all URLs associated with the specified project.",
340+
responses=serializers.URLSerializer(many=True),
341+
)
342+
@action(detail=True, methods=["get"], pagination_class=None, filterset_class=None)
343+
def urls(self, request, id):
344+
project = self.get_object()
345+
return Response(serializers.URLSerializer(project.url_set.all(), many=True).data)
346+
347+
@extend_schema(
348+
summary="Link Farm",
349+
description="Link a farm to the specified project.",
350+
request=serializers.LinkFarmSerializer,
351+
responses=serializers.ProjectRetrieveSerializer,
352+
)
353+
@action(detail=True, methods=["post"], url_path="farm-link")
354+
def link_farm(self, request, id):
355+
serializer = serializers.LinkFarmSerializer(data=request.data)
356+
serializer.is_valid(raise_exception=True)
357+
project = self.get_object()
358+
farm, created = models.Farm.objects.get_or_create(
359+
name=serializer.validated_data["farm"],
360+
source=serializer.validated_data["source"],
361+
)
362+
if created:
363+
farm.refresh()
364+
project.farm = farm
365+
project.save()
366+
return Response(serializers.ProjectRetrieveSerializer(project).data)
367+
368+
@extend_schema(
369+
summary="Unlink Farm",
370+
description="Unlink the farm from the specified project.",
371+
responses=serializers.ProjectRetrieveSerializer,
372+
)
373+
@action(detail=True, methods=["post"], url_path="farm-unlink")
374+
def unlink_farm(self, request, id, oldfarm=None):
375+
project = self.get_object()
376+
if project.farm is None:
377+
return Response(serializers.ProjectRetrieveSerializer(project).data)
378+
379+
old_farm, project.farm = project.farm, None
380+
project.save()
381+
signals.trigger_write_config.send(request)
382+
383+
if old_farm.project_set.count() == 0 and old_farm.editable is False:
384+
old_farm.delete()
385+
return Response(serializers.ProjectRetrieveSerializer(project).data)
386+
387+
@extend_schema(
388+
summary="Register URL",
389+
description="Register a new URL for the specified project.",
390+
request=serializers.RegisterURLProjectSerializer,
391+
responses=serializers.URLSerializer(many=True),
392+
)
393+
@action(
394+
detail=True,
395+
methods=["post"],
396+
url_path="urls/register",
397+
pagination_class=None,
398+
filterset_class=None,
399+
)
400+
def register_url(self, request, id):
401+
serializer = serializers.RegisterURLProjectSerializer(data=request.data)
402+
serializer.is_valid(raise_exception=True)
403+
project = self.get_object()
404+
405+
models.URL.objects.get_or_create(
406+
project=project,
407+
url=serializer.validated_data["url"],
408+
probe=models.Probe.objects.get(module=serializer.validated_data["probe"]),
409+
)
410+
return Response(serializers.URLSerializer(project.url_set, many=True).data)
411+
412+
@extend_schema(
413+
summary="Delete URL",
414+
description="Delete a URL from the specified project.",
415+
request=serializers.RegisterURLProjectSerializer,
416+
responses=serializers.URLSerializer(many=True),
417+
)
418+
@action(
419+
detail=True,
420+
methods=["post"],
421+
url_path="urls/delete",
422+
pagination_class=None,
423+
filterset_class=None,
424+
)
425+
def delete_url(self, request, id):
426+
serializer = serializers.RegisterURLProjectSerializer(data=request.data)
427+
serializer.is_valid(raise_exception=True)
428+
project = self.get_object()
429+
430+
models.URL.objects.filter(
431+
project=project,
432+
url=serializer.validated_data["url"],
433+
probe=models.Probe.objects.get(module=serializer.validated_data["probe"]),
434+
).delete()
435+
return Response(serializers.URLSerializer(project.url_set, many=True).data)
436+
437+
@extend_schema(
438+
summary="Register Exporter",
439+
description="Register a new exporter for the specified project.",
440+
request=serializers.RegisterExporterProjectSerializer,
441+
responses=serializers.ExporterSerializer(many=True),
442+
)
443+
@action(
444+
detail=True,
445+
methods=["post"],
446+
url_path="exporters/register",
447+
pagination_class=None,
448+
filterset_class=None,
449+
)
450+
def register_exporter(self, request, id):
451+
serializer = serializers.RegisterExporterProjectSerializer(data=request.data)
452+
serializer.is_valid(raise_exception=True)
453+
project = self.get_object()
454+
455+
attributes = {
456+
"project_id": project.id,
457+
}
458+
459+
for field in serializer.fields:
460+
value = serializer.validated_data.get(field)
461+
if value is not None:
462+
attributes[field] = value
463+
464+
models.Exporter.objects.get_or_create(**attributes)
465+
return Response(serializers.ExporterSerializer(project.exporter_set, many=True).data)
466+
467+
@extend_schema(
468+
summary="Update Exporter",
469+
description="Update an existing exporter for the specified project.",
470+
request=serializers.UpdateExporterProjectSerializer,
471+
responses={
472+
200: serializers.ExporterSerializer(many=True),
473+
404: {"detail": "Exporter not found."},
474+
},
475+
)
476+
@action(
477+
detail=True,
478+
methods=["post"],
479+
url_path="exporters/update",
480+
pagination_class=None,
481+
filterset_class=None,
482+
)
483+
def update_exporter(self, request, id):
484+
serializer = serializers.RegisterExporterProjectSerializer(data=request.data)
485+
serializer.is_valid(raise_exception=True)
486+
project = self.get_object()
487+
488+
attributes = {
489+
"project_id": project.id,
490+
}
491+
492+
for field in serializer.fields:
493+
value = serializer.validated_data.get(field)
494+
if value is not None and field != "enabled":
495+
attributes[field] = value
496+
497+
exporter = models.Exporter.objects.filter(**attributes).first()
498+
if exporter is not None:
499+
exporter.enabled = serializer.validated_data.get("enabled")
500+
exporter.save()
501+
return Response(serializers.ExporterSerializer(project.exporter_set, many=True).data)
502+
else:
503+
return Response({"detail": "Exporter not found."}, status=404)
504+
505+
@extend_schema(
506+
summary="Delete Exporter",
507+
description="Delete an exporter from the specified project.",
508+
request=serializers.DeleteExporterProjectSerializer,
509+
responses=serializers.ExporterSerializer(many=True),
510+
)
511+
@action(
512+
detail=True,
513+
methods=["post"],
514+
url_path="exporters/delete",
515+
pagination_class=None,
516+
filterset_class=None,
517+
)
518+
def delete_exporter(self, request, id):
519+
serializer = serializers.RegisterExporterProjectSerializer(data=request.data)
520+
serializer.is_valid(raise_exception=True)
521+
project = self.get_object()
522+
523+
attributes = {
524+
"project_id": project.id,
525+
}
526+
527+
for field in serializer.fields:
528+
value = serializer.validated_data.get(field)
529+
if value is not None:
530+
attributes[field] = value
531+
532+
models.Exporter.objects.filter(**attributes).delete()
533+
return Response(serializers.ExporterSerializer(project.exporter_set, many=True).data)
534+
535+
@extend_schema(
536+
summary="Register Notifier",
537+
description="Register a new notifier for the specified project.",
538+
request=serializers.RegisterNotifierSerializer,
539+
responses=serializers.NotifierSerializer(many=True),
540+
)
541+
@action(
542+
detail=True,
543+
methods=["post"],
544+
url_path="notifiers/register",
545+
pagination_class=None,
546+
filterset_class=None,
547+
)
548+
def register_notifier(self, request, id):
549+
serializer = serializers.RegisterNotifierSerializer(data=request.data)
550+
serializer.is_valid(raise_exception=True)
551+
project = self.get_object()
552+
553+
attributes = {
554+
"content_type_id": get_content_type_for_model(models.Project).id,
555+
"object_id": project.id,
556+
}
557+
558+
for field in serializer.fields:
559+
value = serializer.validated_data.get(field)
560+
if value is not None and field != "filters":
561+
attributes[field] = value
562+
563+
notifier, _ = models.Sender.objects.get_or_create(**attributes)
564+
for filter_data in serializer.validated_data.get("filters", []):
565+
models.Filter.objects.get_or_create(sender=notifier, **filter_data)
566+
return Response(serializers.NotifierSerializer(project.notifiers, many=True).data)
567+
568+
@extend_schema(
569+
summary="Register Rule",
570+
description="Register a new rule for the specified project.",
571+
request=serializers.RuleSerializer,
572+
responses=serializers.RuleSerializer(many=True),
573+
)
574+
@action(
575+
detail=True,
576+
methods=["post"],
577+
url_path="rules/register",
578+
pagination_class=None,
579+
filterset_class=None,
580+
)
581+
def register_rule(self, request, id):
582+
serializer = serializers.RuleSerializer(data=request.data)
583+
serializer.is_valid(raise_exception=True)
584+
project = self.get_object()
585+
586+
attributes = {
587+
"content_type_id": get_content_type_for_model(models.Project).id,
588+
"object_id": project.id,
589+
}
590+
591+
for field in serializer.fields:
592+
value = serializer.validated_data.get(field)
593+
if value is not None:
594+
attributes[field] = value
595+
596+
rule, _ = models.Rule.objects.get_or_create(**attributes)
597+
return Response(serializers.RuleSerializer(project.rule_set, many=True).data)

0 commit comments

Comments
 (0)