├── fast_pagination ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── generate_users.py ├── apps.py ├── tests.py └── helpers.py ├── pagination_app ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── setup.py ├── MANIFEST.in ├── requirements.txt ├── manage.py ├── LICENSE ├── setup.cfg ├── .gitignore └── README.md /fast_pagination/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pagination_app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fast_pagination/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fast_pagination/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENCE 2 | include README.md 3 | recursive-include docs * -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.0.4 2 | psycopg2-binary==2.8.4 3 | pytest==5.4.2 4 | pytest-benchmark==3.2.3 5 | pytest-django==3.9.0 6 | -------------------------------------------------------------------------------- /fast_pagination/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FastPaginationConfig(AppConfig): 5 | name = 'fast_pagination' -------------------------------------------------------------------------------- /pagination_app/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for pagination_app project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pagination_app.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /pagination_app/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for pagination_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/3.0/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', 'pagination_app.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /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', 'pagination_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 | -------------------------------------------------------------------------------- /pagination_app/urls.py: -------------------------------------------------------------------------------- 1 | """pagination_app URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/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 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /fast_pagination/management/commands/generate_users.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from django.core.management.base import BaseCommand 3 | from django.contrib.auth.models import User 4 | 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Command(BaseCommand): 10 | 11 | def add_arguments(self, parser): 12 | parser.add_argument("user_count", type=int) 13 | 14 | def handle(self, *args, **options): 15 | batch_size = 1000 16 | user_count = options["user_count"] 17 | users = [] 18 | for i in range(1, user_count + 1): 19 | username = f"user_{i}" 20 | user = User(username=username) 21 | user.set_unusable_password() 22 | users.append(user) 23 | if i % batch_size == 0: 24 | logger.info("User #%s created", i) 25 | User.objects.bulk_create( 26 | users, batch_size=batch_size, ignore_conflicts=True) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tolga Bilbey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = django-fast-paginator 3 | version = v1.0.4 4 | description = A Django app to paginate querysets faster. 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | author = Tolga Bilbey 8 | author_email = bilbeyt@gmail.com 9 | license = MIT 10 | url = https://github.com/bilbeyt/django-fast-pagination 11 | download_url = https://github.com/bilbeyt/django-fast-pagination/archive/v1.0.4.tar.gz 12 | keywords = 13 | django 14 | pagination 15 | optimization 16 | platform = any 17 | classifiers = 18 | Environment :: Web Environment 19 | Framework :: Django 20 | Framework :: Django :: 2.2 21 | Framework :: Django :: 3.0 22 | Intended Audience :: Developers 23 | License :: OSI Approved :: MIT License 24 | Operating System :: OS Independent 25 | Programming Language :: Python 26 | Programming Language :: Python :: 3 27 | Programming Language :: Python :: 3 :: Only 28 | Programming Language :: Python :: 3.6 29 | Programming Language :: Python :: 3.7 30 | Programming Language :: Python :: 3.8 31 | Topic :: Internet :: WWW/HTTP 32 | Topic :: Internet :: WWW/HTTP :: Dynamic Content 33 | 34 | [options] 35 | include_package_data = true 36 | packages = 37 | fast_pagination 38 | python_requires = >= 3.0.0 39 | setup_requires = 40 | setuptools 41 | test_requires = 42 | pytest 43 | pytest-benchmark 44 | pytest-django 45 | Django 46 | -------------------------------------------------------------------------------- /.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 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /fast_pagination/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import logging 4 | import pytest 5 | from django.conf import settings 6 | from django.contrib.auth.models import User 7 | from django.core.paginator import ( 8 | InvalidPage, PageNotAnInteger, EmptyPage, Paginator) 9 | 10 | from fast_pagination.helpers import FastPaginator 11 | 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class FastObjectPaginatorTestMethods(unittest.TestCase): 18 | 19 | def test_invalid_page(self): 20 | example_list = ['1', '2', '3'] 21 | with self.assertRaises(InvalidPage): 22 | paginator = FastPaginator( 23 | example_list, 2, 24 | cache_key="invalid_page_test_key") 25 | paginator.page(3) 26 | 27 | def test_page_not_an_integer(self): 28 | example_list = ['1', '2', '3'] 29 | with self.assertRaises(PageNotAnInteger): 30 | paginator = FastPaginator( 31 | example_list, 2, 32 | cache_key="not_integer_test_key") 33 | paginator.page(None) 34 | 35 | def test_page_empty(self): 36 | example_list = [] 37 | with self.assertRaises(EmptyPage): 38 | paginator = FastPaginator( 39 | example_list, 2, 40 | cache_key="empty_page_test_key") 41 | paginator.page(-1) 42 | 43 | def test_key_not_found(self): 44 | example_list = [] 45 | with self.assertRaises(ValueError): 46 | paginator = FastPaginator( 47 | example_list, 2) 48 | 49 | 50 | @pytest.fixture(scope='session') 51 | def django_db_setup(): 52 | settings.DATABASES['default'] = { 53 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 54 | 'NAME': 'paginator', 55 | 'USER': 'postgres', 56 | 'HOST': 'localhost', 57 | 'PORT': 5432, 58 | } 59 | 60 | 61 | def speed_test(queryset, paginator_cls): 62 | paginator = paginator_cls(queryset, 1000) 63 | for page in paginator.page_range: 64 | objs = paginator.page(page).object_list\ 65 | .values_list('username', flat=True) 66 | usernames = ",".join(objs) 67 | logger.info("%s\n", usernames) 68 | 69 | 70 | @pytest.mark.django_db 71 | def test_django_paginator_speed(benchmark): 72 | queryset = User.objects.all() 73 | benchmark(speed_test, queryset, Paginator) 74 | 75 | 76 | @pytest.mark.django_db 77 | def test_fast_paginator_speed(benchmark): 78 | queryset = User.objects.all() 79 | benchmark(speed_test, queryset, FastPaginator) 80 | 81 | 82 | if __name__ == '__main__': 83 | unittest.main() 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Issues](https://img.shields.io/github/issues/bilbeyt/django-fast-pagination) 2 | ![Forks](https://img.shields.io/github/forks/bilbeyt/django-fast-pagination) 3 | ![Stars](https://img.shields.io/github/stars/bilbeyt/django-fast-pagination) 4 | ![License](https://img.shields.io/github/license/bilbeyt/django-fast-pagination) 5 | ![Twitter](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2Fbilbeyt%2Fdjango-fast-pagination) 6 | 7 | 8 | ## Fast Paginator for Django 9 | Simple speedy pagination over your large database tables. 10 | 11 | ## Features 12 | 13 | **Simple Integration** 14 | 15 | FastPaginator API is compatible with Django's built-in pagination library. Only change your import statements then you're ready. 16 | 17 | **Better SQL Queries** 18 | 19 | Django's built-in pagination system builds SQL queries that have offset and limit clauses. FastPagination does not use them. 20 | 21 | **Built-in Cache System** 22 | 23 | FastPaginator has a built-in cache system. It does not cache QuerySets but caches primary keys of object lists. This provides speedup for pagination progress. 24 | 25 | ## Usage Advices 26 | 27 | 1. Use this package with cache servers like redis. 28 | 2. This package for large database tables. 29 | 3. If the queryset required is complex, this paginator can be helpful, since it first gets all the primary keys and use them to filter further. 30 | 31 | ## Quick Start 32 | 33 | 1. Add "fast_pagination" to your INSTALLED_APPS setting like this: 34 | ```python 35 | INSTALLED_APPS = [ 36 | ... 37 | 'fast_pagination' 38 | ] 39 | ``` 40 | 2. In Django settings, you can set FAST_PAGINATION_TIMEOUT variable to invalidate cache. Default value is 1 hour. 41 | 3. In Django settings, you can set FAST_PAGINATION_PREFIX variable to use in cache keys. Default values is 'fastpagination'. 42 | 4. Import FastPaginator like this: 43 | ```python 44 | from fast_pagination.helpers import FastPaginator 45 | ``` 46 | 5. Then, you are ready. All you have to do is give your queryset and number of entries when creating FastPaginator object. 47 | 48 | ## To Run Tests 49 | 50 | 1. Create a dummy project. 51 | 2. Run following command. 52 | ```python 53 | ./manage.py test fast_pagination.tests 54 | ``` 55 | 56 | ## Benchmark 57 | 58 | This benchmark is executed on Postgres9.6 with a table that has 1000000 rows, and fetched only one field. Results can be seen below. 59 | 60 | | Paginator | Min | Max | Mean | StdDev | Median | 61 | |---------------|------------------|------------------|------------------|---------------|------------------| 62 | | Django | 93.5535 (1.53) | 95.7217 (1.54) | 94.7340 (1.53) | 0.9755 (2.32) | 94.9046 (1.54) | 63 | | FastPaginator | 61.1488 (1.0) | 62.3316 (1.0) | 61.7489 (1.0) | 0.4205 (1.0) | 61.7649 (1.0) | 64 | 65 | 66 | You can also run benchmark tests following instructions: 67 | 68 | 1. Run migrations if app needs using 69 | ```bash 70 | ./manage.py migrate 71 | ``` 72 | 2. Call generate_users command from command line using 73 | ```bash 74 | ./manage.py generate_users 1000000 75 | ``` 76 | 3. Run tests using 77 | ```bash 78 | pytest fast_pagination/tests.py 79 | ``` -------------------------------------------------------------------------------- /fast_pagination/helpers.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import hashlib 3 | 4 | from django.core.paginator import Paginator, Page 5 | from django.core.cache import cache 6 | from django.conf import settings 7 | from django.db.models.query import QuerySet 8 | 9 | 10 | class BaseFastPaginator(metaclass=abc.ABCMeta): 11 | TIMEOUT = getattr( 12 | settings, "FAST_PAGINATION_TIMEOUT", 3600) 13 | PREFIX = getattr( 14 | settings, "FAST_PAGINATION_PREFIX", "fastpagination" 15 | ) 16 | 17 | @abc.abstractmethod 18 | def count(): 19 | raise NotImplementedError 20 | 21 | @abc.abstractmethod 22 | def page(self, number): 23 | raise NotImplementedError 24 | 25 | @abc.abstractmethod 26 | def _get_page(self, *args, **kwargs): 27 | raise NotImplementedError 28 | 29 | 30 | class FastQuerysetPaginator(Paginator, BaseFastPaginator): 31 | def __init__(self, object_list, per_page, orphans=0, 32 | allow_empty_first_page=True): 33 | super().__init__(object_list, per_page, orphans, 34 | allow_empty_first_page) 35 | encoded_query = str(object_list.query).encode('utf-8') 36 | raw_query_key = str( 37 | hashlib.md5(encoded_query).hexdigest()) 38 | self.cache_pks_key = f"{self.PREFIX}:pks:{raw_query_key}" 39 | self.cache_count_key = f"{self.PREFIX}:count:{raw_query_key}" 40 | 41 | @property 42 | def count(self): 43 | result = cache.get(self.cache_count_key) 44 | if result is None: 45 | result = self.object_list.count() 46 | cache.set(self.cache_count_key, result, 47 | timeout=self.TIMEOUT) 48 | return result 49 | 50 | @property 51 | def pks(self): 52 | result = cache.get(self.cache_pks_key) 53 | if result is None: 54 | result = list( 55 | self.object_list.values_list('pk', flat=True)) 56 | cache.set(self.cache_pks_key, result, 57 | timeout=self.TIMEOUT) 58 | return result 59 | 60 | def page(self, number): 61 | number = self.validate_number(number) 62 | bottom = (number - 1) * self.per_page 63 | top = bottom + self.per_page 64 | if top + self.orphans >= self.count: 65 | top = self.count 66 | pks = self.pks[bottom:top] 67 | object_list = self.object_list.filter(pk__in=pks) 68 | return self._get_page(object_list, number, self) 69 | 70 | def _get_page(self, *args, **kwargs): 71 | return FastQuerysetPage(*args, **kwargs) 72 | 73 | 74 | class FastObjectPaginator(BaseFastPaginator, Paginator): 75 | 76 | def __init__(self, object_list, per_page, orphans=0, 77 | allow_empty_first_page=True, cache_key=None): 78 | if cache_key is None: 79 | raise ValueError("You should provide cache_key" + 80 | "for your results") 81 | super().__init__(object_list, per_page, orphans, 82 | allow_empty_first_page) 83 | self.cache_count_key = f"{self.PREFIX}:count:{cache_key}" 84 | 85 | def page(self, number): 86 | number = self.validate_number(number) 87 | bottom = (number - 1) * self.per_page 88 | top = bottom + self.per_page 89 | if top + self.orphans >= self.count: 90 | top = self.count 91 | object_list = self.object_list[bottom:top] 92 | return self._get_page(object_list, number, self) 93 | 94 | @property 95 | def count(self): 96 | result = cache.get(self.cache_count_key) 97 | if result is None: 98 | result = len(self.object_list) 99 | cache.set(self.cache_count_key, result, 100 | timeout=self.TIMEOUT) 101 | return result 102 | 103 | def _get_page(self, *args, **kwargs): 104 | return FastObjectPage(*args, **kwargs) 105 | 106 | 107 | class FastPaginator(): 108 | 109 | def __new__(cls, *args, **kwargs): 110 | object_list = args[0] 111 | if isinstance(object_list, QuerySet): 112 | return FastQuerysetPaginator(*args, **kwargs) 113 | return FastObjectPaginator(*args, **kwargs) 114 | 115 | 116 | class FastQuerysetPage(Page): 117 | def __len__(self): 118 | return len(self.paginator.ids) 119 | 120 | 121 | class FastObjectPage(Page): 122 | def __len__(self): 123 | return len(self.paginator.object_list) 124 | -------------------------------------------------------------------------------- /pagination_app/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for pagination_app project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | import sys 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'y4qx=gte724g(x032&=n2fd*4kjdf#10a%(=@ka!2s8mb6!rx6' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'fast_pagination' 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'pagination_app.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'pagination_app.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 81 | 'NAME': 'paginator', 82 | 'USER': 'postgres', 83 | 'HOST': 'localhost', 84 | 'PORT': 5432, 85 | } 86 | } 87 | 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 91 | 92 | AUTH_PASSWORD_VALIDATORS = [ 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 104 | }, 105 | ] 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 110 | 111 | LANGUAGE_CODE = 'en-us' 112 | 113 | TIME_ZONE = 'UTC' 114 | 115 | USE_I18N = True 116 | 117 | USE_L10N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 124 | 125 | STATIC_URL = '/static/' 126 | 127 | LOGGING = { 128 | 'version': 1, 129 | 'disable_existing_loggers': False, 130 | 'formatters': { 131 | 'default': { 132 | 'format': '[ %(asctime)s - %(levelname)s ] %(message)s' 133 | } 134 | }, 135 | 'handlers': { 136 | 'default': { 137 | 'level': 'DEBUG', 138 | 'class': 'logging.StreamHandler', 139 | 'stream': sys.stdout, 140 | 'formatter': 'default' 141 | }, 142 | }, 143 | 'loggers': { 144 | '': { 145 | 'handlers': ['default'], 146 | 'propagate': True, 147 | 'level': 'INFO', 148 | }, 149 | } 150 | } --------------------------------------------------------------------------------