|
1 | 1 | # Python imports |
2 | 2 | import zoneinfo |
| 3 | +import logging |
3 | 4 |
|
4 | 5 | # Django imports |
5 | 6 | from django.conf import settings |
6 | 7 | from django.core.exceptions import ObjectDoesNotExist, ValidationError |
7 | 8 | from django.db import IntegrityError |
8 | 9 | from django.urls import resolve |
9 | 10 | from django.utils import timezone |
10 | | -from plane.db.models.api import APIToken |
| 11 | + |
| 12 | +# Third party imports |
11 | 13 | from rest_framework import status |
12 | 14 | from rest_framework.permissions import IsAuthenticated |
13 | 15 | from rest_framework.response import Response |
14 | | - |
15 | | -# Third party imports |
| 16 | +from django_filters.rest_framework import DjangoFilterBackend |
| 17 | +from rest_framework.filters import SearchFilter |
| 18 | +from rest_framework.viewsets import ModelViewSet |
| 19 | +from rest_framework.exceptions import APIException |
16 | 20 | from rest_framework.generics import GenericAPIView |
17 | 21 |
|
18 | 22 | # Module imports |
| 23 | +from plane.db.models.api import APIToken |
19 | 24 | from plane.api.middleware.api_authentication import APIKeyAuthentication |
20 | 25 | from plane.api.rate_limit import ApiKeyRateThrottle, ServiceTokenRateThrottle |
21 | 26 | from plane.utils.exception_logger import log_exception |
22 | 27 | from plane.utils.paginator import BasePaginator |
23 | 28 | from plane.utils.core.mixins import ReadReplicaControlMixin |
24 | 29 |
|
25 | 30 |
|
| 31 | +logger = logging.getLogger("plane.api") |
| 32 | + |
| 33 | + |
26 | 34 | class TimezoneMixin: |
27 | 35 | """ |
28 | 36 | This enables timezone conversion according |
@@ -152,3 +160,118 @@ def fields(self): |
152 | 160 | def expand(self): |
153 | 161 | expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand] |
154 | 162 | return expand if expand else None |
| 163 | + |
| 164 | + |
| 165 | +class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePaginator): |
| 166 | + model = None |
| 167 | + |
| 168 | + authentication_classes = [APIKeyAuthentication] |
| 169 | + permission_classes = [ |
| 170 | + IsAuthenticated, |
| 171 | + ] |
| 172 | + use_read_replica = False |
| 173 | + |
| 174 | + def get_queryset(self): |
| 175 | + try: |
| 176 | + return self.model.objects.all() |
| 177 | + except Exception as e: |
| 178 | + log_exception(e) |
| 179 | + raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST) |
| 180 | + |
| 181 | + def handle_exception(self, exc): |
| 182 | + """ |
| 183 | + Handle any exception that occurs, by returning an appropriate response, |
| 184 | + or re-raising the error. |
| 185 | + """ |
| 186 | + try: |
| 187 | + response = super().handle_exception(exc) |
| 188 | + return response |
| 189 | + except Exception as e: |
| 190 | + if isinstance(e, IntegrityError): |
| 191 | + log_exception(e) |
| 192 | + return Response( |
| 193 | + {"error": "The payload is not valid"}, |
| 194 | + status=status.HTTP_400_BAD_REQUEST, |
| 195 | + ) |
| 196 | + |
| 197 | + if isinstance(e, ValidationError): |
| 198 | + logger.warning( |
| 199 | + "Validation Error", |
| 200 | + extra={ |
| 201 | + "error_code": "VALIDATION_ERROR", |
| 202 | + "error_message": str(e), |
| 203 | + }, |
| 204 | + ) |
| 205 | + return Response( |
| 206 | + {"error": "Please provide valid detail"}, |
| 207 | + status=status.HTTP_400_BAD_REQUEST, |
| 208 | + ) |
| 209 | + |
| 210 | + if isinstance(e, ObjectDoesNotExist): |
| 211 | + logger.warning( |
| 212 | + "Object Does Not Exist", |
| 213 | + extra={ |
| 214 | + "error_code": "OBJECT_DOES_NOT_EXIST", |
| 215 | + "error_message": str(e), |
| 216 | + }, |
| 217 | + ) |
| 218 | + return Response( |
| 219 | + {"error": "The required object does not exist."}, |
| 220 | + status=status.HTTP_404_NOT_FOUND, |
| 221 | + ) |
| 222 | + |
| 223 | + if isinstance(e, KeyError): |
| 224 | + logger.error( |
| 225 | + "Key Error", |
| 226 | + extra={ |
| 227 | + "error_code": "KEY_ERROR", |
| 228 | + "error_message": str(e), |
| 229 | + }, |
| 230 | + ) |
| 231 | + return Response( |
| 232 | + {"error": "The required key does not exist."}, |
| 233 | + status=status.HTTP_400_BAD_REQUEST, |
| 234 | + ) |
| 235 | + |
| 236 | + log_exception(e) |
| 237 | + return Response( |
| 238 | + {"error": "Something went wrong please try again later"}, |
| 239 | + status=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 240 | + ) |
| 241 | + |
| 242 | + def dispatch(self, request, *args, **kwargs): |
| 243 | + try: |
| 244 | + response = super().dispatch(request, *args, **kwargs) |
| 245 | + |
| 246 | + if settings.DEBUG: |
| 247 | + from django.db import connection |
| 248 | + |
| 249 | + print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}") |
| 250 | + |
| 251 | + return response |
| 252 | + except Exception as exc: |
| 253 | + response = self.handle_exception(exc) |
| 254 | + return response |
| 255 | + |
| 256 | + @property |
| 257 | + def workspace_slug(self): |
| 258 | + return self.kwargs.get("slug", None) |
| 259 | + |
| 260 | + @property |
| 261 | + def project_id(self): |
| 262 | + project_id = self.kwargs.get("project_id", None) |
| 263 | + if project_id: |
| 264 | + return project_id |
| 265 | + |
| 266 | + if resolve(self.request.path_info).url_name == "project": |
| 267 | + return self.kwargs.get("pk", None) |
| 268 | + |
| 269 | + @property |
| 270 | + def fields(self): |
| 271 | + fields = [field for field in self.request.GET.get("fields", "").split(",") if field] |
| 272 | + return fields if fields else None |
| 273 | + |
| 274 | + @property |
| 275 | + def expand(self): |
| 276 | + expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand] |
| 277 | + return expand if expand else None |
0 commit comments