Skip to content

Commit

Permalink
Merge pull request #35 from OVINC-CN/feat_asgi
Browse files Browse the repository at this point in the history
feat: async views
  • Loading branch information
OrenZhang authored Jun 8, 2024
2 parents 4082419 + 1ef4790 commit a14321a
Show file tree
Hide file tree
Showing 23 changed files with 164 additions and 83 deletions.
15 changes: 15 additions & 0 deletions .cruft.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"template": "https://github.com/OVINC-CN/DevTemplateDjango.git",
"commit": "874fbc19e85ff3972333c2742f17266880c1a233",
"checkout": "main",
"context": {
"cookiecutter": {
"project_name": "iwiki-api",
"_copy_without_render": [
".github"
],
"_template": "https://github.com/OVINC-CN/DevTemplateDjango.git"
}
},
"directory": null
}
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ jobs:
pip install -r requirements.txt
- name: Analysing the code with pylint
run: |
pylint --disable=C0114,W0613,C0115,W1113,W0223,C0116,R0903,R0901 --max-line-length=120 $(git ls-files '*.py')
pylint --disable=C0114,W0613,C0115,W1113,W0223,C0116,R0903,R0901,W0236 --max-line-length=120 $(git ls-files '*.py')
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM python:3.10
RUN mkdir -p /usr/src/app/logs /usr/src/app/celery-logs
RUN mkdir -p /usr/src/app/logs
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN pip3 install -U pip -i https://mirrors.cloud.tencent.com/pypi/simple \
Expand Down
13 changes: 8 additions & 5 deletions apps/cos/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import traceback

from channels.db import database_sync_to_async
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import InMemoryUploadedFile
Expand All @@ -21,11 +22,13 @@ class STSClient:
"""

@classmethod
def generate_cos_upload_credential(cls, user: USER_MODEL, filename: str) -> COSCredential:
async def generate_cos_upload_credential(cls, user: USER_MODEL, filename: str) -> COSCredential:
try:
cos_log = COSLog.objects.create(filename=filename, key=COSLog.build_key(filename), resp={}, owner=user)
cos_log = await database_sync_to_async(COSLog.objects.create)(
filename=filename, key=COSLog.build_key(filename), resp={}, owner=user
)
except IntegrityError:
return cls.generate_cos_upload_credential(user=user, filename=filename)
return await cls.generate_cos_upload_credential(user=user, filename=filename)
tencent_cloud_api_domain = settings.QCLOUD_API_DOMAIN_TMPL.format("sts")
config = {
"domain": tencent_cloud_api_domain,
Expand Down Expand Up @@ -59,7 +62,7 @@ def generate_cos_upload_credential(cls, user: USER_MODEL, filename: str) -> COSC
raise TempKeyGenerateFailed() from err
finally:
cos_log.resp = response
cos_log.save(update_fields=["resp"])
await database_sync_to_async(cos_log.save)(update_fields=["resp"])


class COSClient:
Expand All @@ -74,7 +77,7 @@ def __init__(self, user: USER_MODEL = None):
)
self.client = CosS3Client(self.config)

def upload(self, file: InMemoryUploadedFile, path: str, *args, **kwargs) -> None:
async def upload(self, file: InMemoryUploadedFile, path: str, *args, **kwargs) -> None:
try:
resp = self.client.put_object(Bucket=settings.QCLOUD_COS_BUCKET, Body=file, Key=path, *args, **kwargs)
except Exception as err:
Expand Down
7 changes: 5 additions & 2 deletions apps/cos/permissions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from channels.db import database_sync_to_async
from rest_framework.permissions import BasePermission

from apps.permission.constants import PermissionItem
Expand All @@ -9,5 +10,7 @@ class UploadFilePermission(BasePermission):
Upload File Permission
"""

def has_permission(self, request, view):
return UserPermission.check_permission(user=request.user, permission_item=PermissionItem.UPLOAD_FILE)
async def has_permission(self, request, view):
return await database_sync_to_async(UserPermission.check_permission)(
user=request.user, permission_item=PermissionItem.UPLOAD_FILE
)
3 changes: 2 additions & 1 deletion apps/cos/serializers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from adrf.serializers import Serializer
from django.utils.translation import gettext, gettext_lazy
from rest_framework import serializers


class GenerateTempSecretSerializer(serializers.Serializer):
class GenerateTempSecretSerializer(Serializer):
"""
Temp Secret
"""
Expand Down
8 changes: 4 additions & 4 deletions apps/cos/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import asdict

from django.conf import settings
from ovinc_client.core.viewsets import CreateMixin, MainViewSet
from ovinc_client.core.viewsets import MainViewSet
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
Expand All @@ -13,7 +13,7 @@
from apps.cos.serializers import GenerateTempSecretSerializer


class COSViewSet(CreateMixin, MainViewSet):
class COSViewSet(MainViewSet):
"""
COS
"""
Expand All @@ -22,7 +22,7 @@ class COSViewSet(CreateMixin, MainViewSet):
permission_classes = [UploadFilePermission]

