Browse Source

Merge branch 'master' of gitlab.com:glitchtip/glitchtip-backend into ninja-email

David Burke 9 months ago
parent
commit
28de726cfb
7 changed files with 82 additions and 48 deletions
  1. 35 1
      apps/users/api.py
  2. 6 0
      apps/users/schema.py
  3. 0 8
      apps/users/serializers.py
  4. 4 4
      apps/users/tests/test_api.py
  5. 12 13
      apps/users/views.py
  6. 24 21
      poetry.lock
  7. 1 1
      pyproject.toml

+ 35 - 1
apps/users/api.py

@@ -11,7 +11,13 @@ from glitchtip.api.authentication import AuthHttpRequest
 from glitchtip.api.pagination import paginate
 
 from .models import User
-from .schema import EmailAddressIn, EmailAddressSchema, UserIn, UserSchema
+from .schema import (
+    EmailAddressIn,
+    EmailAddressSchema,
+    UserIn,
+    UserSchema,
+    UserNotificationsSchema,
+)
 
 router = Router()
 
@@ -27,6 +33,8 @@ GET /users/<me_id>/emails/
 POST /users/<me_id>/emails/
 PUT /users/<me_id>/emails/ (Set as primary)
 DELETE /users/<me_id>/emails/
+GET /users/<me_id>/notifications/
+PUT /users/<me_id>/notifications/
 """
 
 
@@ -171,3 +179,29 @@ async def delete_email(
     if result:
         return 204, None
     raise Http404
+
+
+@router.get(
+    "/users/{slug:user_id}/notifications/",
+    response=UserNotificationsSchema,
+    by_alias=True,
+)
+async def get_notifications(request: AuthHttpRequest, user_id: MeID):
+    user_id = request.auth.user_id
+    return await aget_object_or_404(get_user_queryset(user_id))
+
+
+@router.put(
+    "/users/{slug:user_id}/notifications/",
+    response=UserNotificationsSchema,
+    by_alias=True,
+)
+async def update_notifications(
+    request: AuthHttpRequest, user_id: MeID, payload: UserNotificationsSchema
+):
+    user_id = request.auth.user_id
+    user = await aget_object_or_404(get_user_queryset(user_id))
+    for attr, value in payload.dict().items():
+        setattr(user, attr, value)
+    await user.asave()
+    return user

+ 6 - 0
apps/users/schema.py

@@ -81,3 +81,9 @@ class EmailAddressSchema(CamelSchema, ModelSchema):
 
     class Meta(EmailAddressIn.Meta):
         pass
+
+
+class UserNotificationsSchema(CamelSchema, ModelSchema):
+    class Meta:
+        model = User
+        fields = ("subscribe_by_default",)

+ 0 - 8
apps/users/serializers.py

@@ -195,14 +195,6 @@ class RegisterSerializer(BaseRegisterSerializer):
             user.save(update_fields=["analytics"])
 
 
-class UserNotificationsSerializer(serializers.ModelSerializer):
-    subscribeByDefault = serializers.BooleanField(source="subscribe_by_default")
-
-    class Meta:
-        model = User
-        fields = ("subscribeByDefault",)
-
-
 class NoopTokenSerializer(serializers.Serializer):
     """dj-rest-auth requires tokens, but we don't use them."""
 

+ 4 - 4
apps/users/tests/test_api.py

@@ -226,15 +226,15 @@ class UsersTestCase(GlitchTipTestCase):
         )
 
     def test_notifications_retrieve(self):
-        url = reverse("user-detail", args=["me"]) + "notifications/"
+        url = reverse("api:get_notifications", args=["me"])
         res = self.client.get(url)
         self.assertContains(res, "subscribeByDefault")
 
     def test_notifications_update(self):
-        url = reverse("user-detail", args=["me"]) + "notifications/"
+        url = reverse("api:update_notifications", args=["me"])
         data = {"subscribeByDefault": False}
-        res = self.client.put(url, data)
-        self.assertFalse(res.data.get("subscribeByDefault"))
+        res = self.client.put(url, data, format="json")
+        self.assertFalse(res.json().get("subscribeByDefault"))
         self.user.refresh_from_db()
         self.assertFalse(self.user.subscribe_by_default)
 

