Skip to content

Commit

Permalink
Merge branch 'main' into reset_password
Browse files Browse the repository at this point in the history
  • Loading branch information
faucomte97 committed Jan 31, 2024
2 parents df1a80c + ae3abdf commit 4bd0f5a
Show file tree
Hide file tree
Showing 20 changed files with 906 additions and 110 deletions.
35 changes: 35 additions & 0 deletions .vscode/extensions/autoDocstring/docstring.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{{! Based off of: https://github.com/NilsJPWerner/autoDocstring/blob/master/src/docstring/templates/google-notypes.mustache }}
{{summaryPlaceholder}}

{{extendedSummaryPlaceholder}}
{{#parametersExist}}

Args:
{{#args}}
{{var}}: {{descriptionPlaceholder}}
{{/args}}
{{#kwargs}}
{{var}}: {{descriptionPlaceholder}}
{{/kwargs}}
{{/parametersExist}}
{{#exceptionsExist}}

Raises:
{{#exceptions}}
{{type}}: {{descriptionPlaceholder}}
{{/exceptions}}
{{/exceptionsExist}}
{{#returnsExist}}

Returns:
{{#returns}}
{{descriptionPlaceholder}}
{{/returns}}
{{/returnsExist}}
{{#yieldsExist}}

Yields:
{{#yields}}
{{descriptionPlaceholder}}
{{/yields}}
{{/yieldsExist}}
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

<!--next-version-placeholder-->

## v0.11.0 (2024-01-30)

### Feature

* ModelSerializerTestCase ([#66](https://github.com/ocadotechnology/codeforlife-package-python/issues/66)) ([`b04f702`](https://github.com/ocadotechnology/codeforlife-package-python/commit/b04f702f2ed7dfb0f15fe2b90a92c65b836d5073))

## v0.10.1 (2024-01-30)

### Fix

* Various fixes for bulk logic and type hints ([#65](https://github.com/ocadotechnology/codeforlife-package-python/issues/65)) ([`00716d7`](https://github.com/ocadotechnology/codeforlife-package-python/commit/00716d79a30f840bc57ad34743fcea4cdf13fcb2))

## v0.10.0 (2024-01-26)

### Feature

* Support bulk actions ([#63](https://github.com/ocadotechnology/codeforlife-package-python/issues/63)) ([`4d2e7c4`](https://github.com/ocadotechnology/codeforlife-package-python/commit/4d2e7c407037714bc2041aa8a9fb4a8c6b1d2b76))

## v0.9.6 (2024-01-25)

### Fix
Expand Down
1 change: 1 addition & 0 deletions codeforlife/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
"""

from .base import *
from .model import *
42 changes: 29 additions & 13 deletions codeforlife/serializers/base.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
"""
© Ocado Group
Created on 20/01/2024 at 11:19:24(+00:00).
Created on 29/01/2024 at 14:27:09(+00:00).
Base model serializers.
Base serializer.
"""

import typing as t

from django.db.models import Model
from rest_framework.serializers import ModelSerializer as _ModelSerializer
from django.contrib.auth.models import AnonymousUser
from rest_framework.serializers import BaseSerializer as _BaseSerializer

AnyModel = t.TypeVar("AnyModel", bound=Model)
from ..request import Request
from ..user.models import User


class ModelSerializer(_ModelSerializer[AnyModel], t.Generic[AnyModel]):
"""Base model serializer for all model serializers."""
# pylint: disable-next=abstract-method
class BaseSerializer(_BaseSerializer):
"""Base serializer to be inherited by all other serializers."""

# pylint: disable-next=useless-parent-delegation
def update(self, instance, validated_data: t.Dict[str, t.Any]):
return super().update(instance, validated_data)
@property
def request(self):
"""The HTTP request that triggered the view."""

# pylint: disable-next=useless-parent-delegation
def create(self, validated_data: t.Dict[str, t.Any]):
return super().create(validated_data)
return t.cast(Request, self.context["request"])

@property
def request_user(self):
"""
The user that made the request. Assumes the user has authenticated.
"""

return t.cast(User, self.request.user)

@property
def request_anon_user(self):
"""
The user that made the request. Assumes the user has not authenticated.
"""

return t.cast(AnonymousUser, self.request.user)
143 changes: 143 additions & 0 deletions codeforlife/serializers/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""
© Ocado Group
Created on 20/01/2024 at 11:19:24(+00:00).
Base model serializers.
"""

import typing as t

from django.db.models import Model
from rest_framework.serializers import ListSerializer as _ListSerializer
from rest_framework.serializers import ModelSerializer as _ModelSerializer
from rest_framework.serializers import ValidationError as _ValidationError

from .base import BaseSerializer

AnyModel = t.TypeVar("AnyModel", bound=Model)


class ModelSerializer(
BaseSerializer,
_ModelSerializer[AnyModel],
t.Generic[AnyModel],
):
"""Base model serializer for all model serializers."""

# pylint: disable-next=useless-parent-delegation
def update(self, instance, validated_data: t.Dict[str, t.Any]):
return super().update(instance, validated_data)

# pylint: disable-next=useless-parent-delegation
def create(self, validated_data: t.Dict[str, t.Any]):
return super().create(validated_data)

def validate(self, attrs: t.Dict[str, t.Any]):
return attrs


class ModelListSerializer(
BaseSerializer,
t.Generic[AnyModel],
_ListSerializer[t.List[AnyModel]],
):
"""Base model list serializer for all model list serializers.
Inherit this class if you wish to custom handle bulk create and/or update.
class UserListSerializer(ModelListSerializer[User]):
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
class UserSerializer(ModelSerializer[User]):
class Meta:
model = User
list_serializer_class = UserListSerializer
"""

instance: t.List[AnyModel]
batch_size: t.Optional[int] = None

@classmethod
def get_model_class(cls) -> t.Type[AnyModel]:
"""Get the model view set's class.
Returns:
The model view set's class.
"""

# pylint: disable-next=no-member
return t.get_args(cls.__orig_bases__[0])[ # type: ignore[attr-defined]
0
]

def create(self, validated_data: t.List[t.Dict[str, t.Any]]):
"""Bulk create many instances of a model.
https://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-create
Args:
validated_data: The data used to create the models.
Returns:
The models.
"""

model_class = self.get_model_class()
return model_class.objects.bulk_create( # type: ignore[attr-defined]
objs=[model_class(**data) for data in validated_data],
batch_size=self.batch_size,
)

def update(
self,
instance: t.List[AnyModel],
validated_data: t.List[t.Dict[str, t.Any]],
):
"""Bulk update many instances of a model.
https://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-update
Args:
instance: The models to update.
validated_data: The field-value pairs to update for each model.
Returns:
The models.
"""

# Models and data must have equal length and be ordered the same!
for model, data in zip(instance, validated_data):
for field, value in data.items():
setattr(model, field, value)

model_class = self.get_model_class()
model_class.objects.bulk_update( # type: ignore[attr-defined]
objs=instance,
fields={field for data in validated_data for field in data.keys()},
batch_size=self.batch_size,
)

return instance

def validate(self, attrs: t.List[t.Dict[str, t.Any]]):
# If performing a bulk create.
if self.instance is None:
if len(attrs) == 0:
raise _ValidationError("Nothing to create.")

# Else, performing a bulk update.
else:
if len(attrs) == 0:
raise _ValidationError("Nothing to update.")
if len(attrs) != len(self.instance):
raise _ValidationError("Some models do not exist.")

return attrs

# pylint: disable-next=useless-parent-delegation,arguments-renamed
def to_representation(self, instance: t.List[AnyModel]):
return super().to_representation(instance)
1 change: 1 addition & 0 deletions codeforlife/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
"""

from .cron import CronTestCase
from .model_serializer import ModelSerializerTestCase
from .model_view_set import ModelViewSetTestCase
Loading

0 comments on commit 4bd0f5a

Please sign in to comment.