a Django boilerplate with Pre-commit, DRF serializer, Pytest, and Github action
-
Python: No need to say more, Python is a trend and It will be good for any backend web project
-
Django: Django is one of the most powerful frameworks for any python web project using it to handle this task is a bit abusive. But it's still good to use it
- Question: Why don't you use Flask?
- Answer: Yes, Flask is also a good alternative. Let's go over some aspects:
- Time spent and ease of use: Flask might be easier because of its simplicity, but I know Django well.
Django = Flask
- Library Support, Plugin Apps, Built-in Features, Security: Of course,
Django > Flask
- Popularity, Community support, Job opportunities: Django > Flask. Nope, not really!
Django = Flask
. - Performance: Flask is smaller and has fewer layers so it's faster, but Django is more efficient in complex features.
Django = Flask
- Summary: I believe both Django and Flask are fully capable of handling this task. Flask seems more native and can change the architecture flexibly. But Django makes me more confident about doing this task in a short time.
- Time spent and ease of use: Flask might be easier because of its simplicity, but I know Django well.
-
DRF: In this project, we need it for serialization and API routing
-
Pre-commit: A good code cleaner, we can apply many lint rules to it. It will help to check and auto-format code when we commit code
-
PyTest: A well-known unit test framework for Python projects with rich external plugins
-
Github Action: Automate all workflows with built-in CI/CD. Build, test, and deploy code right from GitHub. We can use this to run Pytest, check test coverage
-
simple_setup.sh
-
git clone git@github.com:ngohoangyell/Simple_Django_Project.git
-
cd Simple_Django_Project
-
python3 -m venv hybeta_env
-
source hybeta_env/bin/activate
-
python3 -m pip install -r requirements.txt
- or
python3 -m pip install -r how_to_setup/fallback_requirements.txt
- or
-
pre-commit install
-
pre-commit run --all-files
-
python manage.py migrate
-
pytest -s
-
python manage.py runserver
-
-
sh how_to_setup/create_example_data.sh
that's it, all done! 🥳
- Q&A 🤔🤷♀️🤷♂️
hybeta_doctor_translation
: not enough fields?- Okay, we may add more fields later.
hybeta_doctor
: available_time is a text field?- Humm, a lot of things to handle this. The additional table is a must-have. We will update later.
hybeta_location
: displaying only 1 language?- Hehe, we may create an additional table later.
- Why do all tables have this
hybeta_.*
prefix?- We will use a lot of plugin apps in Django. The prefix will help easy to distinguish from another plugin app table.
- Why do you use Soft Delete(
is_deleted
,deleted_at
)?- DB fees are very cheap nowadays, so we should prioritize solutions that are convenient for maintenance and troubleshooting.
Postman collections:
https://www.getpostman.com/collections/e735f5704363b0546881
URL: http://127.0.0.1:8000/doctor/
cURL:
curl --location --request GET 'http://127.0.0.1:8000/doctor/'
Response:
[
{
"id": 56,
"doctor_translations": [
{
"id": 83,
"language_code": "HK",
"name": "new name HK 1",
"note": "new note HK 1"
},
{
"id": 82,
"language_code": "EN",
"name": "new name 1",
"note": "new note 1"
}
],
"location": {
"id": 44,
"district": "TP",
"latitude": "874.1669429",
"longitude": "111.1669420",
"name": "new loc 1"
},
"phone": "0905360911",
"category": "D",
"price": "123.11",
"available_time": "available 1"
}
]
Usage:
- filter:
filter_{priority}__{field_name}={expected_value}
- sort:
sort_{priority}__{field_name}={asc/desc}
- advanced filter:
filter_{priority}__{field_name}__{lte/le/gte/ge/icontains}={expected_value}
cURL:
curl --location --request GET 'http://127.0.0.1:8000/doctor/?filter_1__district=WTS&filter_2__category=D&filter_3__price__lte=200.02&filter_4__language_code=EN&sort_1__id=desc'
Response:
[
{
"id": 38,
"doctor_translations": [
{
"id": 53,
"language_code": "EN",
"name": "new name EN 2",
"note": "new note EN 2"
}
],
"location": {
"id": 33,
"district": "WTS",
"latitude": "222.3330000",
"longitude": "333.4440000",
"name": "new loc 2"
},
"phone": "+852800930002",
"category": "D",
"price": "200.02",
"available_time": "available time 2"
},
{
"id": 22,
"doctor_translations": [
{
"id": 25,
"language_code": "EN",
"name": "new name EN 2",
"note": "new note EN 2"
}
],
"location": {
"id": 22,
"district": "WTS",
"latitude": "222.3330000",
"longitude": "333.4440000",
"name": "new loc 2"
},
"phone": "+852800930002",
"category": "D",
"price": "200.02",
"available_time": "available time 2"
}
]
URL: http://127.0.0.1:8000/doctor/1/?filter_1__language_code=HK
cURL:
curl --location --request GET 'http://127.0.0.1:8000/doctor/1/?filter_1__language_code=HK'
Response:
{
"id": 1,
"doctor_translations": [
{
"id": 2,
"language_code": "HK",
"name": "new name HK 1",
"note": "new note HK 1"
}
],
"location": {
"id": 1,
"district": "TP",
"latitude": "111.1669429",
"longitude": "111.1669420",
"name": "new loc 1"
},
"phone": "0905360911",
"category": "D",
"price": "123.11",
"available_time": "available 1"
}
URL: http://127.0.0.1:8000/doctor/ or http://127.0.0.1:8000/doctor/bulk_create/
cURL:
curl --location --request POST 'http://127.0.0.1:8000/doctor/' \
--header 'Content-Type: application/json' \
--data-raw ' {
"doctor_translations": [
{
"language_code": "EN",
"name": "new name EN 6",
"note": "new note EN 6"
},
{
"language_code": "HK",
"name": "new name HK 6",
"note": "new note HK 6"
}
],
"location": {
"id": 1
},
"phone": "+852800930005",
"category": "F",
"price": "500.05",
"available_time": "available time 5"
}'
Response:
{
"id": 76,
"doctor_translations": [
{
"id": 116,
"language_code": "HK",
"name": "new name HK 6",
"note": "new note HK 6"
},
{
"id": 115,
"language_code": "EN",
"name": "new name EN 6",
"note": "new note EN 6"
}
],
"location": {
"id": 1,
"district": "TP",
"latitude": "111.1669429",
"longitude": "111.1669420",
"name": "new loc 1"
},
"phone": "+852800930005",
"category": "F",
"price": "500.05",
"available_time": "available time 5"
}
URL: http://127.0.0.1:8000/doctor/ or http://127.0.0.1:8000/doctor/bulk_create/
cURL:
curl --location --request POST 'http://127.0.0.1:8000/doctor/' \
--header 'Content-Type: application/json' \
--data-raw '[
{
"doctor_translations": [
{
"language_code": "EN",
"name": "new name 1",
"note": "new note 1"
},
{
"language_code": "HK",
"name": "new name HK 1",
"note": "new note HK 1"
}
],
"location": {
"district": "TP",
"latitude": "874.1669429",
"longitude": "111.1669420",
"name": "new loc 1"
},
"phone": "0905360911",
"category": "D",
"price": "123.11",
"available_time": "available 1"
},
{
"doctor_translations": [
{
"language_code": "EN",
"name": "new name 2",
"note": "new note 2"
}
],
"location": {
"district": "WTS",
"latitude": "112.9429000",
"longitude": "112.1942900",
"name": "new loc 2"
},
"phone": "20244432322",
"category": "K",
"price": "32432.22",
"available_time": "available 2"
}
]'
Response:
[
{
"id": 74,
"doctor_translations": [
{
"id": 113,
"language_code": "HK",
"name": "new name HK 1",
"note": "new note HK 1"
},
{
"id": 112,
"language_code": "EN",
"name": "new name 1",
"note": "new note 1"
}
],
"location": {
"id": 55,
"district": "TP",
"latitude": "874.1669429",
"longitude": "111.1669420",
"name": "new loc 1"
},
"phone": "0905360911",
"category": "D",
"price": "123.11",
"available_time": "available 1"
},
{
"id": 75,
"doctor_translations": [
{
"id": 114,
"language_code": "EN",
"name": "new name 2",
"note": "new note 2"
}
],
"location": {
"id": 56,
"district": "WTS",
"latitude": "112.9429000",
"longitude": "112.1942900",
"name": "new loc 2"
},
"phone": "20244432322",
"category": "K",
"price": "32432.22",
"available_time": "available 2"
}
]
-
Test folder: tests
-
Command:
pytest -s
orpytest -s tests/jobs
orpytest --cov=hybeta tests/
-
Test Database:
test_db.sqlite3
. You can update the settings in test.py and pytest.ini
- Pagination: It must be implemented for sure!
- Django-hvad
- Django-filter
- Django-environ
- Update
hybeta_doctor.available_time
, createhybeta_doctor.doctor_scheduled_time
to handle it. - Expose API to retrieve doctor service(category)
- Return log_id in the response header if status_code = 500
- Check API Permission
- Validate payload
- API Throttling to prevent DDOS
- Cache model, Cache URL:
- Clear cache when having the new update
- Bulk creates API, If we have to consume the large data:
- Implement Queue to handle it
- Implement Parallel Processing to decrease process time
- Accept to consume & return response in XML instead of only JSON
- Build log system(Sentry, Kibana, Datadog)
- Elasticsearch is also a nice tech to apply if we need to handle a more complex filter