├── .coveragerc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── .flake8 ├── app │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── core │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ └── test_models.py │ └── urls.py └── manage.py ├── docker-compose.yml ├── img ├── README.md ├── build-devops-automation-recycle_code-refresh_settings-preferences-512.png ├── django-icon-0.jpg ├── docker-icon-14.jpg ├── iconfinder_api-code-window_532742.png ├── iconfinder_application-x-python_8974.png ├── iconfinder_folder_images2_2319.png ├── python-icon-18.jpg ├── rest-api-icon-8.jpg └── travis-ci.png └── requirements.txt /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | *migrations* 4 | *venv* 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | .idea/ 125 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.7" 4 | 5 | services: 6 | - docker 7 | 8 | before_install: 9 | - docker version 10 | - docker-compose version 11 | 12 | before_script: 13 | - pip install docker-compose 14 | 15 | script: 16 | # - docker-compose run --rm $(bash <(curl -s https://codecov.io/env)) -u root app sh -c "python manage.py test && flake8" 17 | # Using ROOT user in order to avoid 'PermissionError: [Errno 13] Permission denied: '/app/.coverage' error: 18 | # - docker-compose run -u root app sh -c "coverage run --source='.' manage.py test && coverage report && flake8 && codecov --commit=:sha" 19 | #- docker-compose run --rm -u root app sh -c "coverage run --source='.' manage.py test && coverage report && coverage xml && flake8" 20 | - docker-compose run app sh -c "python manage.py test && flake8" 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-alpine 2 | MAINTAINER Egor Kostan 3 | 4 | ENV PYTHONUNBUFFERED 1 5 | 6 | COPY ./requirements.txt /requirements.txt 7 | RUN pip install -r /requirements.txt 8 | 9 | RUN mkdir /app 10 | WORKDIR /app 11 | COPY ./app /app 12 | 13 | RUN adduser -D user 14 | USER user 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](http://unlicense.org/) 2 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/y/ikostan/Build_Backend_REST_API_with_Python_and_Django) 3 | [![Build Status](https://travis-ci.org/ikostan/Build_Backend_REST_API_with_Python_and_Django.svg?branch=master)](https://travis-ci.org/ikostan/Build_Backend_REST_API_with_Python_and_Django) 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c8581d51c457467a9e5d106e5acebd94)](https://www.codacy.com/manual/ikostan/Build_Backend_REST_API_with_Python_and_Django?utm_source=github.com&utm_medium=referral&utm_content=ikostan/Build_Backend_REST_API_with_Python_and_Django&utm_campaign=Badge_Grade) 5 | 6 | # [Backend EST API with Python & Django](https://www.udemy.com/course/django-python-advanced) 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | ### Create an advanced REST API with Python, Django REST Framework and Docker using Test Driven Development (TDD) 19 | 20 | ### Table of Contents 21 | 22 | 1. About 23 | 24 | 2. Main Objectives 25 | 26 | 3. Tech Topics Covered 27 | 28 | 4. Icons Library 29 | 30 | 5. Official Web Resources 31 | 32 | 6. Additional Resources 33 | 34 | 7. Tech Issues and Problem Solving 35 | 36 | 8. Django App: 37 | 38 | - Django App Configuration Files 39 | - Core app 40 | 41 | ### About 42 | 43 | 44 | The advanced course on how to Build a Backend REST API using Python, Django (2.0), Django REST Framework (3.9), Docker, Travis CI, Postgres and Test Driven Development! 45 | 46 | The original course content was created by [Mark Winterbottom](https://linkedin.com/in/markwinterbottom/). 47 | 48 | ### The main goal is to built a fully functioning REST API that can handle 49 | 50 | 51 | - User authentication 52 | - Creating objects 53 | - Filtering and sorting objects 54 | - Uploading and viewing images 55 | 56 | ### Additional topics covered 57 | 58 | 59 | - Setup a project with Docker and Docker-Compose 60 | - Configure Travis-CI to automatically run linting and unit tests 61 | - Write unit tests using the Django Test Framework 62 | - Apply best practice principles including Test Driven Development 63 | - Handle uploading media files with Django 64 | - Customize the Django admin 65 | - Configure a Postgres database 66 | 67 | ### Official Web Resources 68 | 69 | 70 | - [Python](https://www.python.org/) 71 | - [Django](https://www.djangoproject.com/) 72 | - [Django REST Framework](https://www.django-rest-framework.org/) 73 | - [Docker](https://www.docker.com) 74 | - [Travis-CI](https://travis-ci.org/) 75 | - [PostgreSQL](https://www.postgresql.org) 76 | - [GitHub](https://github.com/) 77 | 78 | ### Additional Resources 79 | 80 | 81 | - [GitHub Desktop](https://desktop.github.com/) 82 | - [PyCharm](https://www.jetbrains.com/pycharm/) 83 | 84 | ### Tech Issues and Problem Solving 85 | 86 | 87 |
88 | First-Time Git Setup 89 | 90 | The first thing you should do when you install Git is to set your user name and email address. This is important because every Git commit uses this information, and it’s immutably baked into the commits you start creating: 91 | 92 | ```bash 93 | git config --global user.name "John Doe" 94 | git config --global user.email johndoe@example.com 95 | ``` 96 | 97 | [Source](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) 98 | 99 |
100 | 101 |
102 | Changing the project interpreter in the PyCharm project settings 103 | 104 | 1. In the **Settings/Preferences dialog** (Ctrl+Alt+S), select **Project | Project Interpreter**. 105 | 2. Expand the list of the available interpreters and click the **Show All** link. 106 | 3. Select the target interpreter. When PyCharm stops supporting any of the outdated Python versions, the corresponding project interpreter is marked as unsupported. 107 | 4. The Python interpreter name specified in the **Name** field, becomes visible in the list of available interpreters. Click **OK** to apply the changes. 108 | 109 | For more info please [check here](https://www.jetbrains.com/help/pycharm/configuring-python-interpreter.html) 110 |
111 | 112 |
113 | PyCharm - Choosing Your Testing Framework 114 | 115 | 1. Open the Settings/Preferences dialog, and under the node Tools, click the page **Python Integrated Tools**. 116 | 2. On this page, click the **Default Test Runner** field. 117 | 3. Choose the desired test runner: 118 | 119 |
120 | 121 |
122 | 123 | For more info please see [Enable Pytest for you project](https://www.jetbrains.com/help/pycharm/pytest.html) 124 |
125 | 126 |
127 | Setting up Python3 virtual environment on Windows machine 128 | 129 | 1. open CMD
130 | 2. navigate to project directory, for example:
131 | ```bash 132 | cd C:\Users\superadmin\Desktop\Python\CodinGame 133 | ``` 134 | 3. run following command:
135 | ```bash 136 | pip install virtualenv 137 | ``` 138 | 4. run following command:
139 | ```bash 140 | virtualenv venv --python=python 141 | ``` 142 |
143 | 144 |
145 | Manjaro: Install Python3 pip, virtualenv 146 | 147 |
148 | All python3 packages are prefixed python-, whereas python2 packages are prefixed python2-. 149 | 150 | 1. The package is called python-pip. First check if it's already installed: `pacman -Qs python-pip` 151 | 152 | 2. If there is no output after running the above command, that means that the package is not installed. In order to install it, run: `sudo pacman -Syu python-pip` 153 | 154 | 3. In order to install `virtualenv` run: `pip install virtualenv` 155 | 156 | 4. You also need to run `sudo /usr/bin/easy_install virtualenv` which puts it in `/usr/local/bin/`. 157 | 158 | [Source](https://stackoverflow.com/questions/31133050/virtualenv-command-not-found) 159 | 160 |
161 | 162 |
163 | Setting up Python3 virtual environment on Linx (Ubuntu) machine 164 | 165 | ### How to install virtualenv 166 | 167 | 1. Install **pip** first 168 | ```bash 169 | sudo apt-get install python3-pip 170 | ``` 171 | 172 | 2. Then install **virtualenv** using pip3 173 | ```bash 174 | sudo pip3 install virtualenv 175 | ``` 176 | 177 | 3. Now create a virtual environment 178 | ```bash 179 | virtualenv venv 180 | ``` 181 | >you can use any name insted of **venv** 182 | 183 | 4. You can also use a Python interpreter of your choice: 184 | 185 | ```bash 186 | virtualenv -p /usr/bin/python2.7 venv 187 | ``` 188 | 189 | 5. Active your virtual environment: 190 | 191 | ```bash 192 | source venv/bin/activate 193 | ``` 194 | 195 | 6. Using fish shell: 196 | 197 | ```bash 198 | source venv/bin/activate.fish 199 | ``` 200 | 201 | 7. To deactivate: 202 | 203 | ```bash 204 | deactivate 205 | ``` 206 | 207 | 8. Create virtualenv using Python3: 208 | 209 | ```bash 210 | virtualenv -p python3 myenv 211 | ``` 212 | 213 | 9. Instead of using virtualenv you can use this command in Python3: 214 | 215 | ```bash 216 | python3 -m venv myenv 217 | ``` 218 | 219 | [Source](https://gist.github.com/frfahim/73c0fad6350332cef7a653bcd762f08d) 220 |
221 | 222 |
223 | Activate Virtual Environment 224 | 225 | In a newly created virtualenv there will be a bin/activate shell script. For Windows systems, activation scripts are provided for CMD.exe and Powershell. 226 | 227 | 1. Open Terminal 228 | 2. Run: \path\to\env\Scripts\activate 229 | 230 | [Source](https://pypi.org/project/virtualenv/1.8.2/) 231 |
232 | 233 |
234 | Auto generate requirements.txt 235 | 236 | Any application typically has a set of dependencies that are required for that application to work. The requirements file is a way to specify and install specific set of package dependencies at once.
237 | Use pip’s freeze command to generate a requirements.txt file for your project: 238 | ```bash 239 | pip freeze > requirements.txt 240 | ``` 241 | 242 | If you save this in requirements.txt, you can follow this guide: [PyCharm - Manage dependencies using requirements.txt](https://www.jetbrains.com/help/pycharm/managing-dependencies.html), or you can:
243 | 244 | ```bash 245 | pip install -r requirements.txt 246 | ``` 247 | [Source](https://www.idiotinside.com/2015/05/10/python-auto-generate-requirements-txt/) 248 |
249 | 250 |
251 | Install Python 3 virtualenv on Ubuntu 252 | 253 | Step by step: 254 | 255 | ```bash 256 | # Step 1: Update your repositories 257 | sudo apt-get update 258 | 259 | # Step 2: Install pip for Python 3 260 | sudo apt-get install build-essential libssl-dev libffi-dev python-dev 261 | sudo apt install python3-pip 262 | 263 | # Step 3: Use pip to install virtualenv 264 | sudo pip3 install virtualenv 265 | 266 | # Step 4: Launch your Python 3 virtual environment, here the name of my virtual environment will be `venv` 267 | virtualenv -p python3 venv 268 | 269 | # Step 5: Activate your new Python 3 environment. There are two ways to do this 270 | . venv/bin/activate # or source env3/bin/activate which does exactly the same thing 271 | 272 | # you can make sure you are now working with Python 3 273 | python -- version 274 | 275 | # this command will show you what is going on: the python executable you are using is now located inside your virtualenv repository 276 | which python 277 | 278 | # Step 6: code your stuff 279 | 280 | # Step 7: done? leave the virtual environment 281 | deactivate 282 | ``` 283 | 284 | [Source](https://naysan.ca/2019/08/05/install-python-3-virtualenv-on-ubuntu/) 285 | 286 |
287 | 288 |
289 | Install Docker Engine on Ubuntu 290 | 291 | 1. Older versions of Docker were called `docker`, `docker.io`, or `docker-engine`. If these are installed, uninstall them: 292 | ```bash 293 | sudo apt-get remove docker docker-engine docker.io containerd runc 294 | ``` 295 | 296 | 2. Update the `apt` package index and install packages to allow `apt` to use a repository over HTTPS: 297 | ```bash 298 | sudo apt-get update 299 | 300 | sudo apt-get install \ 301 | apt-transport-https \ 302 | ca-certificates \ 303 | curl \ 304 | gnupg-agent \ 305 | software-properties-common 306 | ``` 307 | 308 | 3. Add Docker’s official GPG key: 309 | ```bash 310 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 311 | 312 | # Verify that you now have the key with the fingerprint 313 | sudo apt-key fingerprint 0EBFCD88 314 | ``` 315 | 316 | 4. Use the following command to set up the `stable` repository: 317 | ```bash 318 | sudo add-apt-repository \ 319 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 320 | (lsb_release -cs) \ 321 | stable" 322 | ``` 323 | 324 | 5. Update the `apt` package index, and install the latest version of Docker Engine and container: 325 | ```bash 326 | sudo apt-get update 327 | sudo apt-get install docker-ce docker-ce-cli containerd.io 328 | ``` 329 | 330 | 6. Verify that Docker Engine is installed correctly by running the `hello-world` image: 331 | ```bash 332 | sudo docker run hello-world 333 | ``` 334 | This command downloads a test image and runs it in a container. When the container runs, it prints an informational message and exits. 335 | 336 | [Source](https://docs.docker.com/engine/install/ubuntu/) 337 | 338 |
339 | 340 |
341 | Install Docker Compose on Linux systems 342 | 343 | Step-by-step instructions are included below: 344 | 345 | 1. Run this command to download the current stable release of Docker Compose: 346 | ```bash 347 | sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 348 | ``` 349 | 350 | 2. Apply executable permissions to the binary: 351 | ```bash 352 | sudo chmod +x /usr/local/bin/docker-compose 353 | ``` 354 | 355 | 3. Test the installation: 356 | ```bash 357 | docker-compose --version 358 | ``` 359 | 360 | [Source](https://docs.docker.com/compose/install/) 361 |
362 | 363 |
364 | error: RPC failed; curl 56 Recv failure: Connection was reset 365 | 366 | 1. Open Git Bash 367 | 2. Run: "git config --global http.postBuffer 157286400" 368 | 369 | [Source](https://stackoverflow.com/questions/36940425/gitlab-push-failed-error) 370 |
371 | 372 |
373 | How to fix in case .gitignore is ignored by Git 374 | 375 | Even if you haven't tracked the files so far, Git seems to be able to "know" about them even after you add them to .gitignore
376 | 377 | **NOTE:** 378 | 379 | - First commit your current changes, or you will lose them. 380 | - Then run the following commands from the top folder of your Git repository: 381 | 382 | ```bash 383 | git rm -r --cached . 384 | git add . 385 | git commit -m "fixed untracked files" 386 | ``` 387 |
388 | 389 |
390 | How to generate Allure report with history trends (Windows OS) 391 | 392 |
Step by step: 393 | 394 | 1. Run tests from pytest using following arguments: -v --alluredir=allure-results 395 | 2. Copy '.\allure-report\history\' folder into '.\allure-results\history\' 396 | 3. Run: allure generate .\allure-results\ -o .\allure-report\ --clean 397 | 4. Following output should appear: Report successfully generated to .\allure-report 398 | 5. Run: allure open .\allure-report\ 399 | 400 | [Source](https://github.com/allure-framework/allure2/issues/813) 401 |
402 | 403 |
404 | Sphinx Documentation Set Up 405 | 406 |
Step by step: 407 | 408 | 1. Create docs directory 409 | 410 | 2. Open cmd > Go to docs directory 411 | 412 | 3. cmd > Run: sphinx-quickstart. **Note:** run with default answers 413 | 414 | 4. Go to docs/conf.py 415 | 416 | 5. Uncomment following lines: 417 | ```python 418 | import os 419 | import sys 420 | sys.path.insert(0, os.path.abspath('.')) 421 | ``` 422 | 6. Update extensions list as following: 423 | ```python 424 | extensions = ['sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinx.ext.autodoc'] 425 | ``` 426 | 7. Update template as following: 427 | ```python 428 | html_theme = 'sphinx_rtd_theme' 429 | ``` 430 | 8. Update sys.path.insert as following: 431 | ```python 432 | sys.path.insert(0, os.path.abspath('..')) 433 | ``` 434 | 9. Go to docs/index.rst > add modules, see example below: 435 | ```bash 436 | 437 | .. toctree:: 438 | :maxdepth: 2 439 | :caption: Contents: 440 | 441 | modules 442 | ``` 443 | 10. Open cmd > run: 444 | ```python 445 | sphinx-apidoc -o . .. 446 | ``` 447 | 11. cmd > Run: make html 448 | 12. Install html template: 449 | ```python 450 | pip install sphinx_rtd_theme 451 | ``` 452 | 453 | [Video Tutorial](https://www.youtube.com/watch?v=b4iFyrLQQh4) 454 | [Sphinx Documentation](https://www.sphinx-doc.org/en/master/usage/quickstart.html) 455 | [More Info](https://stackoverflow.com/questions/13516404/sphinx-error-unknown-directive-type-automodule-or-autoclass) 456 |
457 | 458 |
459 | Auto-Generated Python Documentation with Sphinx 460 | 461 |
Step by step: 462 | 463 | 1. Open CMD 464 | 2. Go to docs directory 465 | 3. Run: make clean 466 | 4. Run: make html 467 | 468 | [Source](https://www.youtube.com/watch?v=b4iFyrLQQh4) 469 |
470 | -------------------------------------------------------------------------------- /app/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | migrations, 4 | __pycache__, 5 | manage.py, 6 | settings.py -------------------------------------------------------------------------------- /app/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/app/app/__init__.py -------------------------------------------------------------------------------- /app/app/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for app project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.13. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '-j7k8ou#iudvwc+(gp)62e)z^%2nh_z#np@f5mg(a+0!zs)dx$' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'core', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'app.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'app.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'UTC' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | 123 | AUTH_USER_MODEL = 'core.User' 124 | -------------------------------------------------------------------------------- /app/app/urls.py: -------------------------------------------------------------------------------- 1 | """app URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('core.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /app/app/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for app project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /app/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/app/core/__init__.py -------------------------------------------------------------------------------- /app/core/admin.py: -------------------------------------------------------------------------------- 1 | # from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /app/core/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-08-08 04:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ('auth', '0011_update_proxy_permissions'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='User', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('password', models.CharField(max_length=128, verbose_name='password')), 20 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 21 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 22 | ('email', models.EmailField(max_length=255, unique=True)), 23 | ('name', models.CharField(max_length=255)), 24 | ('is_active', models.BooleanField(default=True)), 25 | ('is_staff', models.BooleanField(default=False)), 26 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 27 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 28 | ], 29 | options={ 30 | 'abstract': False, 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /app/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/app/core/migrations/__init__.py -------------------------------------------------------------------------------- /app/core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \ 3 | PermissionsMixin 4 | 5 | # Create your models here. 6 | 7 | MESSAGES = { 8 | 'invalid_email': 'ERROR: invalid email address is not allowed!', 9 | } 10 | 11 | 12 | class UserManager(BaseUserManager): 13 | 14 | def create_user(self, email, password=None, **extra_fields): 15 | """Create and saves a new user""" 16 | if not email: 17 | raise ValueError(MESSAGES['invalid_email']) 18 | user = self.model(email=self.normalize_email(email), 19 | **extra_fields) 20 | user.set_password(password) 21 | user.save(using=self._db) 22 | return user 23 | 24 | def create_superuser(self, email, password): 25 | """Create and save a new superuser""" 26 | user = self.create_user(email, password) 27 | user.is_superuser = True 28 | user.is_staff = True 29 | user.save(using=self._db) 30 | return user 31 | 32 | 33 | class User(AbstractBaseUser, PermissionsMixin): 34 | """Custom user model that supports user email instead of username""" 35 | email = models.EmailField(max_length=255, unique=True) 36 | name = models.CharField(max_length=255) 37 | is_active = models.BooleanField(default=True) 38 | is_staff = models.BooleanField(default=False) 39 | 40 | objects = UserManager() 41 | USERNAME_FIELD = 'email' 42 | -------------------------------------------------------------------------------- /app/core/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/app/core/tests/__init__.py -------------------------------------------------------------------------------- /app/core/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth import get_user_model 3 | from core.models import MESSAGES 4 | 5 | 6 | class ModelTests(TestCase): 7 | 8 | def test_create_user_with_email_successful(self): 9 | """Test creating a new user with email is successful""" 10 | # Arrange 11 | email = "test@testemail.com" 12 | password = 'password123' 13 | # Act 14 | user = get_user_model().objects.create_user( 15 | email=email, 16 | password=password 17 | ) 18 | # Assert 19 | # compare emails 20 | self.assertEqual(user.email, email) 21 | # compare passwords 22 | self.assertTrue(user.check_password(password)) 23 | 24 | def test_new_user_email_normalized(self): 25 | """Test the mmail for a new user is normalized""" 26 | # Arrange 27 | email = "tEst@teStemaiL.cOM" 28 | # Act 29 | user = get_user_model().objects.create_user( 30 | email=email, 31 | password='password123' 32 | ) 33 | # Assert 34 | self.assertEqual(user.email, 'tEst@testemail.com') 35 | 36 | def test_new_user_invalid_email(self): 37 | """Test creating new user with no email raises error""" 38 | with self.assertRaises(ValueError): 39 | # Arrange & Act 40 | get_user_model().objects.create_user( 41 | email=None, 42 | password='password123' 43 | ) 44 | 45 | def test_invalid_email_error(self): 46 | """Verify 'invalid_email' error""" 47 | # Arrange & Act 48 | try: 49 | get_user_model().objects.create_user( 50 | email=None, 51 | password='password123' 52 | ) 53 | except ValueError as e: 54 | self.assertEqual(e.__str__(), MESSAGES['invalid_email']) 55 | 56 | def test_create_new_superuser(self): 57 | """Test creating a new superuser""" 58 | user = get_user_model().objects.create_superuser( 59 | email='superuser@test.com', 60 | password='password123' 61 | ) 62 | 63 | self.assertTrue(user.is_superuser) 64 | self.assertTrue(user.is_staff) 65 | -------------------------------------------------------------------------------- /app/core/urls.py: -------------------------------------------------------------------------------- 1 | """app URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | # from django.urls import path, include 17 | 18 | urlpatterns = [ 19 | ] 20 | -------------------------------------------------------------------------------- /app/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | ports: 8 | - "8000:8000" 9 | volumes: 10 | - ./app:/app 11 | command: > 12 | sh -c "python manage.py runserver 0.0.0.0:8000" 13 | -------------------------------------------------------------------------------- /img/README.md: -------------------------------------------------------------------------------- 1 | ## Images/Icons 2 | 3 |
4 | 5 |
6 | 7 | A good article is the combination of text, images, and other various multi-media elements like videos, charts, slides, etc.
8 | Visuals give us a better understanding of not just the message, but what’s behind the message — explained easier, faster, and clearer than just a cluster of written words. 9 | 10 | Sources: 11 | 12 | - [Iconfinder](https://www.iconfinder.com) 13 | - [5 Powerful Reasons To Add Images To Your Blog Posts](https://www.shoutmeloud.com/4-ways-how-images-enhance-your-blog.html) 14 | - [7 Reasons Why Content Needs Amazing Images, Videos & Visuals](https://www.searchenginejournal.com/why-content-needs-amazing-images-videos-visuals/268911/#close) -------------------------------------------------------------------------------- /img/build-devops-automation-recycle_code-refresh_settings-preferences-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/build-devops-automation-recycle_code-refresh_settings-preferences-512.png -------------------------------------------------------------------------------- /img/django-icon-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/django-icon-0.jpg -------------------------------------------------------------------------------- /img/docker-icon-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/docker-icon-14.jpg -------------------------------------------------------------------------------- /img/iconfinder_api-code-window_532742.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/iconfinder_api-code-window_532742.png -------------------------------------------------------------------------------- /img/iconfinder_application-x-python_8974.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/iconfinder_application-x-python_8974.png -------------------------------------------------------------------------------- /img/iconfinder_folder_images2_2319.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/iconfinder_folder_images2_2319.png -------------------------------------------------------------------------------- /img/python-icon-18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/python-icon-18.jpg -------------------------------------------------------------------------------- /img/rest-api-icon-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/rest-api-icon-8.jpg -------------------------------------------------------------------------------- /img/travis-ci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostan/Build_Backend_REST_API_with_Python_and_Django/6d47cc5c3a92584e4fbf9546c0bb585f712ef5bf/img/travis-ci.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.13 2 | djangorestframework==3.11.1 3 | flake8==3.8.3 4 | mccabe==0.6.1 5 | pycodestyle==2.6.0 6 | pyflakes==2.2.0 7 | pytz==2020.1 8 | sqlparse==0.3.1 9 | --------------------------------------------------------------------------------