mirror of
https://github.com/HackSoftware/Django-Styleguide.git
synced 2024-11-24 18:43:46 +03:00
Apply prettier to the README
This commit is contained in:
parent
9c763eec2f
commit
791be866b6
276
README.md
276
README.md
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
**Table of contents:**
|
**Table of contents:**
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
@ -20,58 +19,58 @@
|
||||||
- [Why not?](#why-not)
|
- [Why not?](#why-not)
|
||||||
- [Cookie Cutter](#cookie-cutter)
|
- [Cookie Cutter](#cookie-cutter)
|
||||||
- [Models](#models)
|
- [Models](#models)
|
||||||
* [Base model](#base-model)
|
- [Base model](#base-model)
|
||||||
* [Validation - `clean` and `full_clean`](#validation---clean-and-full_clean)
|
- [Validation - `clean` and `full_clean`](#validation---clean-and-full_clean)
|
||||||
* [Validation - constraints](#validation---constraints)
|
- [Validation - constraints](#validation---constraints)
|
||||||
* [Properties](#properties)
|
- [Properties](#properties)
|
||||||
* [Methods](#methods)
|
- [Methods](#methods)
|
||||||
* [Testing](#testing)
|
- [Testing](#testing)
|
||||||
- [Services](#services)
|
- [Services](#services)
|
||||||
* [Example - function-based service](#example---function-based-service)
|
- [Example - function-based service](#example---function-based-service)
|
||||||
* [Example - class-based service](#example---class-based-service)
|
- [Example - class-based service](#example---class-based-service)
|
||||||
* [Naming convention](#naming-convention)
|
- [Naming convention](#naming-convention)
|
||||||
* [Modules](#modules)
|
- [Modules](#modules)
|
||||||
* [Selectors](#selectors)
|
- [Selectors](#selectors)
|
||||||
* [Testing](#testing-1)
|
- [Testing](#testing-1)
|
||||||
- [APIs & Serializers](#apis--serializers)
|
- [APIs & Serializers](#apis--serializers)
|
||||||
* [Naming convention](#naming-convention-1)
|
- [Naming convention](#naming-convention-1)
|
||||||
* [Class-based vs. Function-based](#class-based-vs-function-based)
|
- [Class-based vs. Function-based](#class-based-vs-function-based)
|
||||||
* [List APIs](#list-apis)
|
- [List APIs](#list-apis)
|
||||||
+ [Plain](#plain)
|
- [Plain](#plain)
|
||||||
+ [Filters + Pagination](#filters--pagination)
|
- [Filters + Pagination](#filters--pagination)
|
||||||
* [Detail API](#detail-api)
|
- [Detail API](#detail-api)
|
||||||
* [Create API](#create-api)
|
- [Create API](#create-api)
|
||||||
* [Update API](#update-api)
|
- [Update API](#update-api)
|
||||||
* [Fetching objects](#fetching-objects)
|
- [Fetching objects](#fetching-objects)
|
||||||
* [Nested serializers](#nested-serializers)
|
- [Nested serializers](#nested-serializers)
|
||||||
* [Advanced serialization](#advanced-serialization)
|
- [Advanced serialization](#advanced-serialization)
|
||||||
- [Urls](#urls)
|
- [Urls](#urls)
|
||||||
- [Settings](#settings)
|
- [Settings](#settings)
|
||||||
* [Prefixing environment variables with `DJANGO_`](#prefixing-environment-variables-with-django_)
|
- [Prefixing environment variables with `DJANGO_`](#prefixing-environment-variables-with-django_)
|
||||||
* [Integrations](#integrations)
|
- [Integrations](#integrations)
|
||||||
* [Reading from `.env`](#reading-from-env)
|
- [Reading from `.env`](#reading-from-env)
|
||||||
- [Errors & Exception Handling](#errors--exception-handling)
|
- [Errors & Exception Handling](#errors--exception-handling)
|
||||||
* [How exception handling works (in the context of DRF)](#how-exception-handling-works-in-the-context-of-drf)
|
- [How exception handling works (in the context of DRF)](#how-exception-handling-works-in-the-context-of-drf)
|
||||||
+ [DRF's `ValidationError`](#drfs-validationerror)
|
- [DRF's `ValidationError`](#drfs-validationerror)
|
||||||
+ [Django's `ValidationError`](#djangos-validationerror)
|
- [Django's `ValidationError`](#djangos-validationerror)
|
||||||
* [Describe how your API errors are going to look like.](#describe-how-your-api-errors-are-going-to-look-like)
|
- [Describe how your API errors are going to look like.](#describe-how-your-api-errors-are-going-to-look-like)
|
||||||
* [Know how to change the default exception handling behavior.](#know-how-to-change-the-default-exception-handling-behavior)
|
- [Know how to change the default exception handling behavior.](#know-how-to-change-the-default-exception-handling-behavior)
|
||||||
* [Approach 1 - Use DRF's default exceptions, with very little modifications.](#approach-1---use-drfs-default-exceptions-with-very-little-modifications)
|
- [Approach 1 - Use DRF's default exceptions, with very little modifications.](#approach-1---use-drfs-default-exceptions-with-very-little-modifications)
|
||||||
* [Approach 2 - HackSoft's proposed way](#approach-2---hacksofts-proposed-way)
|
- [Approach 2 - HackSoft's proposed way](#approach-2---hacksofts-proposed-way)
|
||||||
* [More ideas](#more-ideas)
|
- [More ideas](#more-ideas)
|
||||||
- [Testing](#testing-2)
|
- [Testing](#testing-2)
|
||||||
* [Naming conventions](#naming-conventions)
|
- [Naming conventions](#naming-conventions)
|
||||||
- [Celery](#celery)
|
- [Celery](#celery)
|
||||||
* [The basics](#the-basics)
|
- [The basics](#the-basics)
|
||||||
* [Error handling](#error-handling)
|
- [Error handling](#error-handling)
|
||||||
* [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
* [Structure](#structure)
|
- [Structure](#structure)
|
||||||
* [Periodic Tasks](#periodic-tasks)
|
- [Periodic Tasks](#periodic-tasks)
|
||||||
* [Beyond](#beyond)
|
- [Beyond](#beyond)
|
||||||
- [Cookbook](#cookbook)
|
- [Cookbook](#cookbook)
|
||||||
* [Handling updates with a service](#handling-updates-with-a-service)
|
- [Handling updates with a service](#handling-updates-with-a-service)
|
||||||
- [DX (Developer Experience)](#dx-developer-experience)
|
- [DX (Developer Experience)](#dx-developer-experience)
|
||||||
* [`mypy` / type annotations](#mypy--type-annotations)
|
- [`mypy` / type annotations](#mypy--type-annotations)
|
||||||
- [Django Styleguide in the Wild](#django-styleguide-in-the-wild)
|
- [Django Styleguide in the Wild](#django-styleguide-in-the-wild)
|
||||||
- [Additional resources](#additional-resources)
|
- [Additional resources](#additional-resources)
|
||||||
- [Inspiration](#inspiration)
|
- [Inspiration](#inspiration)
|
||||||
|
@ -116,24 +115,24 @@ The core of the Django Styleguide can be summarized as follows:
|
||||||
|
|
||||||
**In Django, business logic should live in:**
|
**In Django, business logic should live in:**
|
||||||
|
|
||||||
* Services - functions, that mostly take care of writing things to the database.
|
- Services - functions, that mostly take care of writing things to the database.
|
||||||
* Selectors - functions, that mostly take care of fetching things from the database.
|
- Selectors - functions, that mostly take care of fetching things from the database.
|
||||||
* Model properties (with some exceptions).
|
- Model properties (with some exceptions).
|
||||||
* Model `clean` method for additional validations (with some exceptions).
|
- Model `clean` method for additional validations (with some exceptions).
|
||||||
|
|
||||||
**In Django, business logic should not live in:**
|
**In Django, business logic should not live in:**
|
||||||
|
|
||||||
* APIs and Views.
|
- APIs and Views.
|
||||||
* Serializers and Forms.
|
- Serializers and Forms.
|
||||||
* Form tags.
|
- Form tags.
|
||||||
* Model `save` method.
|
- Model `save` method.
|
||||||
* Custom managers or querysets.
|
- Custom managers or querysets.
|
||||||
* Signals.
|
- Signals.
|
||||||
|
|
||||||
**Model properties vs selectors:**
|
**Model properties vs selectors:**
|
||||||
|
|
||||||
* If the property spans multiple relations, it should better be a selector.
|
- If the property spans multiple relations, it should better be a selector.
|
||||||
* If the property is non-trivial & can easily cause `N + 1` queries problem, when serialized, it should better be a selector.
|
- If the property is non-trivial & can easily cause `N + 1` queries problem, when serialized, it should better be a selector.
|
||||||
|
|
||||||
The general idea is to "separate concerns" so those concerns can be maintainable / testable.
|
The general idea is to "separate concerns" so those concerns can be maintainable / testable.
|
||||||
|
|
||||||
|
@ -196,9 +195,9 @@ We recommend starting every new project with some kind of cookiecutter. Having t
|
||||||
|
|
||||||
Few examples:
|
Few examples:
|
||||||
|
|
||||||
* You can use the [`Styleguide-Example`](https://github.com/HackSoftware/Styleguide-Example) project as a starting point.
|
- You can use the [`Styleguide-Example`](https://github.com/HackSoftware/Styleguide-Example) project as a starting point.
|
||||||
* You can also use [`cookiecutter-django`](https://github.com/pydanny/cookiecutter-django) since it has a ton of good stuff inside.
|
- You can also use [`cookiecutter-django`](https://github.com/pydanny/cookiecutter-django) since it has a ton of good stuff inside.
|
||||||
* Or you can create something that works for your case & turn it into a [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/) project.
|
- Or you can create something that works for your case & turn it into a [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/) project.
|
||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
|
@ -715,8 +714,8 @@ If we take the example above, our service is named `user_create`. The pattern is
|
||||||
|
|
||||||
This is what we prefer in HackSoft's projects. This seems odd at first, but it has few nice features:
|
This is what we prefer in HackSoft's projects. This seems odd at first, but it has few nice features:
|
||||||
|
|
||||||
* **Namespacing.** It's easy to spot all services starting with `user_` and it's a good idea to put them in a `users.py` module.
|
- **Namespacing.** It's easy to spot all services starting with `user_` and it's a good idea to put them in a `users.py` module.
|
||||||
* **Greppability.** Or in other words, if you want to see all actions for a specific entity, just grep for `user_`.
|
- **Greppability.** Or in other words, if you want to see all actions for a specific entity, just grep for `user_`.
|
||||||
|
|
||||||
### Modules
|
### Modules
|
||||||
|
|
||||||
|
@ -780,12 +779,12 @@ If you decide to cover the service layer with tests, we have few general rules o
|
||||||
|
|
||||||
When creating the required state for a given test, one can use a combination of:
|
When creating the required state for a given test, one can use a combination of:
|
||||||
|
|
||||||
* Fakes (We recommend using [`faker`](https://github.com/joke2k/faker))
|
- Fakes (We recommend using [`faker`](https://github.com/joke2k/faker))
|
||||||
* Other services, to create the required objects.
|
- Other services, to create the required objects.
|
||||||
* Special test utility & helper methods.
|
- Special test utility & helper methods.
|
||||||
* Factories (We recommend using [`factory_boy`](https://factoryboy.readthedocs.io/en/latest/orms.html))
|
- Factories (We recommend using [`factory_boy`](https://factoryboy.readthedocs.io/en/latest/orms.html))
|
||||||
* Plain `Model.objects.create()` calls, if factories are not yet introduced in the project.
|
- Plain `Model.objects.create()` calls, if factories are not yet introduced in the project.
|
||||||
* Usually, whatever suits you better.
|
- Usually, whatever suits you better.
|
||||||
|
|
||||||
**Let's take a look at our service from the example:**
|
**Let's take a look at our service from the example:**
|
||||||
|
|
||||||
|
@ -819,9 +818,9 @@ def item_buy(
|
||||||
|
|
||||||
The service:
|
The service:
|
||||||
|
|
||||||
* Calls a selector for validation.
|
- Calls a selector for validation.
|
||||||
* Creates an object.
|
- Creates an object.
|
||||||
* Delays a task.
|
- Delays a task.
|
||||||
|
|
||||||
**Those are our tests:**
|
**Those are our tests:**
|
||||||
|
|
||||||
|
@ -879,31 +878,31 @@ When using services & selectors, all of your APIs should look simple & identical
|
||||||
|
|
||||||
**When we are creating new APIs, we follow those general rules:**
|
**When we are creating new APIs, we follow those general rules:**
|
||||||
|
|
||||||
* Have 1 API per operation. This means, for CRUD on a model, having 4 APIs.
|
- Have 1 API per operation. This means, for CRUD on a model, having 4 APIs.
|
||||||
* Inherit from the most simple `APIView` or `GenericAPIView`.
|
- Inherit from the most simple `APIView` or `GenericAPIView`.
|
||||||
* Avoid the more abstract classes, since they tend to manage things via serializers & we want to do that via services & selectors.
|
- Avoid the more abstract classes, since they tend to manage things via serializers & we want to do that via services & selectors.
|
||||||
* **Don't do business logic in your API.**
|
- **Don't do business logic in your API.**
|
||||||
* You can do **object fetching / data manipulation in your APIs** (potentially, you can extract that to somewhere else).
|
- You can do **object fetching / data manipulation in your APIs** (potentially, you can extract that to somewhere else).
|
||||||
* If you are calling `some_service` in your API, you can extract object fetching / data manipulation to `some_service_parse`.
|
- If you are calling `some_service` in your API, you can extract object fetching / data manipulation to `some_service_parse`.
|
||||||
* Basically, keep the APIs are simple as possible. They are an interface towards your core business logic.
|
- Basically, keep the APIs are simple as possible. They are an interface towards your core business logic.
|
||||||
|
|
||||||
When we are talking about APIs, we need a way to deal with data serialization - both incoming & outgoing data.
|
When we are talking about APIs, we need a way to deal with data serialization - both incoming & outgoing data.
|
||||||
|
|
||||||
**Here are our rules for API serialization:**
|
**Here are our rules for API serialization:**
|
||||||
|
|
||||||
* There should be a dedicated **input serializer** & a dedicated **output serializer**.
|
- There should be a dedicated **input serializer** & a dedicated **output serializer**.
|
||||||
* **Input serializer** takes care of the data coming in.
|
- **Input serializer** takes care of the data coming in.
|
||||||
* **Output serializer** takes care of the data coming out.
|
- **Output serializer** takes care of the data coming out.
|
||||||
* In terms of serialization, Use whatever abstraction works for you.
|
- In terms of serialization, Use whatever abstraction works for you.
|
||||||
|
|
||||||
**In case you are using DRF's serializers, here are our rules:**
|
**In case you are using DRF's serializers, here are our rules:**
|
||||||
|
|
||||||
* Serializer should be **nested in the API** and be named either `InputSerializer` or `OutputSerializer`.
|
- Serializer should be **nested in the API** and be named either `InputSerializer` or `OutputSerializer`.
|
||||||
* Our preference is for both serializers to inherit from the simpler `Serializer` and avoid using `ModelSerializer`
|
- Our preference is for both serializers to inherit from the simpler `Serializer` and avoid using `ModelSerializer`
|
||||||
* This is a matter of preference and choice. If `ModelSerializer` is working fine for you, use it.
|
- This is a matter of preference and choice. If `ModelSerializer` is working fine for you, use it.
|
||||||
* If you need a nested serializer, use the `inline_serializer` util.
|
- If you need a nested serializer, use the `inline_serializer` util.
|
||||||
* Reuse serializers as little as possible.
|
- Reuse serializers as little as possible.
|
||||||
* Reusing serializers may expose you to unexpected behavior, when something changes in the base serializers.
|
- Reusing serializers may expose you to unexpected behavior, when something changes in the base serializers.
|
||||||
|
|
||||||
### Naming convention
|
### Naming convention
|
||||||
|
|
||||||
|
@ -975,7 +974,7 @@ class UserListApi(APIView):
|
||||||
return Response(data)
|
return Response(data)
|
||||||
```
|
```
|
||||||
|
|
||||||
*Keep in mind this API is public by default. Authentication is up to you.*
|
_Keep in mind this API is public by default. Authentication is up to you._
|
||||||
|
|
||||||
#### Filters + Pagination
|
#### Filters + Pagination
|
||||||
|
|
||||||
|
@ -1580,9 +1579,7 @@ def some_service():
|
||||||
The response payload is going to look like this:
|
The response payload is going to look like this:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
["Some message"]
|
||||||
"Some message"
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks strange, because if we do it like this:
|
This looks strange, because if we do it like this:
|
||||||
|
@ -1817,9 +1814,7 @@ Response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": {
|
"detail": {
|
||||||
"non_field_errors": [
|
"non_field_errors": ["Some error message"]
|
||||||
"Some error message"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -1875,9 +1870,7 @@ Response:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": [
|
"detail": ["Some error message"]
|
||||||
"Some error message"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1930,17 +1923,13 @@ Response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": {
|
"detail": {
|
||||||
"foo": [
|
"foo": ["This field is required."],
|
||||||
"This field is required."
|
|
||||||
],
|
|
||||||
"email": [
|
"email": [
|
||||||
"Ensure this field has at least 200 characters.",
|
"Ensure this field has at least 200 characters.",
|
||||||
"Enter a valid email address."
|
"Enter a valid email address."
|
||||||
],
|
],
|
||||||
"nested": {
|
"nested": {
|
||||||
"bar": [
|
"bar": ["This field is required."]
|
||||||
"This field is required."
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1981,12 +1970,8 @@ Response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": {
|
"detail": {
|
||||||
"password": [
|
"password": ["This field cannot be blank."],
|
||||||
"This field cannot be blank."
|
"email": ["This field cannot be blank."]
|
||||||
],
|
|
||||||
"email": [
|
|
||||||
"This field cannot be blank."
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -2024,14 +2009,10 @@ For example, whenerver we have a `ValidationError` (usually coming from a Serial
|
||||||
"message": "Validation error.",
|
"message": "Validation error.",
|
||||||
"extra": {
|
"extra": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"password": [
|
"password": ["This field cannot be blank."],
|
||||||
"This field cannot be blank."
|
"email": ["This field cannot be blank."]
|
||||||
],
|
}
|
||||||
"email": [
|
|
||||||
"This field cannot be blank."
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2123,6 +2104,7 @@ Response:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Code:
|
Code:
|
||||||
|
@ -2139,9 +2121,7 @@ Response:
|
||||||
"message": "Validation error",
|
"message": "Validation error",
|
||||||
"extra": {
|
"extra": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"non_field_errors": [
|
"non_field_errors": ["Some error message"]
|
||||||
"Some error message"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2202,9 +2182,7 @@ Response:
|
||||||
{
|
{
|
||||||
"message": "Validation error",
|
"message": "Validation error",
|
||||||
"extra": {
|
"extra": {
|
||||||
"fields": [
|
"fields": ["Some error message"]
|
||||||
"Some error message"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -2263,17 +2241,13 @@ Response:
|
||||||
"message": "Validation error",
|
"message": "Validation error",
|
||||||
"extra": {
|
"extra": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"foo": [
|
"foo": ["This field is required."],
|
||||||
"This field is required."
|
|
||||||
],
|
|
||||||
"email": [
|
"email": [
|
||||||
"Ensure this field has at least 200 characters.",
|
"Ensure this field has at least 200 characters.",
|
||||||
"Enter a valid email address."
|
"Enter a valid email address."
|
||||||
],
|
],
|
||||||
"nested": {
|
"nested": {
|
||||||
"bar": [
|
"bar": ["This field is required."]
|
||||||
"This field is required."
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2318,16 +2292,13 @@ Response:
|
||||||
"message": "Validation error",
|
"message": "Validation error",
|
||||||
"extra": {
|
"extra": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"password": [
|
"password": ["This field cannot be blank."],
|
||||||
"This field cannot be blank."
|
"email": ["This field cannot be blank."]
|
||||||
],
|
|
||||||
"email": [
|
|
||||||
"This field cannot be blank."
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Now, this can be extended & made to better suit your needs:
|
Now, this can be extended & made to better suit your needs:
|
||||||
|
@ -2375,8 +2346,8 @@ project_name
|
||||||
|
|
||||||
We follow 2 general naming conventions:
|
We follow 2 general naming conventions:
|
||||||
|
|
||||||
* The test file names should be `test_the_name_of_the_thing_that_is_tested.py`
|
- The test file names should be `test_the_name_of_the_thing_that_is_tested.py`
|
||||||
* The test case should be `class TheNameOfTheThingThatIsTestedTests(TestCase):`
|
- The test case should be `class TheNameOfTheThingThatIsTestedTests(TestCase):`
|
||||||
|
|
||||||
For example, if we have:
|
For example, if we have:
|
||||||
|
|
||||||
|
@ -2404,8 +2375,8 @@ For example, if we have `project_name/common/utils.py`, then we are going to hav
|
||||||
|
|
||||||
If we are to split the `utils.py` module into submodules, the same will happen for the tests:
|
If we are to split the `utils.py` module into submodules, the same will happen for the tests:
|
||||||
|
|
||||||
* `project_name/common/utils/files.py`
|
- `project_name/common/utils/files.py`
|
||||||
* `project_name/common/tests/utils/test_files.py`
|
- `project_name/common/tests/utils/test_files.py`
|
||||||
|
|
||||||
We try to match the structure of our modules with the structure of their respective tests.
|
We try to match the structure of our modules with the structure of their respective tests.
|
||||||
|
|
||||||
|
@ -2413,9 +2384,9 @@ We try to match the structure of our modules with the structure of their respect
|
||||||
|
|
||||||
We use [Celery](http://www.celeryproject.org/) for the following general cases:
|
We use [Celery](http://www.celeryproject.org/) for the following general cases:
|
||||||
|
|
||||||
* Communicating with 3rd party services (sending emails, notifications, etc.)
|
- Communicating with 3rd party services (sending emails, notifications, etc.)
|
||||||
* Offloading heavier computational tasks outside the HTTP cycle.
|
- Offloading heavier computational tasks outside the HTTP cycle.
|
||||||
* Periodic tasks (using Celery beat)
|
- Periodic tasks (using Celery beat)
|
||||||
|
|
||||||
### The basics
|
### The basics
|
||||||
|
|
||||||
|
@ -2657,10 +2628,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
Few key things:
|
Few key things:
|
||||||
|
|
||||||
* We use this task as part of a deploy procedure.
|
- We use this task as part of a deploy procedure.
|
||||||
* We always put a link to [`crontab.guru`](https://crontab.guru) to explain the cron. Otherwise it's unreadable.
|
- We always put a link to [`crontab.guru`](https://crontab.guru) to explain the cron. Otherwise it's unreadable.
|
||||||
* Everything is in one place.
|
- Everything is in one place.
|
||||||
* ⚠️ We use, almost exclusively, a cron schedule. **If you plan on using the other schedule objects, provided by Celery, please read thru their documentation** & the important notes - <https://django-celery-beat.readthedocs.io/en/latest/#example-creating-interval-based-periodic-task> - about pointing to the same schedule object. ⚠️
|
- ⚠️ We use, almost exclusively, a cron schedule. **If you plan on using the other schedule objects, provided by Celery, please read thru their documentation** & the important notes - <https://django-celery-beat.readthedocs.io/en/latest/#example-creating-interval-based-periodic-task> - about pointing to the same schedule object. ⚠️
|
||||||
|
|
||||||
### Beyond
|
### Beyond
|
||||||
|
|
||||||
|
@ -2697,8 +2668,8 @@ def user_update(*, user: User, data) -> User:
|
||||||
return user
|
return user
|
||||||
```
|
```
|
||||||
|
|
||||||
* We're calling the generic `model_update` service for the fields that have no side-effects related to them (meaning that they're just set to the value that we provide).
|
- We're calling the generic `model_update` service for the fields that have no side-effects related to them (meaning that they're just set to the value that we provide).
|
||||||
* This pattern allows us to extract the repetitive field setting in a generic service and perform only the specific tasks inside of the update service (side-effects).
|
- This pattern allows us to extract the repetitive field setting in a generic service and perform only the specific tasks inside of the update service (side-effects).
|
||||||
|
|
||||||
The generic `model_update` implementation looks like this:
|
The generic `model_update` implementation looks like this:
|
||||||
|
|
||||||
|
@ -2727,8 +2698,9 @@ def model_update(
|
||||||
```
|
```
|
||||||
|
|
||||||
The full implementations of these services can be found in our example project:
|
The full implementations of these services can be found in our example project:
|
||||||
* [`model_update`](https://github.com/HackSoftware/Django-Styleguide-Example/blob/master/styleguide_example/common/services.py)
|
|
||||||
* [`user_update`](https://github.com/HackSoftware/Django-Styleguide-Example/blob/master/styleguide_example/users/services.py)
|
- [`model_update`](https://github.com/HackSoftware/Django-Styleguide-Example/blob/master/styleguide_example/common/services.py)
|
||||||
|
- [`user_update`](https://github.com/HackSoftware/Django-Styleguide-Example/blob/master/styleguide_example/users/services.py)
|
||||||
|
|
||||||
## DX (Developer Experience)
|
## DX (Developer Experience)
|
||||||
|
|
||||||
|
@ -2775,6 +2747,6 @@ Additional resources that we found useful and that can add value to the stylegui
|
||||||
|
|
||||||
The way we do Django is inspired by the following things:
|
The way we do Django is inspired by the following things:
|
||||||
|
|
||||||
* The general idea for **separation of concerns**
|
- The general idea for **separation of concerns**
|
||||||
* [Boundaries by Gary Bernhardt](https://www.youtube.com/watch?v=yTkzNHF6rMs)
|
- [Boundaries by Gary Bernhardt](https://www.youtube.com/watch?v=yTkzNHF6rMs)
|
||||||
* Rails service objects
|
- Rails service objects
|
||||||
|
|
Loading…
Reference in New Issue
Block a user