Skip to content

Commit

Permalink
Bumped python and django. Updated .django-app-template. (#564)
Browse files Browse the repository at this point in the history
* bumped python and django. Updated .django-app-template.

* set template file naming according to app_name

* add default viewsets

* Fix circle ci and Dockerfile update (#565)

1. Circle: update orb version, cause the previous one deprecated
2. Circle: explicitly set python 3.11 version to use for test and build
3. Dockerfile: update debian version to last one
4. Dockerfile: update UWSGI cause previous doesn't support python 3.11

---------

Co-authored-by: Nikolay Kiryanov <[email protected]>
  • Loading branch information
nvo87 and nkiryanov authored Nov 19, 2023
1 parent accb892 commit e6dbb90
Show file tree
Hide file tree
Showing 19 changed files with 210 additions and 22 deletions.
7 changes: 4 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
version: 2.1
orbs:
docker: circleci/docker@1.7.0
docker: circleci/docker@2.4.0

jobs:
build:
docker:
- image: cimg/python:3.10
- image: cimg/python:3.11

steps:
- checkout
Expand Down Expand Up @@ -34,7 +34,7 @@ jobs:

coverage:
docker:
- image: cimg/python:3.10
- image: cimg/python:3.11
steps:
- checkout
- attach_workspace:
Expand Down Expand Up @@ -73,5 +73,6 @@ workflows:
image: f213/django
path: testproject/django
docker-context: testproject/django
extra_build_args: '--build-arg PYTHON_VERSION=3.11'
deploy: false
attach-at: .
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/.python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10.8
3.11.6
6 changes: 3 additions & 3 deletions {{cookiecutter.project_slug}}/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG PYTHON_VERSION
FROM python:${PYTHON_VERSION}-slim-bullseye
FROM python:${PYTHON_VERSION}-slim-bookworm
LABEL maintainer="[email protected]"

LABEL com.datadoghq.ad.logs='[{"source": "uwsgi", "service": "django"}]'
Expand All @@ -9,9 +9,9 @@ ENV DEBIAN_FRONTEND noninteractive

ENV STATIC_ROOT /static

ENV _UWSGI_VERSION 2.0.20
ENV _UWSGI_VERSION 2.0.23

RUN echo deb http://deb.debian.org/debian bullseye contrib non-free > /etc/apt/sources.list.d/debian-contrib.list \
RUN echo deb http://deb.debian.org/debian bookworm contrib non-free > /etc/apt/sources.list.d/debian-contrib.list \
&& apt update \
&& apt --no-install-recommends install -y gettext locales-all wget \
imagemagick tzdata wait-for-it build-essential \
Expand Down
2 changes: 2 additions & 0 deletions {{cookiecutter.project_slug}}/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ test:
cd src && ./manage.py compilemessages
cd src && pytest --dead-fixtures
cd src && pytest -x

pr: fmt lint test
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "{{cookiecutter.project_slug}}"
version = "{{cookiecutter.project_version}}"
dependencies = [
"Django<3.3",
"Django<4.3",
"bcrypt",
"django-behaviors",
"django-environ",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = [
"",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from rest_framework import serializers

# Rename this file to singular form of your entity, e.g. "orders.py -> order.py". Add your class to __init__.py.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = [
"",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from app.api.viewsets import DefaultModelViewSet

# Rename this file to singular form of your entity, e.g. "orders.py -> order.py". Add your class to __init__.py.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = [
"",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.db import models

from app.models import DefaultModel, TimestampedModel

# Rename this file to singular form of your entity, e.g. "orders.py -> order.py". Add your class to __init__.py.
177 changes: 177 additions & 0 deletions {{cookiecutter.project_slug}}/src/app/api/viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
from typing import Any, Optional, Protocol, Type

from rest_framework import mixins
from rest_framework import status
from rest_framework.mixins import CreateModelMixin
from rest_framework.mixins import UpdateModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import BaseSerializer
from rest_framework.viewsets import GenericViewSet

__all__ = ["DefaultModelViewSet"]


class BaseGenericViewSet(Protocol):
def get_serializer(self, *args: Any, **kwargs: Any) -> Any:
...

def get_response(self, *args: Any, **kwargs: Any) -> Any:
...

def perform_create(self, *args: Any, **kwargs: Any) -> Any:
...

def perform_update(self, *args: Any, **kwargs: Any) -> Any:
...

def get_success_headers(self, *args: Any, **kwargs: Any) -> Any:
...

def get_serializer_class(self, *args: Any, **kwargs: Any) -> Any:
...

def get_object(self, *args: Any, **kwargs: Any) -> Any:
...


class DefaultCreateModelMixin(CreateModelMixin):
"""Return detail-serialized created instance"""

def create(self: BaseGenericViewSet, request: Request, *args: Any, **kwargs: Any) -> Response:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer) # No getting created instance in original DRF
headers = self.get_success_headers(serializer.data)
return self.get_response(instance, status.HTTP_201_CREATED, headers)

def perform_create(self: BaseGenericViewSet, serializer: Any) -> Any:
return serializer.save() # No returning created instance in original DRF


class DefaultUpdateModelMixin(UpdateModelMixin):
"""Return detail-serialized updated instance"""

def update(self: BaseGenericViewSet, request: Request, *args: Any, **kwargs: Any) -> Response:
partial = kwargs.pop("partial", False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
instance = self.perform_update(serializer) # No getting updated instance in original DRF

if getattr(instance, "_prefetched_objects_cache", None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}

return self.get_response(instance, status.HTTP_200_OK)

def perform_update(self: BaseGenericViewSet, serializer: Any) -> Any:
return serializer.save() # No returning updated instance in original DRF


class ResponseWithRetrieveSerializerMixin:
"""
Always response with 'retrieve' serializer or fallback to `serializer_class`.
Usage:
class MyViewSet(DefaultModelViewSet):
serializer_class = MyDefaultSerializer
serializer_action_classes = {
'list': MyListSerializer,
'my_action': MyActionSerializer,
}
@action
def my_action:
...
'my_action' request will be validated with MyActionSerializer,
but response will be serialized with MyDefaultSerializer
(or 'retrieve' if provided).
Thanks gonz: http://stackoverflow.com/a/22922156/11440
"""

def get_response(
self: BaseGenericViewSet,
instance: Any,
status: Any,
headers: Any = None,
) -> Response:
retrieve_serializer_class = self.get_serializer_class(action="retrieve")
context = self.get_serializer_context() # type: ignore
retrieve_serializer = retrieve_serializer_class(instance, context=context)
return Response(
retrieve_serializer.data,
status=status,
headers=headers,
)

def get_serializer_class(
self: BaseGenericViewSet,
action: Optional[str] = None,
) -> Type[BaseSerializer]:
if action is None:
action = self.action # type: ignore

try:
return self.serializer_action_classes[action] # type: ignore
except (KeyError, AttributeError):
return super().get_serializer_class() # type: ignore


class DefaultModelViewSet(
DefaultCreateModelMixin, # Create response is overriden
mixins.RetrieveModelMixin,
DefaultUpdateModelMixin, # Update response is overriden
mixins.DestroyModelMixin,
mixins.ListModelMixin,
ResponseWithRetrieveSerializerMixin, # Response with retrieve or default serializer
GenericViewSet,
):
pass


class ReadonlyModelViewSet(
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
ResponseWithRetrieveSerializerMixin, # Response with retrieve or default serializer
GenericViewSet,
):
pass


class ListOnlyModelViewSet(
mixins.ListModelMixin,
ResponseWithRetrieveSerializerMixin, # Response with retrieve or default serializer
GenericViewSet,
):
pass


class UpdateOnlyModelViewSet(
DefaultUpdateModelMixin,
ResponseWithRetrieveSerializerMixin,
GenericViewSet,
):
pass


class DefaultRetrieveDestroyListViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
ResponseWithRetrieveSerializerMixin, # Response with retrieve or default serializer
GenericViewSet,
):
pass


class ListUpdateModelViewSet(
DefaultUpdateModelMixin,
mixins.ListModelMixin,
ResponseWithRetrieveSerializerMixin,
GenericViewSet,
):
pass
4 changes: 2 additions & 2 deletions {{cookiecutter.project_slug}}/src/app/urls/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

app_name = "api_v1"
urlpatterns = [
path("auth/", include("a12n.urls")),
path("users/", include("users.urls")),
path("auth/", include("a12n.api.urls")),
path("users/", include("users.api.urls")),
path("healthchecks/", include("django_healthchecks.urls")),
path("docs/schema/", SpectacularAPIView.as_view(), name="schema"),
path("docs/swagger/", SpectacularSwaggerView.as_view(url_name="schema")),
Expand Down

0 comments on commit e6dbb90

Please sign in to comment.