{"id":16417407,"url":"https://github.com/HackSoftware/Django-Styleguide-Example","last_synced_at":"2025-10-26T20:30:37.120Z","repository":{"id":36954748,"uuid":"273433222","full_name":"HackSoftware/Django-Styleguide-Example","owner":"HackSoftware","description":"Repository for example styleguide project","archived":false,"fork":false,"pushed_at":"2025-02-03T09:08:33.000Z","size":811,"stargazers_count":741,"open_issues_count":16,"forks_count":131,"subscribers_count":25,"default_branch":"master","last_synced_at":"2025-02-06T02:48:50.161Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HackSoftware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-19T07:34:11.000Z","updated_at":"2025-02-04T04:23:57.000Z","dependencies_parsed_at":"2024-12-07T22:01:48.797Z","dependency_job_id":"74305b6a-ad5e-4e31-9ed1-270572e79e14","html_url":"https://github.com/HackSoftware/Django-Styleguide-Example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HackSoftware%2FDjango-Styleguide-Example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HackSoftware%2FDjango-Styleguide-Example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HackSoftware%2FDjango-Styleguide-Example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HackSoftware%2FDjango-Styleguide-Example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HackSoftware","download_url":"https://codeload.github.com/HackSoftware/Django-Styleguide-Example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238394323,"owners_count":19464583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T07:11:29.501Z","updated_at":"2025-10-26T20:30:37.108Z","avatar_url":"https://github.com/HackSoftware.png","language":"Python","readme":"# Django Styleguide Example\n\n\u003e 👀 **Need help with your Django project?** [HackSoft is here for you](https://www.hacksoft.io/solutions/django?utm_source=django-styleguide\u0026utm_medium=web\u0026utm_campaign=Django-Campaign). Reach out at `consulting@hacksoft.io`\n\n**Table of contents:**\n\n\u003c!-- toc --\u003e\n\n- [How to ask a question or propose something?](#how-to-ask-a-question-or-propose-something)\n- [What is this?](#what-is-this)\n- [Structure](#structure)\n- [General API Stuff](#general-api-stuff)\n  * [CORS](#cors)\n- [Authentication - JWT](#authentication---jwt)\n  * [Settings](#settings)\n  * [APIs](#apis)\n  * [Requiring authentication](#requiring-authentication)\n- [Authentication - Sessions](#authentication---sessions)\n  * [DRF \u0026 Overriding `SessionAuthentication`](#drf--overriding-sessionauthentication)\n  * [Cross origin](#cross-origin)\n  * [APIs](#apis-1)\n  * [`HTTP Only` / `SameSite`](#http-only--samesite)\n  * [Reading list](#reading-list)\n- [Example List API](#example-list-api)\n- [File uploads](#file-uploads)\n- [Helpful commands for local development without `docker compose`](#helpful-commands-for-local-development-without-docker-compose)\n- [Helpful commands for local development with `docker compose`](#helpful-commands-for-local-development-with-docker-compose)\n- [Deployment](#deployment)\n  * [Heroku](#heroku)\n  * [AWS ECS](#aws-ecs)\n- [Linters and Code Formatters](#linters-and-code-formatters)\n\n\u003c!-- tocstop --\u003e\n\n---\n\n## How to ask a question or propose something?\n\nFew points to navigate yourself:\n\n1. If you have an issue with something related to the Django Styleguide Example - **just open an issue. We will respond.**\n1. If you have a general question or suggestion - **just open an issue. We will respond.**\n1. Even if you have a question that you are not sure if it's related to the Django Styleguide - **just open an issue anyway. We will respond.**\n\nThat's about it ✨\n\n## What is this?\n\nHello 👋\n\nThis projects serves as the following:\n\n1. As an [example of our Django Styleguide](https://github.com/HackSoftware/Django-Styleguide), where people can explore actual code \u0026 not just snippets.\n1. As a Django project, where we can test various things \u0026 concepts. A lot of the things you see here are being used as a foundation of our internal projects at [HackSoft](https://www.hacksoft.io/).\n    - Usually, this is how something ends up as a section in the [Django Styleguide](https://github.com/HackSoftware/Django-Styleguide)\n1. As a place for all code examples from [our blog](https://www.hacksoft.io/blog).\n    - Code snippets tend to decay \u0026 **we want most of our blog articles to be up to date.** That's why we place the code here, write tests for it \u0026 guarantee a longer shelf life of the examples.\n\nIf you want to learn more about the Django Styleguide, you can watch the videos below:\n\n**Radoslav Georgiev's [Django structure for scale and longevity](https://www.youtube.com/watch?v=yG3ZdxBb1oo) for the philosophy behind the styleguide:**\n\n[![Django structure for scale and longevity by Radoslav Georgiev](https://img.youtube.com/vi/yG3ZdxBb1oo/0.jpg)](https://www.youtube.com/watch?v=yG3ZdxBb1oo)\n\n**Radoslav Georgiev \u0026 Ivaylo Bachvarov's [discussion on HackCast, around the Django Styleguide](https://www.youtube.com/watch?v=9VfRaPECbpY):**\n\n[![HackCast S02E08 - Django Community \u0026 Django Styleguide](https://img.youtube.com/vi/9VfRaPECbpY/0.jpg)](https://www.youtube.com/watch?v=9VfRaPECbpY)\n\n## Structure\n\nThe initial structure was inspired by [cookiecutter-django](https://github.com/pydanny/cookiecutter-django).\n\n**The structure now is modified based on our work \u0026 production experience with Django.**\n\nFew important things:\n\n- Linux / Ubuntu is our primary OS and things are tested for that.\n- It's dockerized for local development with `docker compose`.\n- It uses Postgres as the primary database.\n- It comes with [`whitenoise`](http://whitenoise.evans.io/en/stable/) setup, even for local development.\n- It comes with [`mypy`](https://mypy.readthedocs.io/en/stable/) configured, using both \u003chttps://github.com/typeddjango/django-stubs\u003e and \u003chttps://github.com/typeddjango/djangorestframework-stubs/\u003e\n  - Basic `mypy` configuration is located in [`setup.cfg`](setup.cfg)\n  - `mypy` is ran as a build step in [`.github/workflows/django.yml`](.github/workflows/django.yml)\n  - ⚠️ The provided configuration is quite minimal. **You should figure out your team needs \u0026 configure accordingly** - \u003chttps://mypy.readthedocs.io/en/stable/config_file.html\u003e\n- It comes with GitHub Actions support, [based on that article](https://hacksoft.io/github-actions-in-action-setting-up-django-and-postgres/)\n- It can be easily deployed to Heroku or AWS ECS.\n- It comes with an example list API, that uses [`django-filter`](https://django-filter.readthedocs.io/en/stable/) for filtering \u0026 pagination from DRF.\n- It comes with setup for [Django Debug Toolbar](https://django-debug-toolbar.readthedocs.io/en/latest/)\n- It comes with examples for writing tests with fakes \u0026 factories, based on the following articles - \u003chttps://www.hacksoft.io/blog/improve-your-tests-django-fakes-and-factories\u003e, \u003chttps://www.hacksoft.io/blog/improve-your-tests-django-fakes-and-factories-advanced-usage\u003e\n- It comes with examples for how to add Google login, based on the following article - \u003chttps://www.hacksoft.io/blog/adding-google-login-to-your-existing-django-and-django-rest-framework-applications\u003e\n\n## General API Stuff\n\n### CORS\n\nThe project is running [`django-cors-headers`](https://github.com/adamchainz/django-cors-headers) with the following general configuration:\n\n```python\nCORS_ALLOW_CREDENTIALS = True\nCORS_ALLOW_ALL_ORIGINS = True\n```\n\nFor `production.py`, we have the following:\n\n```python\nCORS_ALLOW_ALL_ORIGINS = False\nCORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST', default=[])\n```\n\n## Authentication - JWT\n\nThe project is using \u003chttps://github.com/Styria-Digital/django-rest-framework-jwt\u003e for having authentication via JWT capabilities.\n\n### Settings\n\nAll JWT related settings are located in `config/settings/jwt.py`.\n\n\u003e ⚠️ We highly recommend reading the entire settings page from the project documentation - \u003chttps://styria-digital.github.io/django-rest-framework-jwt/#additional-settings\u003e - to figure out your needs \u0026 the proper defaults for you!\n\nThe default settings also include the JWT token as a cookie.\n\nThe specific details about how the cookie is set, can be found here - \u003chttps://github.com/Styria-Digital/django-rest-framework-jwt/blob/master/src/rest_framework_jwt/compat.py#L43\u003e\n\n### APIs\n\nThe JWT related APIs are:\n\n1. `/api/auth/jwt/login/`\n1. `/api/auth/jwt/logout/`\n\nThe current implementation of the login API returns just the token:\n\n```json\n{\n  \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InJhZG9yYWRvQGhhY2tzb2Z0LmlvIiwiaWF0IjoxNjQxMjIxMDMxLCJleHAiOjE2NDE4MjU4MzEsImp0aSI6ImIyNTEyNmY4LTM3ZDctNGI5NS04Y2M0LTkzZjI3MjE4ZGZkOSIsInVzZXJfaWQiOjJ9.TUoQQPSijO2O_3LN-Pny4wpQp-0rl4lpTs_ulkbxzO4\"\n}\n```\n\nThis can be changed from `auth_jwt_response_payload_handler`.\n\n### Requiring authentication\n\nWe follow this concept:\n\n1. All APIs are public by default (no default authentication classes)\n1. If you want a certain API to require authentication, you add the `ApiAuthMixin` to it.\n\n## Authentication - Sessions\n\nThis project is using the already existing [**cookie-based session authentication**](https://docs.djangoproject.com/en/3.1/topics/auth/default/#how-to-log-a-user-in) in Django:\n\n1. On successful authentication, Django returns the `sessionid` cookie:\n\n```\nsessionid=5yic8rov868prmfoin2vhtg4vx35h71p; expires=Tue, 13 Apr 2021 11:17:58 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax\n```\n\n2. When making calls from the frontend, don't forget to **include credentials**. For example, when using `axios`:\n\n```javascript\naxios.get(url, { withCredentials: true });\naxios.post(url, data, { withCredentials: true });\n```\n\n3. For convenience, `CSRF_USE_SESSIONS` is set to `True`\n\n4. Check `config/settings/sessions.py` for all configuration that's related to sessions.\n\n### DRF \u0026 Overriding `SessionAuthentication`\n\nSince the default implementation of `SessionAuthentication` enforces CSRF check, which is not the desired behavior for our APIs, we've done the following:\n\n```python\nfrom rest_framework.authentication import SessionAuthentication\n\n\nclass CsrfExemptedSessionAuthentication(SessionAuthentication):\n    \"\"\"\n    DRF SessionAuthentication is enforcing CSRF, which may be problematic.\n    That's why we want to make sure we are exempting any kind of CSRF checks for APIs.\n    \"\"\"\n    def enforce_csrf(self, request):\n        return\n```\n\nWhich is then used to construct an `ApiAuthMixin`, which marks an API that requires authentication:\n\n```python\nfrom rest_framework.permissions import IsAuthenticated\n\n\nclass ApiAuthMixin:\n    authentication_classes = (CsrfExemptedSessionAuthentication, )\n    permission_classes = (IsAuthenticated, )\n```\n\n**By default, all APIs are public, unless you add the `ApiAuthMixin`**\n\n### Cross origin\n\nWe have the following general cases:\n\n1. The current configuration works out of the box for `localhost` development.\n1. If the backend is located on `*.domain.com` and the frontend is located on `*.domain.com`, the configuration is going to work out of the box.\n1. If the backend is located on `somedomain.com` and the frontend is located on `anotherdomain.com`, then you'll need to set `SESSION_COOKIE_SAMESITE = 'None'` and `SESSION_COOKIE_SECURE = True`\n\n### APIs\n\n1. `POST` to `/api/auth/session/login/` requires JSON body with `email` and `password`.\n1. `GET` to `/api/auth/me/` returns the current user information, if the request is authenticated (has the corresponding `sessionid` cookie)\n1. `GET` or `POST` to `/api/auth/logout/` will remove the `sessionid` cookie, effectively logging you out.\n\n### `HTTP Only` / `SameSite`\n\nThe current implementation of `/api/auth/session/login` does 2 things:\n\n1. Sets a `HTTP Only` cookie with the session id.\n1. Returns the actual session id from the JSON payload.\n\nThe second thing is required, because Safari is not respecting the `SameSite = None` option for cookies.\n\nMore on the issue here - \u003chttps://www.chromium.org/updates/same-site/incompatible-clients\u003e\n\n### Reading list\n\nSince cookies can be somewhat elusive, check the following urls:\n\n1. \u003chttps://docs.djangoproject.com/en/3.1/ref/settings/#sessions\u003e - It's a good idea to just read every description for `SESSION_*`\n1. \u003chttps://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies\u003e - It's a good idea to read everything, several times.\n\n## Example List API\n\nYou can find the `UserListApi` in [`styleguide_example/users/apis.py`](https://github.com/HackSoftware/Styleguide-Example/blob/master/styleguide_example/users/apis.py#L12)\n\nList API is located at:\n\n\u003chttp://localhost:8000/api/users/\u003e\n\nThe API can be filtered:\n\n- \u003chttp://localhost:8000/api/users/?is_admin=True\u003e\n- \u003chttp://localhost:8000/api/users/?id=1\u003e\n- \u003chttp://localhost:8000/api/users/?email=radorado@hacksoft.io\u003e\n\nExample data structure:\n\n```\n{\n    \"limit\": 1,\n    \"offset\": 0,\n    \"count\": 4,\n    \"next\": \"http://localhost:8000/api/users/?limit=1\u0026offset=1\",\n    \"previous\": null,\n    \"results\": [\n        {\n            \"id\": 1,\n            \"email\": \"radorado@hacksoft.io\",\n            \"is_admin\": false\n        }\n    ]\n}\n```\n\n## File uploads\n\nFollowing this article - \u003chttps://www.hacksoft.io/blog/direct-to-s3-file-upload-with-django\u003e - there's a rich file-upload implementation in the Django Styleguide Example.\n\nEverything is located in the `files` app.\n\nConfiguration wise, everything is located in [`config/settings/files_and_storages.py`](config/settings/files_and_storages.py)\n\nAdditionally, you can check the available options in [`.env.example`](.env.example)\n\nCurrently, the following is supported:\n\n1. Standard local file upload.\n1. Standard S3 file upload.\n1. Using CloudFront as CDN.\n1. The so-called \"direct\" upload that can work both locally and with S3 (for more context, [check the article](https://www.hacksoft.io/blog/direct-to-s3-file-upload-with-django))\n\nFeel free to use this as the basis of your file upload needs.\n\n## Helpful commands for local development without `docker compose`\n\nTo create Postgres database:\n\n```\nsudo -u postgres createdb -O your_postgres_user_here database_name_here\n```\n\nIf you want to recreate your database, you can use the bootstrap script:\n\n```\n./scripts/bootstrap.sh your_postgres_user_here\n```\n\nTo start Celery:\n\n```\ncelery -A styleguide_example.tasks worker -l info --without-gossip --without-mingle --without-heartbeat\n```\n\nTo start Celery Beat:\n\n```\ncelery -A styleguide_example.tasks beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler\n```\n\n## Helpful commands for local development with `docker compose`\n\nTo build and run everything\n\n```\ndocker compose up\n```\n\nTo run migrations\n\n```\ndocker compose run django python manage.py migrate\n```\n\nTo shell\n\n```\ndocker compose run django python manage.py shell\n```\n\n## Deployment\n\nThis project is ready to be deployed either on **Heroku** or **AWS ECS**.\n\n### Heroku\n\nDeploying a Python / Django application on Heroku is quite straighforward \u0026 this project is ready to be deployed.\n\nTo get an overview of how Heroku deployment works, we recommend reading this first - \u003chttps://devcenter.heroku.com/articles/deploying-python\u003e\n\n**Files related to Heroku deployment:**\n\n1. `Procfile`\n   - Comes with default `web`, `worker` and `beat` processes.\n   - Additionally, there's a `release` phase to run migrations safely, before releasing the new build.\n1. `.python-version`\n   - Simply specifies the Python version to be used.\n1. `requirements.txt`\n   - Heroku requires a root-level `requirements.txt`, so we've added that.\n\n**Additionally, you need to specify at least the following settings:**\n\n1. `DJANGO_SETTINGS_MODULE`, usually to `config.django.production`\n1. `SECRET_KEY` to something secret. [Check here for ideas](https://stackoverflow.com/questions/41298963/is-there-a-function-for-generating-settings-secret-key-in-django).\n1. `ALLOWED_HOSTS`, usually to the default heroku domain (for example - `hacksoft-styleguide-example.herokuapp.com`)\n\nOn top of that, we've added `gunicorn.conf.py` with some example settings.\n\n**We recommend the following materials, to figure out `gunicorn` defaults and configuration:**\n\n1. \u003chttps://devcenter.heroku.com/articles/python-gunicorn\u003e\n1. \u003chttps://adamj.eu/tech/2019/09/19/working-around-memory-leaks-in-your-django-app/\u003e\n1. \u003chttps://adamj.eu/tech/2021/12/29/set-up-a-gunicorn-configuration-file-and-test-it/\u003e\n1. Worker settings - \u003chttps://docs.gunicorn.org/en/latest/settings.html#worker-processes\u003e\n1. A brief description of the architecture of Gunicorn - \u003chttps://docs.gunicorn.org/en/latest/design.html\u003e\n\n### AWS ECS\n\n_Coming soon_\n\n## Linters and Code Formatters\n\nIn all our Django projects we use:\n\n- [ruff](https://docs.astral.sh/ruff/) - an extremely fast Python linter and code formatter, written in Rust.\n- [pre-commit](https://pre-commit.com/) - a tool that triggers the linters before each commit.\n\nTo make sure all of the above tools work in symbiosis, you'd need to add some configuration:\n\n1. Add `.pre-commit-config.yaml` file to the root of your project. There you can add the instructions for `pre-commit`\n2. Add `pyproject.toml` file to the root of your project. There you can add the `ruff` config.\n3. Make sure the linters are run against each PR on your CI. This is the config you need if you use GH actions:\n\n- If you are running it as a separate step in the build process:\n```\nbuild:\n  runs-on: ubuntu-latest\n  steps:\n    - name: Run ruff\n\t  uses: chartboost/ruff-action@v1\n```\n\n- If you would like to run it as a part of another step, which has already ran the package installation commands:\n```\n- name: Run ruff\n  run: ruff check .\n```\n\n4. Last but not least, we highly recommend you to setup your editor to run `ruff` every time you save a new Python file.\n\nIn order to test if your local setup is up to date, you can either:\n\n1. Try making a commit, to see if `pre-commit` is going to be triggered.\n2. Or run `ruff check .` in the project root directory.\n","funding_links":[],"categories":["Python"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHackSoftware%2FDjango-Styleguide-Example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHackSoftware%2FDjango-Styleguide-Example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHackSoftware%2FDjango-Styleguide-Example/lists"}