@action(methods=["POST"], detail=False)
def temp_secret(self, request: Request, *args, **kwargs):
async def temp_secret(self, request: Request, *args, **kwargs):
"""
Generate New Temp Secret for COS
"""
Expand All @@ -37,5 +37,5 @@ def temp_secret(self, request: Request, *args, **kwargs):
request_data = serializer.validated_data

# generate
data = STSClient.generate_cos_upload_credential(user=request.user, filename=request_data["filename"])
data = await STSClient.generate_cos_upload_credential(user=request.user, filename=request_data["filename"])
return Response(asdict(data))
14 changes: 10 additions & 4 deletions apps/doc/permissions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from channels.db import database_sync_to_async
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from rest_framework.viewsets import GenericViewSet
Expand All @@ -12,10 +13,13 @@ class DocOwnerPermission(BasePermission):
Owner Permission
"""

def has_permission(self, request: Request, view: GenericViewSet):
async def has_permission(self, request: Request, view: GenericViewSet):
return True

def has_object_permission(self, request: Request, view: GenericViewSet, obj: Doc):
async def has_object_permission(self, request: Request, view: GenericViewSet, obj: Doc):
return await database_sync_to_async(self._has_object_permission)(request, view, obj)

def _has_object_permission(self, request: Request, view: GenericViewSet, obj: Doc):
# Retrieve Public Inst is Allowed
if view.action == "retrieve" and obj.is_public:
return True
Expand All @@ -28,5 +32,7 @@ class CreateDocPermission(BasePermission):
Create Doc Permission
"""

def has_permission(self, request, view):
return UserPermission.check_permission(user=request.user, permission_item=PermissionItem.CREATE_DOC)
async def has_permission(self, request, view):
return await database_sync_to_async(UserPermission.check_permission)(
user=request.user, permission_item=PermissionItem.CREATE_DOC
)
14 changes: 10 additions & 4 deletions apps/doc/serializers/doc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import List

from adrf.serializers import ModelSerializer, Serializer
from django.utils.translation import gettext_lazy
from ovinc_client.core.constants import SHORT_CHAR_LENGTH
from rest_framework import serializers
Expand All @@ -8,16 +9,21 @@
from apps.doc.serializers.tag import TagInfoSerializer


class DocListSerializer(serializers.ModelSerializer):
class DocListSerializer(ModelSerializer):
"""
Doc List
"""

class TagsSerializerField(serializers.SerializerMethodField):
async def ato_representation(self, val):
return super().to_representation(val)

owner_nick_name = serializers.CharField(
label=gettext_lazy("Owner Nick Name"), read_only=True, source="owner.nick_name"
)
comments = serializers.IntegerField(label=gettext_lazy("Comment Count"), read_only=True, source="comment_set.count")
tags = serializers.SerializerMethodField(label=gettext_lazy("Tags"), read_only=True)
tags = TagsSerializerField(label=gettext_lazy("Tags"), read_only=True)
owner = serializers.CharField(label=gettext_lazy("Owner"), read_only=True)

class Meta:
model = Doc
Expand All @@ -39,7 +45,7 @@ class Meta:
fields = "__all__"


class EditDocSerializer(serializers.ModelSerializer):
class EditDocSerializer(ModelSerializer):
"""
Edit Doc
"""
Expand All @@ -55,7 +61,7 @@ class Meta:
fields = ["title", "content", "header_img", "is_public", "tags", "created_at"]


class DocSearchSerializer(serializers.Serializer):
class DocSearchSerializer(Serializer):
"""
Search Doc
"""
Expand Down
4 changes: 2 additions & 2 deletions apps/doc/serializers/tag.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from rest_framework import serializers
from adrf.serializers import ModelSerializer

from apps.doc.models import Tag


class TagInfoSerializer(serializers.ModelSerializer):
class TagInfoSerializer(ModelSerializer):
"""
Tag Info
"""
Expand Down
58 changes: 44 additions & 14 deletions apps/doc/views/doc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from channels.db import database_sync_to_async
from django.conf import settings
from django.db import transaction
from django.db.models import Q
from django.db.models import Q, QuerySet
from django.utils import timezone
from ovinc_client.core.auth import SessionAuthenticate
from ovinc_client.core.paginations import NumPagination
Expand Down Expand Up @@ -47,7 +48,7 @@ def get_permissions(self):
return permissions

# pylint: disable=R0914
def list(self, request: Request, *args, **kwargs):
async def list(self, request: Request, *args, **kwargs):
"""
Doc List
"""
Expand Down Expand Up @@ -99,24 +100,33 @@ def list(self, request: Request, *args, **kwargs):

# Page
page = NumPagination()
queryset = page.paginate_queryset(queryset=queryset, request=request, view=self)
queryset = await database_sync_to_async(page.paginate_queryset)(queryset=queryset, request=request, view=self)