+ 12 - 13
apps/users/views.py

@@ -7,7 +7,6 @@ from apps.projects.models import UserProjectAlert
 from .models import User
 from .serializers import (
     CurrentUserSerializer,
-    UserNotificationsSerializer,
     UserSerializer,
 )
 
@@ -38,20 +37,20 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
             return CurrentUserSerializer
         return super().get_serializer_class()
 
-    @action(detail=True, methods=["get", "post", "put"])
-    def notifications(self, request, pk=None):
-        user = self.get_object()
+    # @action(detail=True, methods=["get", "post", "put"])
+    # def notifications(self, request, pk=None):
+    #     user = self.get_object()
 
-        if request.method == "GET":
-            serializer = UserNotificationsSerializer(user)
-            return Response(serializer.data)
+    #     if request.method == "GET":
+    #         serializer = UserNotificationsSerializer(user)
+    #         return Response(serializer.data)
 
-        serializer = UserNotificationsSerializer(user, data=request.data)
-        if serializer.is_valid():
-            serializer.save()
-            return Response(serializer.data)
-        else:
-            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+    #     serializer = UserNotificationsSerializer(user, data=request.data)
+    #     if serializer.is_valid():
+    #         serializer.save()
+    #         return Response(serializer.data)
+    #     else:
+    #         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 
     @action(
         detail=True, methods=["get", "post", "put"], url_path="notifications/alerts"

+ 24 - 21
poetry.lock

@@ -288,17 +288,17 @@ files = [
 
 [[package]]
 name = "boto3"
-version = "1.34.121"
+version = "1.34.122"
 description = "The AWS SDK for Python"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "boto3-1.34.121-py3-none-any.whl", hash = "sha256:4e79e400d6d44b4eee5deda6ac0ecd08a3f5a30c45a0d30712795cdc4459fd79"},
-    {file = "boto3-1.34.121.tar.gz", hash = "sha256:ec89f3e0b0dc959c418df29e14d3748c0b05ab7acf7c0b90c839e9f340a659fa"},
+    {file = "boto3-1.34.122-py3-none-any.whl", hash = "sha256:b2d7400ff84fa547e53b3d9acfa3c95d65d45b5886ba1ede1f7df4768d1cc0b1"},
+    {file = "boto3-1.34.122.tar.gz", hash = "sha256:56840d8ce91654d182f1c113f0791fa2113c3aa43230c50b4481f235348a6037"},
 ]
 
 [package.dependencies]
-botocore = ">=1.34.121,<1.35.0"
+botocore = ">=1.34.122,<1.35.0"
 jmespath = ">=0.7.1,<2.0.0"
 s3transfer = ">=0.10.0,<0.11.0"
 
@@ -307,13 +307,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
 
 [[package]]
 name = "botocore"
-version = "1.34.121"
+version = "1.34.122"
 description = "Low-level, data-driven core of boto 3."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "botocore-1.34.121-py3-none-any.whl", hash = "sha256:25b05c7646a9f240cde1c8f839552a43f27e71e15c42600275dea93e219f7dd9"},
-    {file = "botocore-1.34.121.tar.gz", hash = "sha256:1a8f94b917c47dfd84a0b531ab607dc53570efb0d073d8686600f2d2be985323"},
+    {file = "botocore-1.34.122-py3-none-any.whl", hash = "sha256:6d75df3af831b62f0c7baa109728d987e0a8d34bfadf0476eb32e2f29a079a36"},
+    {file = "botocore-1.34.122.tar.gz", hash = "sha256:9374e16a36f1062c3e27816e8599b53eba99315dfac71cc84fc3aee3f5d3cbe3"},
 ]
 
 [package.dependencies]
@@ -2436,13 +2436,13 @@ test = ["coverage", "pytest", "pytest-cov"]
 
 [[package]]
 name = "locust"
