Skip to content

feature: DJango Ninja and drf cleanup #1646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions backend/apps/github/api/issue.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should add /v1/ for consistent versioning approach w/ API urls (/api/v1)

Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
"""Issue API."""

from rest_framework import serializers, viewsets
from datetime import datetime

from django.http import HttpRequest
from ninja import Router
from pydantic import BaseModel

from apps.github.models.issue import Issue

router = Router()


# Serializers define the API representation.
class IssueSerializer(serializers.HyperlinkedModelSerializer):
"""Issue serializer."""
class IssueSchema(BaseModel):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why base on this instead of ninja::Schema?

"""Schema for Issue."""

class Meta:
model = Issue
fields = (
"title",
"body",
"state",
"url",
"created_at",
"updated_at",
)
model_config = {"from_attributes": True}

body: str
created_at: datetime
title: str
state: str
updated_at: datetime
url: str

# ViewSets define the view behavior.
class IssueViewSet(viewsets.ReadOnlyModelViewSet):
"""Issue view set."""

queryset = Issue.objects.all()
serializer_class = IssueSerializer
@router.get("/", response=list[IssueSchema])
def get_issue(request: HttpRequest) -> list[IssueSchema]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be plural and probably list_issues.

"""Get all issues."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Get all issues."""
"""Get issues."""

return Issue.objects.all()
31 changes: 15 additions & 16 deletions backend/apps/github/api/label.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
"""Label API."""

from rest_framework import serializers, viewsets
from django.http import HttpRequest
from ninja import Router
from pydantic import BaseModel

from apps.github.models.label import Label

router = Router()

# Serializers define the API representation.
class LabelSerializer(serializers.HyperlinkedModelSerializer):
"""Label serializer."""

class Meta:
model = Label
fields = (
"name",
"description",
"color",
)
class LabelSchema(BaseModel):
"""Schema for Label."""

model_config = {"from_attributes": True}

# ViewSets define the view behavior.
class LabelViewSet(viewsets.ReadOnlyModelViewSet):
"""Label view set."""
color: str
description: str
name: str

queryset = Label.objects.all()
serializer_class = LabelSerializer

@router.get("/", response=list[LabelSchema])
def get_label(request: HttpRequest) -> list[LabelSchema]:
"""Get all labels."""
return Label.objects.all()
39 changes: 20 additions & 19 deletions backend/apps/github/api/organization.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
"""Organization API."""

from rest_framework import serializers, viewsets
from datetime import datetime

from django.http import HttpRequest
from ninja import Router
from pydantic import BaseModel

from apps.github.models.organization import Organization

router = Router()


# Serializers define the API representation.
class OrganizationSerializer(serializers.HyperlinkedModelSerializer):
"""Organization serializer."""
class OrganizationSchema(BaseModel):
"""Schema for Organization."""

class Meta:
model = Organization
fields = (
"name",
"login",
"company",
"location",
"created_at",
"updated_at",
)
model_config = {"from_attributes": True}

company: str
created_at: datetime
location: str
login: str
name: str
updated_at: datetime

# ViewSets define the view behavior.
class OrganizationViewSet(viewsets.ReadOnlyModelViewSet):
"""Organization view set."""

queryset = Organization.objects.filter(is_owasp_related_organization=True)
serializer_class = OrganizationSerializer
@router.get("/", response=list[OrganizationSchema])
def get_organization(request: HttpRequest) -> list[OrganizationSchema]:
"""Get all organizations."""
return Organization.objects.filter(is_owasp_related_organization=True)
37 changes: 19 additions & 18 deletions backend/apps/github/api/release.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
"""Release API."""

from rest_framework import serializers, viewsets
from datetime import datetime

from django.http import HttpRequest
from ninja import Router
from pydantic import BaseModel

from apps.github.models.release import Release

router = Router()


# Serializers define the API representation.
class ReleaseSerializer(serializers.HyperlinkedModelSerializer):
"""Release serializer."""
class ReleaseSchema(BaseModel):
"""Schema for Release."""

class Meta:
model = Release
fields = (
"name",
"tag_name",
"description",
"created_at",
"published_at",
)
model_config = {"from_attributes": True}

created_at: datetime
description: str
name: str
published_at: datetime
tag_name: str

# ViewSets define the view behavior.
class ReleaseViewSet(viewsets.ReadOnlyModelViewSet):
"""Release view set."""

queryset = Release.objects.all()
serializer_class = ReleaseSerializer
@router.get("/", response=list[ReleaseSchema])
def get_release(request: HttpRequest) -> list[ReleaseSchema]:
"""Get all releases."""
return Release.objects.all()
35 changes: 18 additions & 17 deletions backend/apps/github/api/repository.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
"""Repository API."""