# Serialize
serializer = DocListSerializer(instance=queryset, many=True)
return page.get_paginated_response(serializer.data)
return page.get_paginated_response(await serializer.adata)

def retrieve(self, request: Request, *args, **kwargs):
async def retrieve(self, request: Request, *args, **kwargs):
"""
Doc Info
"""

inst: QuerySet = await database_sync_to_async(self.get_and_incr_read)()
serializer = DocInfoSerializer(instance=inst, many=True)
data = await serializer.adata
return Response(data[0])

def get_and_incr_read(self) -> QuerySet:
inst: Doc = self.get_object()
inst.record_read()
inst.refresh_from_db()
return Response(DocInfoSerializer(instance=inst).data)
return (
Doc.objects.filter(pk=inst.pk)
.prefetch_related("owner")
.prefetch_related("comment_set")
.prefetch_related("doctag_set__tag")
)

@transaction.atomic()
def create(self, request: Request, *args, **kwargs):
async def create(self, request: Request, *args, **kwargs):
"""
Create Doc
"""
Expand All @@ -126,6 +136,13 @@ def create(self, request: Request, *args, **kwargs):
serializer.is_valid(raise_exception=True)
request_data = serializer.validated_data

# Create
doc = await database_sync_to_async(self.create_and_bind_tag)(request, request_data)

return Response({"id": doc.id})

@transaction.atomic()
def create_and_bind_tag(self, request: Request, request_data: dict) -> Doc:
# Create Doc
doc = Doc.objects.create(
title=request_data["title"],
Expand All @@ -140,22 +157,28 @@ def create(self, request: Request, *args, **kwargs):
# Create Doc Tag Relation
doc.bind_tags(tags=request_data["tags"])

return Response({"id": doc.id})
return doc

@transaction.atomic()
def update(self, request: Request, *args, **kwargs):
async def update(self, request: Request, *args, **kwargs):
"""
Update Doc
"""

# Load Inst
inst: Doc = self.get_object()
inst: Doc = await database_sync_to_async(self.get_object)()

# Validate
serializer = EditDocSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
request_data = serializer.validated_data

# Update
await database_sync_to_async(self.update_and_bind_tag)(inst, request_data)

return Response({"id": inst.id})

@transaction.atomic()
def update_and_bind_tag(self, inst: Doc, request_data):
# Update Tag
inst.bind_tags(request_data.pop("tags", []))

Expand All @@ -165,4 +188,11 @@ def update(self, request: Request, *args, **kwargs):
inst.updated_at = timezone.now()
inst.save(update_fields=[*request_data.keys(), "updated_at"])

return Response({"id": inst.id})
async def destroy(self, request, *args, **kwargs):
"""
Delete Doc
"""

inst = await database_sync_to_async(self.get_object)()
await database_sync_to_async(inst.delete)()
return Response()
20 changes: 14 additions & 6 deletions apps/doc/views/tag.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from channels.db import database_sync_to_async
from django.db.models import QuerySet
from ovinc_client.core.auth import SessionAuthenticate
from ovinc_client.core.viewsets import ListMixin, MainViewSet
from rest_framework.decorators import action
Expand All @@ -16,21 +18,27 @@ class TagViewSet(ListMixin, MainViewSet):
queryset = Tag.objects.all()
authentication_classes = [SessionAuthenticate]

def list(self, request: Request, *args, **kwargs):
async def list(self, request: Request, *args, **kwargs):
"""
Tag List
"""

tags = Tag.objects.all().order_by("name")
tags = await database_sync_to_async(self.list_tags)()
serializer = TagInfoSerializer(instance=tags, many=True)
return Response(data=serializer.data)
return Response(data=await serializer.adata)

def list_tags(self) -> QuerySet:
return Tag.objects.all().order_by("name")

@action(methods=["GET"], detail=False)
def bound(self, request: Request, *args, **kwargs):
async def bound(self, request: Request, *args, **kwargs):
"""
Bound Tags
"""

tags = Tag.objects.filter(id__in=DocTag.objects.all().values("tag_id")).order_by("name")
tags = await database_sync_to_async(self.filter_tags)()
serializer = TagInfoSerializer(instance=tags, many=True)
return Response(data=serializer.data)
return Response(data=await serializer.adata)

def filter_tags(self) -> QuerySet:
return Tag.objects.filter(id__in=DocTag.objects.all().values("tag_id")).order_by("name")
3 changes: 2 additions & 1 deletion apps/home/serializers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from adrf.serializers import Serializer
from django.utils.translation import check_for_language, gettext_lazy
from rest_framework import serializers

from apps.home.exceptions import LanguageCodeInvalid


class I18nRequestSerializer(serializers.Serializer):
class I18nRequestSerializer(Serializer):
"""
I18n
"""
Expand Down
Loading

0 comments on commit a14321a

Please sign in to comment.