Browse Source

Added bulk edit via PUT to viewsets and routers

David Burke 5 years ago
parent
commit
25d74ee851

+ 20 - 0
glitchtip/routers.py

@@ -0,0 +1,20 @@
+from rest_framework.routers import Route
+from rest_framework_nested import routers
+
+
+class BulkSimpleRouter(routers.SimpleRouter):
+    """
+    Router supports PUT method on list view to support bulk updates
+    Thanks to Github user thomasWajs
+    https://github.com/miki725/django-rest-framework-bulk/issues/11#issuecomment-45742375
+    Fun fact. I, bufke, first opened the question about this in 2014!
+    """
+
+    routes = routers.SimpleRouter.routes
+    routes[0] = Route(
+        url=r"^{prefix}{trailing_slash}$",
+        mapping={"get": "list", "post": "create", "put": "bulk_update",},
+        name="{basename}-list",
+        detail=False,
+        initkwargs={"suffix": "List"},
+    )

+ 1 - 1
glitchtip/settings.py

@@ -102,7 +102,7 @@ ROOT_URLCONF = "glitchtip.urls"
 TEMPLATES = [
     {
         "BACKEND": "django.template.backends.django.DjangoTemplates",
-        "DIRS": [path("dist")],
+        "DIRS": [path("dist"), path("templates")],
         "APP_DIRS": True,
         "OPTIONS": {
             "context_processors": [

+ 1 - 1
glitchtip/urls.py

@@ -1,6 +1,6 @@
 from django.conf import settings
 from django.urls import path, include, re_path
-from django.views.generic.base import TemplateView
+from django.views.generic import TemplateView
 from rest_framework_nested import routers
 from issues.urls import router as issuesRouter
 from projects.urls import router as projectsRouter

+ 6 - 0
issues/models.py

@@ -14,6 +14,12 @@ class EventStatus(models.IntegerChoices):
     RESOLVED = 1, "resolved"
     IGNORED = 2, "ignored"
 
+    @classmethod
+    def from_string(cls, string: str):
+        for status in cls:
+            if status.label == string:
+                return status
+
 
 class LogLevel(models.IntegerChoices):
     NOTSET = 0, "sample"

+ 1 - 1
issues/serializers.py

@@ -54,7 +54,7 @@ class IssueSerializer(serializers.ModelSerializer):
     shortId = serializers.CharField(default="Not implemented", read_only=True)
     stats = serializers.JSONField(default=dict, read_only=True)
     status = serializers.CharField(source="get_status_display")
-    statusDetails = serializers.JSONField(default=dict)
+    statusDetails = serializers.JSONField(default=dict, read_only=True)
     subscriptionDetails = serializers.CharField(default=None, read_only=True)
     type = serializers.CharField(source="get_type_display", read_only=True)
     userCount = serializers.IntegerField(default=0, read_only=True)

+ 21 - 0
issues/tests.py

@@ -2,6 +2,7 @@ import json
 from rest_framework.test import APITestCase
 from model_bakery import baker
 from glitchtip.test_utils import generators
+from .models import Issue, EventStatus
 
 
 class IssueStoreTestCase(APITestCase):
@@ -45,3 +46,23 @@ class EventTestCase(APITestCase):
         url = f"/api/0/projects/{project.organization.slug}/{project.slug}/events/"
         res = self.client.get(url)
         self.assertContains(res, event.pk.hex)
+
+
+class IssuesAPITestCase(APITestCase):
+    def setUp(self):
+        self.user = baker.make("users.user")
+        self.client.force_login(self.user)
+
+    def test_bulk_update(self):
+        """ Bulk update only supports Issue status """
+        project = baker.make("projects.Project")
+        issues = baker.make(Issue, project=project, _quantity=2)
+        url = f"/api/0/issues/?id={issues[0].id}&id={issues[1].id}"
+        status_to_set = EventStatus.RESOLVED
+        data = {"status": status_to_set.label}
+        res = self.client.put(url, data)
+        self.assertContains(res, status_to_set.label)
+        issues = Issue.objects.all()
+        self.assertEqual(issues[0].status, status_to_set)
+        self.assertEqual(issues[1].status, status_to_set)
+

+ 3 - 1
issues/urls.py

@@ -1,8 +1,10 @@
 from django.urls import path, include
 from rest_framework_nested import routers
+from glitchtip.routers import BulkSimpleRouter
 from .views import IssueViewSet, EventViewSet, EventStoreAPIView, MakeSampleErrorView
 
-router = routers.SimpleRouter()
+
+router = BulkSimpleRouter()
 router.register(r"issues", IssueViewSet)
 
 issues_router = routers.NestedSimpleRouter(router, r"issues", lookup="issue")

+ 24 - 2
issues/views.py

@@ -5,7 +5,7 @@ from rest_framework.views import APIView
 from rest_framework.response import Response
 from utils.auth import parse_auth_header
 from projects.models import Project
-from .models import Issue, Event
+from .models import Issue, Event, EventStatus
 from .serializers import (
     IssueSerializer,
     EventSerializer,
@@ -16,6 +16,19 @@ from .serializers import (
 
 
 class IssueViewSet(viewsets.ModelViewSet):
+    """
+    View and bulk update issues.
+    
+    # Bulk updates
+
+    Submit PUT request to bulk update Issue statuses
+
+    ## Query Parameters
+
+    - id (int) — a list of IDs of the issues to be removed.  This parameter shall be repeated for each issue.
+
+    """
+
     queryset = Issue.objects.all()
     serializer_class = IssueSerializer
 
@@ -34,6 +47,14 @@ class IssueViewSet(viewsets.ModelViewSet):
         )
         return qs
 
+    def bulk_update(self, request, *args, **kwargs):
+        queryset = self.get_queryset()
+        ids = request.GET.getlist("id")
+        queryset = queryset.filter(id__in=ids)
+        status = EventStatus.from_string(request.POST.get("status"))
+        queryset.update(status=status)
+        return Response({"status": status.label})
+
 
 class EventViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = Event.objects.all()
@@ -55,7 +76,7 @@ class EventViewSet(viewsets.ReadOnlyModelViewSet):
 class EventStoreAPIView(APIView):
     permission_classes = [permissions.AllowAny]
 
-    def get_serializer(self, data):
+    def get_serializer(self, data=[]):
         """ Determine event type and return serializer """
         if "exception" in data:
             return StoreErrorSerializer(data=data)
@@ -76,6 +97,7 @@ class EventStoreAPIView(APIView):
             serializer.create(project, serializer.data)
             return Response({"id": data["event_id"].replace("-", "")})
         # TODO {"error": "Invalid api key"}, CSP type, valid json but no type at all
+        print(serializer.errors)
         return Response()
 
     @classmethod

+ 2 - 1
organizations_ext/urls.py

@@ -1,9 +1,10 @@
 from django.urls import path, include
 from rest_framework_nested import routers
 from issues.views import IssueViewSet
+from glitchtip.routers import BulkSimpleRouter
 from .views import OrganizationViewSet
 
-router = routers.SimpleRouter()
+router = BulkSimpleRouter()
 router.register(r"organizations", OrganizationViewSet)
 
 organizations_router = routers.NestedSimpleRouter(

+ 29 - 0
projects/migrations/0002_auto_20191215_1550.py

@@ -0,0 +1,29 @@
+# Generated by Django 3.0 on 2019-12-15 15:50
+
+from django.db import migrations, models
+import django_extensions.db.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('projects', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='projectkey',
+            old_name='date_added',
+            new_name='created',
+        ),
+        migrations.AlterField(
+            model_name='project',
+            name='platform',
+            field=models.CharField(blank=True, max_length=64, null=True),
+        ),
+        migrations.AlterField(
+            model_name='project',
+            name='slug',
+            field=django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=['name', 'organization_id']),
+        ),
+    ]

Some files were not shown because too many files changed in this diff