from rest_framework import serializers, viewsets
from datetime import datetime

from django.http import HttpRequest
from ninja import Router
from pydantic import BaseModel

from apps.github.models.repository import Repository

router = Router()


# Serializers define the API representation.
class RepositorySerializer(serializers.HyperlinkedModelSerializer):
"""Repository serializer."""
class RepositorySchema(BaseModel):
"""Schema for Repository."""

class Meta:
model = Repository
fields = (
"name",
"description",
"created_at",
"updated_at",
)
model_config = {"from_attributes": True}

created_at: datetime
description: str
name: str
updated_at: datetime

# ViewSets define the view behavior.
class RepositoryViewSet(viewsets.ReadOnlyModelViewSet):
"""Repository view set."""

queryset = Repository.objects.all()
serializer_class = RepositorySerializer
@router.get("/", response=list[RepositorySchema])
def get_repository(request: HttpRequest) -> list[RepositorySchema]:
"""Get all repositories."""
return Repository.objects.all()
28 changes: 14 additions & 14 deletions backend/apps/github/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""GitHub API URLs."""

from rest_framework import routers
from ninja import Router

from apps.github.api.issue import IssueViewSet
from apps.github.api.label import LabelViewSet
from apps.github.api.organization import OrganizationViewSet
from apps.github.api.release import ReleaseViewSet
from apps.github.api.repository import RepositoryViewSet
from apps.github.api.user import UserViewSet
from apps.github.api.issue import router as issue_router
from apps.github.api.label import router as label_router
from apps.github.api.organization import router as organization_router
from apps.github.api.release import router as release_router
from apps.github.api.repository import router as repository_router
from apps.github.api.user import router as user_router

router = routers.SimpleRouter()
router = Router()

router.register(r"github/issues", IssueViewSet)
router.register(r"github/labels", LabelViewSet)
router.register(r"github/organizations", OrganizationViewSet)
router.register(r"github/releases", ReleaseViewSet)
router.register(r"github/repositories", RepositoryViewSet)
router.register(r"github/users", UserViewSet)
router.add_router(r"/issues", issue_router)
router.add_router(r"/labels", label_router)
router.add_router(r"/organizations", organization_router)
router.add_router(r"/releases", release_router)
router.add_router(r"/repositories", repository_router)
router.add_router(r"/users", user_router)
97 changes: 43 additions & 54 deletions backend/apps/github/api/user.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,49 @@
"""User API."""

from rest_framework import serializers, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from datetime import datetime

from django.http import HttpRequest
from ninja import Router
from ninja.responses import Response
from pydantic import BaseModel

from apps.github.models.user import User

router = Router()


class UserSchema(BaseModel):
"""Schema for User."""

model_config = {"from_attributes": True}

avatar_url: str
bio: str
company: str
created_at: datetime
email: str
followers_count: int
following_count: int
location: str
login: str
name: str
public_repositories_count: int
title: str
twitter_username: str
updated_at: datetime
url: str


@router.get("/", response=list[UserSchema])
def list_users(request: HttpRequest) -> list[UserSchema]:
"""Get all users."""
return User.objects.all()


# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
"""User serializer."""

class Meta:
model = User
fields = (
"avatar_url",
"bio",
"company",
"email",
"followers_count",
"following_count",
"location",
"login",
"name",
"public_repositories_count",
"title",
"twitter_username",
"url",
"created_at",
"updated_at",
)


# ViewSets define the view behavior.
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""User view set."""

queryset = User.objects.all()
serializer_class = UserSerializer

@action(detail=False, methods=["get"], url_path="login/(?P<login>[^/.]+)")
def get_user_by_login(self, request, login=None):
"""Retrieve a user by their login.

Args:
request (Request): The HTTP request object.
login (str, optional): The login of the user to retrieve.

Returns:
Response: The serialized user data or a 404 error if the user is not found.

"""
try:
user = User.objects.get(login=login)
serializer = self.get_serializer(user)
data = serializer.data
return Response(data)
except User.DoesNotExist:
return Response({"detail": "User not found."}, status=404)
@router.get("/{login}", response=UserSchema)
def get_user(request: HttpRequest, login: str) -> UserSchema | None:
"""Get user by login."""
try:
return User.objects.get(login=login)
except User.DoesNotExist:
return Response({"detail": "User not found."}, status=200)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is generally 404, something like

from ninja.errors import HttpError

@router.get("/{login}", response={200: UserSchema, 404: dict})
def get_user(request: HttpRequest, login: str):
    user = User.objects.filter(login=login).first()
    if not user:
        raise HttpError(404, "User not found")
    return user

Loading