-version = "2.28.0"
+version = "2.29.0"
 description = "Developer-friendly load testing framework"
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "locust-2.28.0-py3-none-any.whl", hash = "sha256:766be879db030c0118e7d9fca712f3538c4e628bdebf59468fa1c6c2fab217d3"},
-    {file = "locust-2.28.0.tar.gz", hash = "sha256:260557eec866f7e34a767b6c916b5b278167562a280480aadb88f43d962fbdeb"},
+    {file = "locust-2.29.0-py3-none-any.whl", hash = "sha256:aa9d94d3604ed9f2aab3248460d91e55d3de980a821dffdf8658b439b049d03f"},
+    {file = "locust-2.29.0.tar.gz", hash = "sha256:649c99ce49d00720a3084c0109547035ad9021222835386599a8b545d31ebe51"},
 ]
 
 [package.dependencies]
@@ -2456,7 +2456,10 @@ msgpack = ">=1.0.0"
 psutil = ">=5.9.1"
 pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
 pyzmq = ">=25.0.0"
-requests = ">=2.26.0"
+requests = [
+    {version = ">=2.32.2", markers = "python_version > \"3.11\""},
+    {version = ">=2.26.0", markers = "python_version <= \"3.11\""},
+]
 tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
 Werkzeug = ">=2.0.0"
 
@@ -3729,13 +3732,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
 
 [[package]]
 name = "sentry-sdk"
-version = "2.5.0"
+version = "2.5.1"
 description = "Python client for Sentry (https://sentry.io)"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "sentry_sdk-2.5.0-py2.py3-none-any.whl", hash = "sha256:75d2e1fd8a887fdf63df612f5d37991c248a080b886d80714560bdded2bb9051"},
-    {file = "sentry_sdk-2.5.0.tar.gz", hash = "sha256:05453f921c561b51159f712c2cd267b595e5c195b38b337d03baeb42719dd3c7"},
+    {file = "sentry_sdk-2.5.1-py2.py3-none-any.whl", hash = "sha256:1f87acdce4a43a523ae5aa21a3fc37522d73ebd9ec04b1dbf01aa3d173852def"},
+    {file = "sentry_sdk-2.5.1.tar.gz", hash = "sha256:fbc40a78a8a9c6675133031116144f0d0940376fa6e4e1acd5624c90b0aaf58b"},
 ]
 
 [package.dependencies]
@@ -3903,13 +3906,13 @@ files = [
 
 [[package]]
 name = "textual"
-version = "0.65.2"
+version = "0.66.0"
 description = "Modern Text User Interface framework"
 optional = false
 python-versions = "<4.0,>=3.8"
 files = [
-    {file = "textual-0.65.2-py3-none-any.whl", hash = "sha256:f01ce9ee3d2a613c63b9255900bf5a709b9ff7931d54d42f438e15495fa4ad3c"},
-    {file = "textual-0.65.2.tar.gz", hash = "sha256:05d4d39eedffac977b9bce9bea307dfefefdaaaa5081722549472cbb1d32e53f"},
+    {file = "textual-0.66.0-py3-none-any.whl", hash = "sha256:6088c6defc36afe6775a9a8707a52c54e6dadcde516d00b48902cc4d6946cbcd"},
+    {file = "textual-0.66.0.tar.gz", hash = "sha256:f2649a4a487bf939d2c78e10c06e3b9ce8562b978fbb07ba2ec8507006ea7aeb"},
 ]
 
 [package.dependencies]
@@ -3959,13 +3962,13 @@ files = [
 
 [[package]]
 name = "typing-extensions"
-version = "4.12.1"
+version = "4.12.2"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"},
-    {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"},
+    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
 ]
 
 [[package]]
@@ -4305,4 +4308,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.10"
-content-hash = "8baa659f84fd0d4075e708a988117166d8a040e0c4c37c9f955f8cd75a189afd"
+content-hash = "c77298552e971df83421340f81e218d101090f301a7b5ed2f735562f9dbf32fb"

+ 1 - 1
pyproject.toml

@@ -42,7 +42,7 @@ django-import-export = "~4.0.3"
 psycopg = {extras = ["c"], version = "^3.1.12"}
 uvicorn = "^0.30.0"
 gunicorn = "^22.0.0"
-boto3 = "1.34.121"
+boto3 = "1.34.122"
 django-ninja = "^1.0.1"
 orjson = "^3.9.9"
 celery-batches = "^0.9"