diff --git a/.env b/.env new file mode 100644 index 00000000..02a5ae14 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +SECRET_KEY= +DEBUG= +ALLOWED_HOSTS= + +DB_NAME= +DB_USER= +DB_PASS= +DB_HOST= +DB_PORT= + +TIME_ZONE= + +ADMIN_USERNAME= +ADMIN_PASSWORD= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..717fb5cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# IDE +.idea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..391e515d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.10 +LABEL MAINTAINER = "Mahdi Namaki | mavenium@gmail.com" + +ENV PYTHONUNBUFFERD 1 + +RUN mkdir /project +WORKDIR /project +COPY /src /project + +ADD requirements_base.txt /project + +RUN apt-get -y update +RUN apt-get -y upgrade + +RUN pip install --upgrade pip +RUN pip install --upgrade -r requirements_base.txt + +RUN apt-get clean \ No newline at end of file diff --git a/README.md b/README.md index a3318cbd..93006992 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,38 @@ -# django-challenge - - -The volleyball Federation decided to use an online selling platform for the next season, and our company has been chosen for implementing that. - -# Requirements - -Our system should have REST APIs for the following tasks: - -- User signup and login -- Adding a new stadium -- Defining matches -- Defining the place of seats for each match -- Buying seats of a match (There is no need for using a payment gateway) - -# Implementation details - -We don't need a GUI for this system. You can use the Django admin. -Try to write your code as **reusable** and **readable** as possible. Also, don't forget to **document your code** and clear the reasons for all your decisions in the code. -Using API documentation tools is a plus. -Don't forget that many people trying to buy tickets for a match. So try to implement your code in a way that could handle the load. If your solution is not sample enough for implementing fast, you can just describe it in your documents. - -Please fork this repository and add your code to that. Don't forget that your commits are so important. So be sure that you're committing your code often with a proper commit message. +### How to execute via docker? +- Install `docker` and `docker-compose` on server +- Clone the project `git clone https://@github.com/mavenium/django-challenge.git` +- Go to the project directory `cd django-challenge` +- Run `docker-compose -f docker-compose-dev.yml up -d --force-recreate --build` command +- Then go to the `http://127.0.0.1:8000/` in your browser + +### How to execute in local? +- Clone the project `git clone https://@github.com/mavenium/django-challenge.git` +- Go to the project directory `cd django-challenge/src` +- Create a new environment and active that +- Run the `pip install --upgrade -r ../requirements_development.txt` to install the requirements +- Then create a new database in the Postgresql like `ticket_sales_system` and add the database name to the `core/settings.py` +- Then run the `python manage.py migrate` to create the tables +- Then run the `python manage.py initialize_admin` to create the default admin +- After that run the `python manage.py runserver` + +### Additional Notes +#### What is the username and password for the default superuser? +- Username is `admin@ticketing.sample` and password is `admin@control*987` +#### How to run the tests ? +- By running the `python manage.py test` command in the running environment (local or docker container) +#### What are the `swagger` and `redoc` urls ? +- The `swagger` url is `http://127.0.0.1:8000/api/v1/swagger/` +- The `redoc` url is `http://127.0.0.1:8000/api/v1/redoc/` +#### Is the postman collection also offered? +- Yes, `postman_collection.json` +#### How to run on the production server ? +- This project is not 100% ready for production mode, but you can change the `Dockerfile` and `docker-compose-dev.yml` for this purpose +- The `.env` file is available in the project +#### Where is the documentation? +- It located in the `docs/_build/html/index.html` +#### Anything else ? +- We need the Celery for creating the scheduled task in order to remove the ticket object with reserved status after 5 or 10 minutes of creating it in order to make tickets available for buying for people. +- A scheduled task system is not developed due to the experimental of the project +- There are 44 tests in the project. Those are not enough, but they show my ability to write tests on two levels. +- A caching system is needed in production mode, it has not been implemented due to the experimental of the project +- The project has been implemented with the ability to translate into other languages \ No newline at end of file diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 00000000..6096a060 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,57 @@ +version: '3.3' + +services: + postgresql: + image: postgres:latest + hostname: postgresql + container_name: postgresql + volumes: + - postgresql_volume:/data + restart: always + ports: + - "5432:5432" + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + networks: + - project_network + + project: + build: + context: . + dockerfile: Dockerfile + command: sh -c ' + python manage.py wait_for_db && + python manage.py migrate && + python manage.py migrate --run-syncdb && + python manage.py collectstatic --noinput && + python manage.py initialize_admin && + python manage.py runserver 0.0.0.0:8000' + hostname: project + container_name: project + volumes: + - ./src:/project + - statics_volume:/project/statics + restart: always + environment: + - DB_NAME=postgres + - DB_HOST=postgresql + - DB_USER=postgres + - DB_PASS=postgres + ports: + - "8000:8000" + networks: + - project_network + depends_on: + - postgresql + +volumes: + postgresql_volume: + external: false + statics_volume: + external: false + +networks: + project_network: + external: false \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_build/doctrees/_rst/api.doctree b/docs/_build/doctrees/_rst/api.doctree new file mode 100644 index 00000000..91df6a61 Binary files /dev/null and b/docs/_build/doctrees/_rst/api.doctree differ diff --git a/docs/_build/doctrees/_rst/api.tests.doctree b/docs/_build/doctrees/_rst/api.tests.doctree new file mode 100644 index 00000000..2d56d9f0 Binary files /dev/null and b/docs/_build/doctrees/_rst/api.tests.doctree differ diff --git a/docs/_build/doctrees/_rst/booking.doctree b/docs/_build/doctrees/_rst/booking.doctree new file mode 100644 index 00000000..edf8429f Binary files /dev/null and b/docs/_build/doctrees/_rst/booking.doctree differ diff --git a/docs/_build/doctrees/_rst/booking.models.doctree b/docs/_build/doctrees/_rst/booking.models.doctree new file mode 100644 index 00000000..1d11563a Binary files /dev/null and b/docs/_build/doctrees/_rst/booking.models.doctree differ diff --git a/docs/_build/doctrees/_rst/booking.serializers.doctree b/docs/_build/doctrees/_rst/booking.serializers.doctree new file mode 100644 index 00000000..35f05fd3 Binary files /dev/null and b/docs/_build/doctrees/_rst/booking.serializers.doctree differ diff --git a/docs/_build/doctrees/_rst/booking.views.doctree b/docs/_build/doctrees/_rst/booking.views.doctree new file mode 100644 index 00000000..de968da1 Binary files /dev/null and b/docs/_build/doctrees/_rst/booking.views.doctree differ diff --git a/docs/_build/doctrees/_rst/commands.doctree b/docs/_build/doctrees/_rst/commands.doctree new file mode 100644 index 00000000..9e3c9d5f Binary files /dev/null and b/docs/_build/doctrees/_rst/commands.doctree differ diff --git a/docs/_build/doctrees/_rst/core.doctree b/docs/_build/doctrees/_rst/core.doctree new file mode 100644 index 00000000..c2a0f861 Binary files /dev/null and b/docs/_build/doctrees/_rst/core.doctree differ diff --git a/docs/_build/doctrees/_rst/extensions.doctree b/docs/_build/doctrees/_rst/extensions.doctree new file mode 100644 index 00000000..5a04646b Binary files /dev/null and b/docs/_build/doctrees/_rst/extensions.doctree differ diff --git a/docs/_build/doctrees/_rst/manage.doctree b/docs/_build/doctrees/_rst/manage.doctree new file mode 100644 index 00000000..2a9e0289 Binary files /dev/null and b/docs/_build/doctrees/_rst/manage.doctree differ diff --git a/docs/_build/doctrees/_rst/modules.doctree b/docs/_build/doctrees/_rst/modules.doctree new file mode 100644 index 00000000..2cc7accd Binary files /dev/null and b/docs/_build/doctrees/_rst/modules.doctree differ diff --git a/docs/_build/doctrees/_rst/user_management.doctree b/docs/_build/doctrees/_rst/user_management.doctree new file mode 100644 index 00000000..d17aff23 Binary files /dev/null and b/docs/_build/doctrees/_rst/user_management.doctree differ diff --git a/docs/_build/doctrees/_rst/user_management.models.doctree b/docs/_build/doctrees/_rst/user_management.models.doctree new file mode 100644 index 00000000..e31ba100 Binary files /dev/null and b/docs/_build/doctrees/_rst/user_management.models.doctree differ diff --git a/docs/_build/doctrees/_rst/user_management.serializers.doctree b/docs/_build/doctrees/_rst/user_management.serializers.doctree new file mode 100644 index 00000000..39ade7c3 Binary files /dev/null and b/docs/_build/doctrees/_rst/user_management.serializers.doctree differ diff --git a/docs/_build/doctrees/_rst/user_management.tests.doctree b/docs/_build/doctrees/_rst/user_management.tests.doctree new file mode 100644 index 00000000..df3d04dd Binary files /dev/null and b/docs/_build/doctrees/_rst/user_management.tests.doctree differ diff --git a/docs/_build/doctrees/_rst/user_management.views.doctree b/docs/_build/doctrees/_rst/user_management.views.doctree new file mode 100644 index 00000000..3738cdbd Binary files /dev/null and b/docs/_build/doctrees/_rst/user_management.views.doctree differ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle new file mode 100644 index 00000000..8f7d928d Binary files /dev/null and b/docs/_build/doctrees/environment.pickle differ diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree new file mode 100644 index 00000000..d329c66a Binary files /dev/null and b/docs/_build/doctrees/index.doctree differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo new file mode 100644 index 00000000..e75bb2f7 --- /dev/null +++ b/docs/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 405e4bab41ed14b5f0c33c454d997738 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_modules/api/apps.html b/docs/_build/html/_modules/api/apps.html new file mode 100644 index 00000000..12f5628d --- /dev/null +++ b/docs/_build/html/_modules/api/apps.html @@ -0,0 +1,107 @@ + + + + + + api.apps — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for api.apps

+from django.apps import AppConfig
+
+
+
[docs]class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api'
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/api/tests/test_group.html b/docs/_build/html/_modules/api/tests/test_group.html new file mode 100644 index 00000000..b9978ac9 --- /dev/null +++ b/docs/_build/html/_modules/api/tests/test_group.html @@ -0,0 +1,238 @@ + + + + + + api.tests.test_group — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for api.tests.test_group

+from django.contrib.auth.models import Group, Permission
+from django.test import TestCase
+from django.urls import reverse
+
+from rest_framework import status
+from rest_framework.test import APIClient
+
+from user_management.tests.common_functions import sample_superuser, sample_group
+
+from user_management.serializers import GroupSerializer, PermissionSerializer
+
+GROUPS_LIST_URL = reverse('api:user_management:groups-list')
+
+
+
[docs]class PublicGroupsAPITests(TestCase): + """Test the public available groups API""" +
[docs] def setUp(self) -> None: + self.client = APIClient()
+ +
[docs] def test_login_required(self): + """Test that login is required for retrieving permissions""" + result = self.client.get(GROUPS_LIST_URL) + self.assertEqual(result.status_code, status.HTTP_401_UNAUTHORIZED)
+ + +
[docs]class PrivateGroupsAPITests(TestCase): + """Test the authorized user groups API""" +
[docs] def setUp(self) -> None: + self.super_user = sample_superuser() + self.client = APIClient() + self.client.force_authenticate(self.super_user)
+ +
[docs] def test_create_group_successful(self): + """Test creating a new group is successful""" + payload = { + 'name': 'Sample Group', + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['name'], payload['name'])
+ +
[docs] def test_create_group_with_permissions_successful(self): + """Test creating a new group with permissions is successful""" + permissions = Permission.objects.all() + payload = { + 'name': 'Sample Group', + 'permission_ids': sorted([permission.pk for permission in permissions]) + } + result = self.client.post(GROUPS_LIST_URL, payload) + permission_serializer = PermissionSerializer(permissions, many=True) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['name'], payload['name']) + self.assertEqual(result.data['permissions'], permission_serializer.data)
+ +
[docs] def test_create_group_with_invalid_name_unsuccessful(self): + """Test creating a new group with invalid name is unsuccessful""" + payload = { + 'name': '', + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_create_group_with_duplicate_name_unsuccessful(self): + """Test creating a new group with duplicate name is unsuccessful""" + instance = sample_group() + payload = { + 'name': instance.name, + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_create_group_with_invalid_permission_unsuccessful(self): + """Test creating a new group with invalid permission is unsuccessful""" + payload = { + 'name': 'Sample Group', + 'permission_ids': [123456] + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_retrieve_groups_successful(self): + """Test retrieving groups is successful""" + sample_group(name='Sample Group One') + sample_group(name='Sample Group Two') + result = self.client.get(GROUPS_LIST_URL) + groups = Group.objects.all() + serializer = GroupSerializer(groups, many=True) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['results'], serializer.data)
+ +
[docs] def test_retrieve_single_group(self): + """Test retrieving a single group is successful""" + instance = sample_group() + result = self.client.get(reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['name'], instance.name)
+ +
[docs] def test_update_group_successful(self): + """Test updating the group is successful""" + instance = sample_group() + payload = { + 'name': 'Update Group' + } + result = self.client.patch( + reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['name'], payload['name']) + self.assertNotEqual(result.data['name'], instance.name)
+ +
[docs] def test_update_group_unsuccessful(self): + """Test updating the group is unsuccessful""" + instance = sample_group() + payload = { + 'name': '', + } + result = self.client.patch( + reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_delete_group_successful(self): + """Test deleting the group is successful""" + instance = sample_group() + result = self.client.delete( + reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk}), + ) + self.assertEqual(result.status_code, status.HTTP_204_NO_CONTENT)
+ +
[docs] def test_delete_group_unsuccessful(self): + """Test deleting the group is unsuccessful""" + result = self.client.delete( + reverse('api:user_management:groups-detail', kwargs={'pk': 123}), + ) + self.assertEqual(result.status_code, status.HTTP_404_NOT_FOUND)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/api/tests/test_permission.html b/docs/_build/html/_modules/api/tests/test_permission.html new file mode 100644 index 00000000..a0ca3209 --- /dev/null +++ b/docs/_build/html/_modules/api/tests/test_permission.html @@ -0,0 +1,175 @@ + + + + + + api.tests.test_permission — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for api.tests.test_permission

+from django.contrib.auth.models import Permission
+from django.test import TestCase
+from django.urls import reverse
+
+from rest_framework import status
+from rest_framework.test import APIClient
+
+from user_management.tests.common_functions import sample_superuser
+
+from user_management.serializers import PermissionSerializer
+
+PERMISSIONS_LIST_URL = reverse('api:user_management:permissions-list')
+
+
+
[docs]class PublicPermissionsAPITests(TestCase): + """Test the public available permissions API""" +
[docs] def setUp(self) -> None: + self.client = APIClient()
+ +
[docs] def test_login_required(self): + """Test that login is required for retrieving permissions""" + result = self.client.get(PERMISSIONS_LIST_URL) + self.assertEqual(result.status_code, status.HTTP_401_UNAUTHORIZED)
+ + +
[docs]class PrivatePermissionsAPITests(TestCase): + """Test the authorized user permissions API""" +
[docs] def setUp(self) -> None: + self.super_user = sample_superuser() + self.client = APIClient() + self.client.force_authenticate(self.super_user)
+ +
[docs] def test_retrieve_permissions(self): + """Test retrieving the permissions""" + result = self.client.get(PERMISSIONS_LIST_URL) + permissions = Permission.objects.all().order_by('id')[:20] + serializer = PermissionSerializer(permissions, many=True) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['results'], serializer.data)
+ +
[docs] def test_retrieve_single_permission(self): + """Test retrieving a single permission""" + instance = Permission.objects.get(pk=1) + result = self.client.get(reverse('api:user_management:permissions-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['name'], instance.name) + self.assertEqual(result.data['codename'], instance.codename)
+ +
[docs] def test_create_permission_not_allowed(self): + """Test creating a new permission is not allowed""" + payload = { + 'name': 'Sample Permission', + 'content_type_id': 1, + 'codename': 'sample_permission', + } + result = self.client.post(PERMISSIONS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ +
[docs] def test_update_permission_not_allowed(self): + """Test updating the permission is not allowed""" + instance = Permission.objects.get(pk=1) + result = self.client.patch( + reverse('api:user_management:permissions-detail', kwargs={'pk': instance.pk}), + data={ + 'name': 'Other Name', + } + ) + self.assertEqual(result.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ +
[docs] def test_delete_permission_not_allowed(self): + """Test deleting the permission is not allowed""" + instance = Permission.objects.get(pk=1) + result = self.client.delete(reverse('api:user_management:permissions-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/api/tests/test_user.html b/docs/_build/html/_modules/api/tests/test_user.html new file mode 100644 index 00000000..b5f50e2a --- /dev/null +++ b/docs/_build/html/_modules/api/tests/test_user.html @@ -0,0 +1,370 @@ + + + + + + api.tests.test_user — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for api.tests.test_user

+from django.contrib.auth.models import Permission
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from django.urls import reverse
+
+from rest_framework import status
+from rest_framework.test import APIClient
+
+from user_management.tests.common_functions import sample_superuser, sample_group, sample_user
+
+from user_management.serializers import UserSerializer, PermissionSerializer, GroupSerializer
+
+USERS_LIST_URL = reverse('api:user_management:users-list')
+
+
+
[docs]class PublicUsersAPITests(TestCase): + """Test the public available users API""" +
[docs] def setUp(self) -> None: + self.client = APIClient()
+ +
[docs] def test_login_required(self): + """Test that login is required for retrieving permissions""" + result = self.client.get(USERS_LIST_URL) + self.assertEqual(result.status_code, status.HTTP_401_UNAUTHORIZED)
+ + +
[docs]class PrivateUsersAPITests(TestCase): + """Test the authorized user users API""" +
[docs] def setUp(self) -> None: + self.super_user = sample_superuser() + self.client = APIClient() + self.client.force_authenticate(self.super_user)
+ +
[docs] def test_create_user_successful(self): + """Test creating a new user is successful""" + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertFalse(result.data['is_superuser']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login'])
+ +
[docs] def test_create_user_with_group_successful(self): + """Test creating a new user with group is successful""" + group = sample_group() + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [group.id], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertEqual(result.data['groups'], GroupSerializer(many=True, instance=[group]).data) + self.assertFalse(result.data['is_superuser']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login'])
+ +
[docs] def test_create_user_with_permission_successful(self): + """Test creating a new user with permission is successful""" + permission = Permission.objects.get(pk=1) + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [permission.id] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertEqual(result.data['user_permissions'], PermissionSerializer(many=True, instance=[permission]).data) + self.assertFalse(result.data['is_superuser']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login'])
+ +
[docs] def test_create_user_with_group_with_permission_to_do_something_successful(self): + """Test creating a new user with group with the permission to do something is successful""" + group = sample_group() + permission = Permission.objects.get(codename='add_group') + group.permissions.set([permission.id]) + + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [group.id], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertEqual(result.data['groups'], GroupSerializer(many=True, instance=[group]).data) + + user = get_user_model().objects.get(pk=result.data['id']) + self.assertTrue(user.has_perm("auth.add_group")) + + self.client.logout() + self.client.force_authenticate(user=user) + create_group_payload = { + 'name': 'Sample Group Two', + } + create_group_result = self.client.post(reverse('api:user_management:groups-list'), create_group_payload) + self.assertEqual(create_group_result.status_code, status.HTTP_201_CREATED) + self.assertEqual(create_group_result.data['name'], create_group_payload['name'])
+ +
[docs] def test_create_user_with_group_without_permission_to_do_something_unsuccessful(self): + """Test creating a new user with group without the permission to do something is unsuccessful""" + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + + user = get_user_model().objects.get(pk=result.data['id']) + self.assertFalse(user.has_perm("auth.add_group")) + + self.client.logout() + self.client.force_authenticate(user=user) + create_group_payload = { + 'name': 'Sample Group Two', + } + create_group_result = self.client.post(reverse('api:user_management:groups-list'), create_group_payload) + self.assertEqual(create_group_result.status_code, status.HTTP_403_FORBIDDEN)
+ +
[docs] def test_create_user_with_invalid_username_unsuccessful(self): + """Test creating a new user with invalid username is unsuccessful""" + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "Sample User", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_create_user_with_invalid_password_unsuccessful(self): + """Test creating a new user with invalid password is unsuccessful""" + payload = { + "password": "123", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_create_user_with_duplicate_username_unsuccessful(self): + """Test creating a new user with duplicate username is unsuccessful""" + instance = sample_user() + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": instance.username, + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_create_superuser_successful(self): + """Test creating a new superuser is successful""" + payload = { + "password": "@strong#password", + "is_superuser": True, + "is_staff": True, + "username": "sample_superuser@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertTrue(result.data['is_superuser']) + self.assertTrue(result.data['is_staff']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login'])
+ +
[docs] def test_retrieve_users_successful(self): + """Test retrieving users is successful""" + sample_user() + sample_user(username='user2@ticketing.sample') + result = self.client.get(USERS_LIST_URL) + users = get_user_model().objects.all() + serializer = UserSerializer(users, many=True) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['results'], serializer.data)
+ +
[docs] def test_retrieve_single_user_successful(self): + """Test retrieving a single user is successful""" + instance = sample_user() + result = self.client.get(reverse('api:user_management:users-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['username'], instance.username)
+ +
[docs] def test_update_user_successful(self): + """Test updating the user is successful""" + instance = sample_user() + payload = { + 'is_active': False + } + result = self.client.patch( + reverse('api:user_management:users-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['is_active'], payload['is_active']) + self.assertNotEqual(result.data['is_active'], instance.is_active) + self.assertFalse(result.data['is_active'])
+ +
[docs] def test_update_user_unsuccessful(self): + """Test updating the user is unsuccessful""" + instance = sample_user() + payload = { + 'username': '', + } + result = self.client.patch( + reverse('api:user_management:users-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
+ +
[docs] def test_delete_user_successful(self): + """Test deleting the user is successful""" + instance = sample_user() + result = self.client.delete( + reverse('api:user_management:users-detail', kwargs={'pk': instance.pk}), + ) + self.assertEqual(result.status_code, status.HTTP_204_NO_CONTENT)
+ +
[docs] def test_delete_user_unsuccessful(self): + """Test deleting the user is unsuccessful""" + result = self.client.delete( + reverse('api:user_management:users-detail', kwargs={'pk': 10000}), + ) + self.assertEqual(result.status_code, status.HTTP_404_NOT_FOUND)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/admin.html b/docs/_build/html/_modules/booking/admin.html new file mode 100644 index 00000000..b8626f91 --- /dev/null +++ b/docs/_build/html/_modules/booking/admin.html @@ -0,0 +1,317 @@ + + + + + + booking.admin — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.admin

+from django.contrib import admin
+
+from booking.models import Stadium, Section, Team, Match, Invoice, Ticket, Payment
+
+
+
[docs]class SectionInlineAdmin(admin.TabularInline): + model = Section + min_num = 1 + extra = 0
+ + +
[docs]class StadiumAdmin(admin.ModelAdmin): + list_display = [ + 'name', + 'province', + 'city', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'province', + 'city', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'name', + 'province', + 'city', + ] + + actions = [ + 'delete_selected', + ] + + inlines = ( + SectionInlineAdmin, + ) + +
[docs] def get_queryset(self, request): + return self.model.objects.prefetch_related('section_stadiums')
+ + +
[docs]class TeamAdmin(admin.ModelAdmin): + list_display = [ + 'name', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'name', + ] + + actions = [ + 'delete_selected', + ]
+ + +
[docs]class MatchAdmin(admin.ModelAdmin): + list_display = [ + 'stadium', + 'host_team', + 'guest_team', + 'start_time', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'stadium', + 'host_team', + 'guest_team', + 'start_time', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'stadium', + 'host_team', + 'guest_team', + ] + + actions = [ + 'delete_selected', + ] + +
[docs] def get_queryset(self, request): + return self.model.objects.select_related('stadium', 'host_team', 'guest_team')
+ + +
[docs]class InvoiceAdmin(admin.ModelAdmin): +
[docs] def has_add_permission(self, request): + return False
+ +
[docs] def has_change_permission(self, request, obj=None): + return False
+ +
[docs] def has_delete_permission(self, request, obj=None): + return False
+ + list_display = [ + 'user', + 'status', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'status', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'user', + ] + + actions = [ + 'delete_selected', + ] + +
[docs] def get_queryset(self, request): + return self.model.objects.select_related('user')
+ + +
[docs]class TicketAdmin(admin.ModelAdmin): +
[docs] def has_add_permission(self, request): + return False
+ +
[docs] def has_change_permission(self, request, obj=None): + return False
+ +
[docs] def has_delete_permission(self, request, obj=None): + return False
+ + list_display = [ + 'match', + 'section', + 'seat_number', + 'status', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'match', + 'section', + 'status', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'match', + 'section', + 'seat_number', + ] + + actions = [ + 'delete_selected', + ] + +
[docs] def get_queryset(self, request): + return self.model.objects.select_related('invoice', 'match', 'section')
+ + +
[docs]class PaymentAdmin(admin.ModelAdmin): +
[docs] def has_add_permission(self, request): + return False
+ +
[docs] def has_change_permission(self, request, obj=None): + return False
+ +
[docs] def has_delete_permission(self, request, obj=None): + return False
+ + list_display = [ + 'invoice', + 'amount', + 'status', + 'created_at', + ] + + list_filter = [ + 'status', + 'created_at', + ] + + search_fields = [ + 'invoice', + 'amount', + ] + + actions = [ + 'delete_selected', + ] + +
[docs] def get_queryset(self, request): + return self.model.objects.select_related('invoice')
+ + +admin.site.register(Stadium, StadiumAdmin) +admin.site.register(Team, TeamAdmin) +admin.site.register(Match, MatchAdmin) +admin.site.register(Invoice, InvoiceAdmin) +admin.site.register(Ticket, TicketAdmin) +admin.site.register(Payment, PaymentAdmin) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/apps.html b/docs/_build/html/_modules/booking/apps.html new file mode 100644 index 00000000..3e6f5f5f --- /dev/null +++ b/docs/_build/html/_modules/booking/apps.html @@ -0,0 +1,109 @@ + + + + + + booking.apps — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.apps

+from django.utils.translation import gettext_lazy as _
+from django.apps import AppConfig
+
+
+
[docs]class BookingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'booking' + verbose_name = _('Booking')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/invoice.html b/docs/_build/html/_modules/booking/models/invoice.html new file mode 100644 index 00000000..e7a0aa21 --- /dev/null +++ b/docs/_build/html/_modules/booking/models/invoice.html @@ -0,0 +1,147 @@ + + + + + + booking.models.invoice — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.invoice

+from django.utils.translation import gettext_lazy as _
+from django.contrib.auth import get_user_model
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+from extensions.choices import InvoiceStatusChoices, TicketStatusChoices
+
+from decimal import Decimal
+
+
+
[docs]class Invoice(AbstractCreatAtUpdateAt, models.Model): + user = models.ForeignKey( + get_user_model(), + verbose_name=_('User'), + on_delete=models.CASCADE, + related_name='invoice_users' + ) + status = models.PositiveSmallIntegerField( + verbose_name=_('Status'), + choices=InvoiceStatusChoices.choices, + default=InvoiceStatusChoices.UNPAID + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_invoices' + verbose_name = _('Invoice') + verbose_name_plural = _('Invoices') + ordering = ['id'] + + def __str__(self) -> str: + return f"{self.user} - {self.id}" + +
[docs] def set_as_paid(self) -> None: + """It will change the status field and save the object and tickets""" + self.status = InvoiceStatusChoices.PAID + self.save() + + # We have to do this in order to update the 'updated_at' field instead of using the 'update' method + for ticket in self.ticket_invoices.all(): + ticket.set_as_sold()
+ + @property + def get_total_amount(self) -> Decimal: + """It returns total price based on price of the section price""" + return sum(list(self.ticket_invoices.values_list('section__price', flat=True)))
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/match.html b/docs/_build/html/_modules/booking/models/match.html new file mode 100644 index 00000000..925a4893 --- /dev/null +++ b/docs/_build/html/_modules/booking/models/match.html @@ -0,0 +1,140 @@ + + + + + + booking.models.match — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.match

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+
+
+
[docs]class Match(AbstractCreatAtUpdateAt, models.Model): + stadium = models.ForeignKey( + 'Stadium', + verbose_name=_('Stadium'), + on_delete=models.PROTECT, + related_name='match_stadiums' + ) + host_team = models.ForeignKey( + 'Team', + verbose_name=_('Host Team'), + on_delete=models.PROTECT, + related_name='match_host_teams' + ) + guest_team = models.ForeignKey( + 'Team', + verbose_name=_('Guest Team'), + on_delete=models.PROTECT, + related_name='match_guest_teams' + ) + start_time = models.DateTimeField( + verbose_name=_('Start Time') + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_matches' + verbose_name = _('Match') + verbose_name_plural = _('Matches') + ordering = ['id'] + unique_together = ['stadium', 'host_team', 'guest_team', 'start_time'] + + def __str__(self) -> str: + return f"{self.stadium} - {self.host_team} & {self.guest_team} - {self.start_time}"
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/payment.html b/docs/_build/html/_modules/booking/models/payment.html new file mode 100644 index 00000000..12e68961 --- /dev/null +++ b/docs/_build/html/_modules/booking/models/payment.html @@ -0,0 +1,139 @@ + + + + + + booking.models.payment — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.payment

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+from extensions.choices import PaymentStatusChoices
+
+
+
[docs]class Payment(models.Model): + invoice = models.ForeignKey( + 'Invoice', + verbose_name=_('Invoice'), + on_delete=models.CASCADE, + related_name='payment_invoices' + ) + amount = models.DecimalField( + verbose_name=_('Amount'), + decimal_places=2, + max_digits=14 + ) + status = models.PositiveSmallIntegerField( + verbose_name=_('Status'), + choices=PaymentStatusChoices.choices + ) + created_at = models.DateTimeField( + verbose_name=_('Created At'), + auto_now_add=True, + editable=False + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_payments' + verbose_name = _('Payment') + verbose_name_plural = _('Payments') + ordering = ['id'] + + def __str__(self) -> str: + return f"Payment - {self.id}"
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/section.html b/docs/_build/html/_modules/booking/models/section.html new file mode 100644 index 00000000..58aa1277 --- /dev/null +++ b/docs/_build/html/_modules/booking/models/section.html @@ -0,0 +1,143 @@ + + + + + + booking.models.section — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.section

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+from extensions.choices import LocationChoices
+
+
+
[docs]class Section(AbstractCreatAtUpdateAt, models.Model): + stadium = models.ForeignKey( + 'Stadium', + verbose_name=_('Stadium'), + on_delete=models.CASCADE, + related_name='section_stadiums' + ) + location = models.PositiveSmallIntegerField( + verbose_name=_('Location'), + choices=LocationChoices.choices, + ) + capacity = models.PositiveSmallIntegerField( + verbose_name=_('Capacity') + ) + price = models.DecimalField( + verbose_name=_('Price'), + decimal_places=2, + max_digits=14 + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_sections' + verbose_name = _('Section') + verbose_name_plural = _('Sections') + ordering = ['id'] + unique_together = ['stadium', 'location'] + + def __str__(self) -> str: + return f"{self.stadium} - {self.get_location_display()}" + + @property + def get_list_of_seat_numbers(self) -> list: + """It returns a list of seat numbers""" + return [*range(1, self.capacity + 1)]
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/stadium.html b/docs/_build/html/_modules/booking/models/stadium.html new file mode 100644 index 00000000..ed86130d --- /dev/null +++ b/docs/_build/html/_modules/booking/models/stadium.html @@ -0,0 +1,147 @@ + + + + + + booking.models.stadium — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.stadium

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+
+
+
[docs]class Stadium(AbstractCreatAtUpdateAt, models.Model): + name = models.CharField( + verbose_name=_('Name'), + max_length=200, + db_index=True + ) + province = models.CharField( + verbose_name=_('Province'), + max_length=200, + db_index=True + ) + city = models.CharField( + verbose_name=_('City'), + max_length=200, + db_index=True + ) + address = models.TextField( + verbose_name=_('Address'), + blank=True + ) + map_url = models.URLField( + verbose_name=_('Map URL'), + blank=True + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_stadiums' + verbose_name = _('Stadium') + verbose_name_plural = _('Stadiums') + ordering = ['id'] + unique_together = ['name', 'province', 'city'] + + def __str__(self) -> str: + return f"{self.province} - {self.city} - {self.name}" + + @property + def get_section_ids(self) -> list: + """It returns a list of section ids""" + return list(self.section_stadiums.values_list('id', flat=True))
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/team.html b/docs/_build/html/_modules/booking/models/team.html new file mode 100644 index 00000000..5c9a89af --- /dev/null +++ b/docs/_build/html/_modules/booking/models/team.html @@ -0,0 +1,124 @@ + + + + + + booking.models.team — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.team

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+
+
+
[docs]class Team(AbstractCreatAtUpdateAt, models.Model): + name = models.CharField( + verbose_name=_('Name'), + max_length=200, + unique=True, + db_index=True + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_teams' + verbose_name = _('Team') + verbose_name_plural = _('Teams') + ordering = ['id'] + + def __str__(self) -> str: + return self.name.__str__()
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/models/ticket.html b/docs/_build/html/_modules/booking/models/ticket.html new file mode 100644 index 00000000..4bf75963 --- /dev/null +++ b/docs/_build/html/_modules/booking/models/ticket.html @@ -0,0 +1,150 @@ + + + + + + booking.models.ticket — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.models.ticket

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+from extensions.choices import TicketStatusChoices
+
+
+
[docs]class Ticket(AbstractCreatAtUpdateAt, models.Model): + invoice = models.ForeignKey( + 'Invoice', + verbose_name=_('Invoice'), + on_delete=models.PROTECT, + related_name='ticket_invoices' + ) + match = models.ForeignKey( + 'Match', + verbose_name=_('Match'), + on_delete=models.PROTECT, + related_name='ticket_matches' + ) + section = models.ForeignKey( + 'Section', + verbose_name=_('Section'), + on_delete=models.PROTECT, + related_name='ticket_sections' + ) + seat_number = models.PositiveSmallIntegerField( + verbose_name=_('Seat Number') + ) + status = models.PositiveSmallIntegerField( + verbose_name=_('Status'), + choices=TicketStatusChoices.choices, + default=TicketStatusChoices.RESERVED + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_tickets' + verbose_name = _('Ticket') + verbose_name_plural = _('Tickets') + ordering = ['id'] + + def __str__(self) -> str: + return f"Ticket {self.id}" + +
[docs] def set_as_sold(self) -> None: + """It will change the status field and save the object""" + self.status = TicketStatusChoices.SOLD + self.save()
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/invoice.html b/docs/_build/html/_modules/booking/serializers/invoice.html new file mode 100644 index 00000000..045e3d07 --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/invoice.html @@ -0,0 +1,121 @@ + + + + + + booking.serializers.invoice — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.invoice

+from rest_framework.serializers import ModelSerializer
+
+from booking.models import Invoice
+
+
+
[docs]class InvoiceSerializer(ModelSerializer): + """ + Serializer for the 'Invoice' model + """ + + class Meta: + model = Invoice + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') + +
[docs] def to_representation(self, obj): + data = super(InvoiceSerializer, self).to_representation(obj) + data['status'] = obj.get_status_display() + data['total_amount'] = obj.get_total_amount + return data
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/match.html b/docs/_build/html/_modules/booking/serializers/match.html new file mode 100644 index 00000000..ff36df21 --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/match.html @@ -0,0 +1,115 @@ + + + + + + booking.serializers.match — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.match

+from rest_framework.serializers import ModelSerializer
+
+from booking.models import Match
+
+
+
[docs]class MatchSerializer(ModelSerializer): + """ + Serializer for the 'Match' model + """ + + class Meta: + model = Match + fields = '__all__' + read_only_fields = ('created_at', 'updated_at')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/payment.html b/docs/_build/html/_modules/booking/serializers/payment.html new file mode 100644 index 00000000..4540c2d0 --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/payment.html @@ -0,0 +1,179 @@ + + + + + + booking.serializers.payment — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.payment

+from django.utils.translation import gettext_lazy as _
+from django.db import transaction
+
+from rest_framework.serializers import ModelSerializer
+from rest_framework.exceptions import ValidationError
+from rest_framework.response import Response
+from rest_framework import status
+
+from booking.models import Payment
+
+from extensions.choices import InvoiceStatusChoices, PaymentStatusChoices
+
+from decimal import Decimal
+
+
+
[docs]class PaymentSerializer(ModelSerializer): + """ + Serializer for the 'Payment' model + """ + + class Meta: + model = Payment + fields = '__all__' + read_only_fields = ('status', 'created_at') + +
[docs] def validate(self, data): + """ + It will use for making the validation on data + :param data: A dict of fields + :return: A valid data or errors + """ + + invoice = data.get('invoice') + amount = data.get('amount') + + if invoice.user != self.context['request'].user: + raise ValidationError({ + "invoice": [_("This invoice does not belong to you!")] + }) + + if invoice.status == InvoiceStatusChoices.PAID: + raise ValidationError({ + "invoice": [_("This invoice has already been paid!")] + }) + + if invoice.get_total_amount != Decimal(amount): + raise ValidationError({ + "amount": [_("The entered amount is not equal to the invoice amount!")] + }) + + return data
+ +
[docs] def create(self, validated_data): + """ + Create a new instance + :param validated_data: A dict of fields + :return: A new instance + """ + with transaction.atomic(): + try: + invoice = validated_data.get('invoice') + + payment_instance = self.Meta.model.objects.create( + invoice=invoice, + amount=invoice.get_total_amount, + status=PaymentStatusChoices.SUCCESSFUL + ) + + invoice.set_as_paid() + + return payment_instance + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
+ +
[docs] def to_representation(self, obj): + data = super(PaymentSerializer, self).to_representation(obj) + data['status'] = obj.get_status_display() + return data
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/section.html b/docs/_build/html/_modules/booking/serializers/section.html new file mode 100644 index 00000000..2e477bfc --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/section.html @@ -0,0 +1,121 @@ + + + + + + booking.serializers.section — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.section

+from rest_framework.serializers import ModelSerializer
+
+from booking.models import Section
+
+
+
[docs]class SectionSerializer(ModelSerializer): + """ + Serializer for the 'Section' model + """ + + class Meta: + model = Section + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') + +
[docs] def to_representation(self, obj): + data = super(SectionSerializer, self).to_representation(obj) + data['location'] = obj.get_location_display() + data['seat_numbers'] = obj.get_list_of_seat_numbers + return data
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/stadium.html b/docs/_build/html/_modules/booking/serializers/stadium.html new file mode 100644 index 00000000..079f829e --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/stadium.html @@ -0,0 +1,115 @@ + + + + + + booking.serializers.stadium — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.stadium

+from rest_framework.serializers import ModelSerializer
+
+from booking.models import Stadium
+
+
+
[docs]class StadiumSerializer(ModelSerializer): + """ + Serializer for the 'Stadium' model + """ + + class Meta: + model = Stadium + fields = '__all__' + read_only_fields = ('created_at', 'updated_at')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/team.html b/docs/_build/html/_modules/booking/serializers/team.html new file mode 100644 index 00000000..b757600e --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/team.html @@ -0,0 +1,115 @@ + + + + + + booking.serializers.team — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.team

+from rest_framework.serializers import ModelSerializer
+
+from booking.models import Team
+
+
+
[docs]class TeamSerializer(ModelSerializer): + """ + Serializer for the 'Team' model + """ + + class Meta: + model = Team + fields = '__all__' + read_only_fields = ('created_at', 'updated_at')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/serializers/ticket.html b/docs/_build/html/_modules/booking/serializers/ticket.html new file mode 100644 index 00000000..e7ddc33d --- /dev/null +++ b/docs/_build/html/_modules/booking/serializers/ticket.html @@ -0,0 +1,219 @@ + + + + + + booking.serializers.ticket — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.serializers.ticket

+from django.utils.translation import gettext_lazy as _
+from django.utils import timezone
+from django.db import transaction
+from django.db.models import Q
+
+from rest_framework.serializers import ModelSerializer
+from rest_framework.exceptions import ValidationError
+from rest_framework.response import Response
+from rest_framework import status
+
+from booking.models import Ticket, Invoice
+
+from extensions.choices import TicketStatusChoices, InvoiceStatusChoices
+
+
+
[docs]class TicketSerializer(ModelSerializer): + """ + Serializer for the 'Ticket' model + """ + + class Meta: + model = Ticket + fields = '__all__' + read_only_fields = ('invoice', 'status', 'created_at', 'updated_at') + +
[docs] def validate(self, data): + """ + It will use for making the validation on data + :param data: A dict of fields + :return: A valid data or errors + """ + + match = data.get('match') + section = data.get('section') + seat_number = data.get('seat_number') + + if match.start_time < timezone.now(): + raise ValidationError({ + "match": [_("The time for this match has passed!")] + }) + + if section.stadium != match.stadium: + raise ValidationError({ + "match": [_("The stadium is not the same!")], + "section": [_("The stadium is not the same!")] + }) + + if section.id not in match.stadium.get_section_ids: + raise ValidationError({ + "section": [_("It does not belong to the stadium of this match!")] + }) + + if seat_number not in section.get_list_of_seat_numbers: + raise ValidationError({ + "seat_number": [_("There is no such seat in the selected section!")] + }) + + ticket = self.Meta.model.objects.filter( + invoice__user=self.context['request'].user, + match=match, + section=section, + seat_number=seat_number + ).first() + if ticket: + if ticket.status == TicketStatusChoices.SOLD: + raise ValidationError({ + "seat_number": [_("You have already purchased this seat number!")] + }) + if ticket.status == TicketStatusChoices.RESERVED: + raise ValidationError({ + "seat_number": [_("You have already reserved this seat number, check your invoices section!")] + }) + + unavailable_seats = list(self.Meta.model.objects.filter(match=match, section=section).filter( + Q(status=TicketStatusChoices.RESERVED) | + Q(status=TicketStatusChoices.SOLD) + ).values_list('seat_number', flat=True)) + + if seat_number in unavailable_seats: + raise ValidationError({ + "seat_number": [_("This seat number cannot be purchased!")] + }) + + return data
+ +
[docs] def create(self, validated_data): + """ + Create a new instance + :param validated_data: A dict of fields + :return: A new instance + """ + with transaction.atomic(): + try: + match = validated_data.get('match') + section = validated_data.get('section') + seat_number = validated_data.get('seat_number') + + invoice, created = Invoice.objects.get_or_create( + user=self.context['request'].user, + status=InvoiceStatusChoices.UNPAID + ) + + reserved_ticket = self.Meta.model.objects.create( + invoice=invoice, + match=match, + section=section, + seat_number=seat_number, + status=TicketStatusChoices.RESERVED + ) + + return reserved_ticket + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
+ +
[docs] def to_representation(self, obj): + data = super(TicketSerializer, self).to_representation(obj) + data['status'] = obj.get_status_display() + return data
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/invoice.html b/docs/_build/html/_modules/booking/views/invoice.html new file mode 100644 index 00000000..7ce1a6f5 --- /dev/null +++ b/docs/_build/html/_modules/booking/views/invoice.html @@ -0,0 +1,122 @@ + + + + + + booking.views.invoice — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.invoice

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ReadOnlyModelViewSet
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Invoice
+from booking.serializers import InvoiceSerializer
+
+
+
[docs]class InvoiceViewSet(ReadOnlyModelViewSet): + """ + ViewSet for the 'Invoice' model objects + """ + serializer_class = InvoiceSerializer + queryset = Invoice.objects.select_related('user').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['user', 'status'] + search_fields = ['user__username']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/match.html b/docs/_build/html/_modules/booking/views/match.html new file mode 100644 index 00000000..2ac6b2e0 --- /dev/null +++ b/docs/_build/html/_modules/booking/views/match.html @@ -0,0 +1,136 @@ + + + + + + booking.views.match — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.match

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ModelViewSet
+from rest_framework.decorators import action
+from rest_framework.response import Response
+from rest_framework import status
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Match
+from booking.serializers import MatchSerializer, SectionSerializer
+
+
+
[docs]class MatchViewSet(ModelViewSet): + """ + ViewSet for the 'Match' model objects + """ + serializer_class = MatchSerializer + queryset = Match.objects.select_related('stadium', 'host_team', 'guest_team').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['stadium', 'host_team', 'guest_team', 'start_time'] + search_fields = ['stadium__name', 'host_team__name', 'guest_team__name', 'start_time'] + +
[docs] @action(detail=True, methods=["get"], url_path='seats', url_name='seats') + def seats(self, request, pk=None): + """ + This will use for show the current match seats + :return: The current match seats + """ + return Response( + SectionSerializer(self.get_object().stadium.section_stadiums.all(), many=True).data, + status=status.HTTP_200_OK + )
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/payment.html b/docs/_build/html/_modules/booking/views/payment.html new file mode 100644 index 00000000..09697f55 --- /dev/null +++ b/docs/_build/html/_modules/booking/views/payment.html @@ -0,0 +1,123 @@ + + + + + + booking.views.payment — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.payment

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import GenericViewSet
+from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Payment
+from booking.serializers import PaymentSerializer
+
+
+
[docs]class PaymentViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet): + """ + ViewSet for the 'Payment' model objects + """ + serializer_class = PaymentSerializer + queryset = Payment.objects.select_related('invoice').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['invoice', 'amount', 'status', 'created_at'] + search_fields = ['invoice', 'invoice__user__username', 'amount', 'created_at']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/section.html b/docs/_build/html/_modules/booking/views/section.html new file mode 100644 index 00000000..d922a661 --- /dev/null +++ b/docs/_build/html/_modules/booking/views/section.html @@ -0,0 +1,122 @@ + + + + + + booking.views.section — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.section

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ModelViewSet
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Section
+from booking.serializers import SectionSerializer
+
+
+
[docs]class SectionViewSet(ModelViewSet): + """ + ViewSet for the 'Section' model objects + """ + serializer_class = SectionSerializer + queryset = Section.objects.select_related('stadium').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['stadium', 'location', 'capacity', 'price'] + search_fields = ['stadium__name', 'location', 'capacity', 'price']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/stadium.html b/docs/_build/html/_modules/booking/views/stadium.html new file mode 100644 index 00000000..cca6be5d --- /dev/null +++ b/docs/_build/html/_modules/booking/views/stadium.html @@ -0,0 +1,122 @@ + + + + + + booking.views.stadium — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.stadium

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ModelViewSet
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Stadium
+from booking.serializers import StadiumSerializer
+
+
+
[docs]class StadiumViewSet(ModelViewSet): + """ + ViewSet for the 'Stadium' model objects + """ + serializer_class = StadiumSerializer + queryset = Stadium.objects.prefetch_related('section_stadiums').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', 'province', 'city'] + search_fields = ['name', 'province', 'city', 'address']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/team.html b/docs/_build/html/_modules/booking/views/team.html new file mode 100644 index 00000000..f67419bb --- /dev/null +++ b/docs/_build/html/_modules/booking/views/team.html @@ -0,0 +1,122 @@ + + + + + + booking.views.team — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.team

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ModelViewSet
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Team
+from booking.serializers import TeamSerializer
+
+
+
[docs]class TeamViewSet(ModelViewSet): + """ + ViewSet for the 'Team' model objects + """ + serializer_class = TeamSerializer + queryset = Team.objects.all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name'] + search_fields = ['name']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/booking/views/ticket.html b/docs/_build/html/_modules/booking/views/ticket.html new file mode 100644 index 00000000..b4447d06 --- /dev/null +++ b/docs/_build/html/_modules/booking/views/ticket.html @@ -0,0 +1,123 @@ + + + + + + booking.views.ticket — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for booking.views.ticket

+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import GenericViewSet
+from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from booking.models import Ticket
+from booking.serializers import TicketSerializer
+
+
+
[docs]class TicketViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet): + """ + ViewSet for the 'Ticket' model objects + """ + serializer_class = TicketSerializer + queryset = Ticket.objects.select_related('invoice', 'match', 'section').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['invoice', 'match', 'section', 'seat_number', 'status'] + search_fields = ['invoice', 'invoice__user__username', 'match', 'section', 'seat_number']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/commands/apps.html b/docs/_build/html/_modules/commands/apps.html new file mode 100644 index 00000000..3345fecd --- /dev/null +++ b/docs/_build/html/_modules/commands/apps.html @@ -0,0 +1,109 @@ + + + + + + commands.apps — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for commands.apps

+from django.utils.translation import gettext_lazy as _
+from django.apps import AppConfig
+
+
+
[docs]class CommandsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'commands' + verbose_name = _('Commands')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/django/db/models.html b/docs/_build/html/_modules/django/db/models.html new file mode 100644 index 00000000..f6ace003 --- /dev/null +++ b/docs/_build/html/_modules/django/db/models.html @@ -0,0 +1,216 @@ + + + + + + django.db.models — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for django.db.models

+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models import signals
+from django.db.models.aggregates import *  # NOQA
+from django.db.models.aggregates import __all__ as aggregates_all
+from django.db.models.constraints import *  # NOQA
+from django.db.models.constraints import __all__ as constraints_all
+from django.db.models.deletion import (
+    CASCADE,
+    DO_NOTHING,
+    PROTECT,
+    RESTRICT,
+    SET,
+    SET_DEFAULT,
+    SET_NULL,
+    ProtectedError,
+    RestrictedError,
+)
+from django.db.models.enums import *  # NOQA
+from django.db.models.enums import __all__ as enums_all
+from django.db.models.expressions import (
+    Case,
+    Exists,
+    Expression,
+    ExpressionList,
+    ExpressionWrapper,
+    F,
+    Func,
+    OrderBy,
+    OuterRef,
+    RowRange,
+    Subquery,
+    Value,
+    ValueRange,
+    When,
+    Window,
+    WindowFrame,
+)
+from django.db.models.fields import *  # NOQA
+from django.db.models.fields import __all__ as fields_all
+from django.db.models.fields.files import FileField, ImageField
+from django.db.models.fields.json import JSONField
+from django.db.models.fields.proxy import OrderWrt
+from django.db.models.indexes import *  # NOQA
+from django.db.models.indexes import __all__ as indexes_all
+from django.db.models.lookups import Lookup, Transform
+from django.db.models.manager import Manager
+from django.db.models.query import Prefetch, QuerySet, prefetch_related_objects
+from django.db.models.query_utils import FilteredRelation, Q
+
+# Imports that would create circular imports if sorted
+from django.db.models.base import DEFERRED, Model  # isort:skip
+from django.db.models.fields.related import (  # isort:skip
+    ForeignKey,
+    ForeignObject,
+    OneToOneField,
+    ManyToManyField,
+    ForeignObjectRel,
+    ManyToOneRel,
+    ManyToManyRel,
+    OneToOneRel,
+)
+
+
+__all__ = aggregates_all + constraints_all + enums_all + fields_all + indexes_all
+__all__ += [
+    "ObjectDoesNotExist",
+    "signals",
+    "CASCADE",
+    "DO_NOTHING",
+    "PROTECT",
+    "RESTRICT",
+    "SET",
+    "SET_DEFAULT",
+    "SET_NULL",
+    "ProtectedError",
+    "RestrictedError",
+    "Case",
+    "Exists",
+    "Expression",
+    "ExpressionList",
+    "ExpressionWrapper",
+    "F",
+    "Func",
+    "OrderBy",
+    "OuterRef",
+    "RowRange",
+    "Subquery",
+    "Value",
+    "ValueRange",
+    "When",
+    "Window",
+    "WindowFrame",
+    "FileField",
+    "ImageField",
+    "JSONField",
+    "OrderWrt",
+    "Lookup",
+    "Transform",
+    "Manager",
+    "Prefetch",
+    "Q",
+    "QuerySet",
+    "prefetch_related_objects",
+    "DEFERRED",
+    "Model",
+    "FilteredRelation",
+    "ForeignKey",
+    "ForeignObject",
+    "OneToOneField",
+    "ManyToManyField",
+    "ForeignObjectRel",
+    "ManyToOneRel",
+    "ManyToManyRel",
+    "OneToOneRel",
+]
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/django/db/models/fields/related_descriptors.html b/docs/_build/html/_modules/django/db/models/fields/related_descriptors.html new file mode 100644 index 00000000..8f86b788 --- /dev/null +++ b/docs/_build/html/_modules/django/db/models/fields/related_descriptors.html @@ -0,0 +1,1608 @@ + + + + + + django.db.models.fields.related_descriptors — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for django.db.models.fields.related_descriptors

+"""
+Accessors for related objects.
+
+When a field defines a relation between two models, each model class provides
+an attribute to access related instances of the other model class (unless the
+reverse accessor has been disabled with related_name='+').
+
+Accessors are implemented as descriptors in order to customize access and
+assignment. This module defines the descriptor classes.
+
+Forward accessors follow foreign keys. Reverse accessors trace them back. For
+example, with the following models::
+
+    class Parent(Model):
+        pass
+
+    class Child(Model):
+        parent = ForeignKey(Parent, related_name='children')
+
+ ``child.parent`` is a forward many-to-one relation. ``parent.children`` is a
+reverse many-to-one relation.
+
+There are three types of relations (many-to-one, one-to-one, and many-to-many)
+and two directions (forward and reverse) for a total of six combinations.
+
+1. Related instance on the forward side of a many-to-one relation:
+   ``ForwardManyToOneDescriptor``.
+
+   Uniqueness of foreign key values is irrelevant to accessing the related
+   instance, making the many-to-one and one-to-one cases identical as far as
+   the descriptor is concerned. The constraint is checked upstream (unicity
+   validation in forms) or downstream (unique indexes in the database).
+
+2. Related instance on the forward side of a one-to-one
+   relation: ``ForwardOneToOneDescriptor``.
+
+   It avoids querying the database when accessing the parent link field in
+   a multi-table inheritance scenario.
+
+3. Related instance on the reverse side of a one-to-one relation:
+   ``ReverseOneToOneDescriptor``.
+
+   One-to-one relations are asymmetrical, despite the apparent symmetry of the
+   name, because they're implemented in the database with a foreign key from
+   one table to another. As a consequence ``ReverseOneToOneDescriptor`` is
+   slightly different from ``ForwardManyToOneDescriptor``.
+
+4. Related objects manager for related instances on the reverse side of a
+   many-to-one relation: ``ReverseManyToOneDescriptor``.
+
+   Unlike the previous two classes, this one provides access to a collection
+   of objects. It returns a manager rather than an instance.
+
+5. Related objects manager for related instances on the forward or reverse
+   sides of a many-to-many relation: ``ManyToManyDescriptor``.
+
+   Many-to-many relations are symmetrical. The syntax of Django models
+   requires declaring them on one side but that's an implementation detail.
+   They could be declared on the other side without any change in behavior.
+   Therefore the forward and reverse descriptors can be the same.
+
+   If you're looking for ``ForwardManyToManyDescriptor`` or
+   ``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
+"""
+
+from asgiref.sync import sync_to_async
+
+from django.core.exceptions import FieldError
+from django.db import (
+    DEFAULT_DB_ALIAS,
+    NotSupportedError,
+    connections,
+    router,
+    transaction,
+)
+from django.db.models import Q, Window, signals
+from django.db.models.functions import RowNumber
+from django.db.models.lookups import GreaterThan, LessThanOrEqual
+from django.db.models.query import QuerySet
+from django.db.models.query_utils import DeferredAttribute
+from django.db.models.utils import AltersData, resolve_callables
+from django.utils.functional import cached_property
+
+
+class ForeignKeyDeferredAttribute(DeferredAttribute):
+    def __set__(self, instance, value):
+        if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(
+            instance
+        ):
+            self.field.delete_cached_value(instance)
+        instance.__dict__[self.field.attname] = value
+
+
+def _filter_prefetch_queryset(queryset, field_name, instances):
+    predicate = Q(**{f"{field_name}__in": instances})
+    db = queryset._db or DEFAULT_DB_ALIAS
+    if queryset.query.is_sliced:
+        if not connections[db].features.supports_over_clause:
+            raise NotSupportedError(
+                "Prefetching from a limited queryset is only supported on backends "
+                "that support window functions."
+            )
+        low_mark, high_mark = queryset.query.low_mark, queryset.query.high_mark
+        order_by = [
+            expr for expr, _ in queryset.query.get_compiler(using=db).get_order_by()
+        ]
+        window = Window(RowNumber(), partition_by=field_name, order_by=order_by)
+        predicate &= GreaterThan(window, low_mark)
+        if high_mark is not None:
+            predicate &= LessThanOrEqual(window, high_mark)
+        queryset.query.clear_limits()
+    return queryset.filter(predicate)
+
+
+class ForwardManyToOneDescriptor:
+    """
+    Accessor to the related object on the forward side of a many-to-one or
+    one-to-one (via ForwardOneToOneDescriptor subclass) relation.
+
+    In the example::
+
+        class Child(Model):
+            parent = ForeignKey(Parent, related_name='children')
+
+    ``Child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
+    """
+
+    def __init__(self, field_with_rel):
+        self.field = field_with_rel
+
+    @cached_property
+    def RelatedObjectDoesNotExist(self):
+        # The exception can't be created at initialization time since the
+        # related model might not be resolved yet; `self.field.model` might
+        # still be a string model reference.
+        return type(
+            "RelatedObjectDoesNotExist",
+            (self.field.remote_field.model.DoesNotExist, AttributeError),
+            {
+                "__module__": self.field.model.__module__,
+                "__qualname__": "%s.%s.RelatedObjectDoesNotExist"
+                % (
+                    self.field.model.__qualname__,
+                    self.field.name,
+                ),
+            },
+        )
+
+    def is_cached(self, instance):
+        return self.field.is_cached(instance)
+
+    def get_queryset(self, **hints):
+        return self.field.remote_field.model._base_manager.db_manager(hints=hints).all()
+
+    def get_prefetch_queryset(self, instances, queryset=None):
+        if queryset is None:
+            queryset = self.get_queryset()
+        queryset._add_hints(instance=instances[0])
+
+        rel_obj_attr = self.field.get_foreign_related_value
+        instance_attr = self.field.get_local_related_value
+        instances_dict = {instance_attr(inst): inst for inst in instances}
+        related_field = self.field.foreign_related_fields[0]
+        remote_field = self.field.remote_field
+
+        # FIXME: This will need to be revisited when we introduce support for
+        # composite fields. In the meantime we take this practical approach to
+        # solve a regression on 1.6 when the reverse manager in hidden
+        # (related_name ends with a '+'). Refs #21410.
+        # The check for len(...) == 1 is a special case that allows the query
+        # to be join-less and smaller. Refs #21760.
+        if remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1:
+            query = {
+                "%s__in"
+                % related_field.name: {instance_attr(inst)[0] for inst in instances}
+            }
+        else:
+            query = {"%s__in" % self.field.related_query_name(): instances}
+        queryset = queryset.filter(**query)
+
+        # Since we're going to assign directly in the cache,
+        # we must manage the reverse relation cache manually.
+        if not remote_field.multiple:
+            for rel_obj in queryset:
+                instance = instances_dict[rel_obj_attr(rel_obj)]
+                remote_field.set_cached_value(rel_obj, instance)
+        return (
+            queryset,
+            rel_obj_attr,
+            instance_attr,
+            True,
+            self.field.get_cache_name(),
+            False,
+        )
+
+    def get_object(self, instance):
+        qs = self.get_queryset(instance=instance)
+        # Assuming the database enforces foreign keys, this won't fail.
+        return qs.get(self.field.get_reverse_related_filter(instance))
+
+    def __get__(self, instance, cls=None):
+        """
+        Get the related instance through the forward relation.
+
+        With the example above, when getting ``child.parent``:
+
+        - ``self`` is the descriptor managing the ``parent`` attribute
+        - ``instance`` is the ``child`` instance
+        - ``cls`` is the ``Child`` class (we don't need it)
+        """
+        if instance is None:
+            return self
+
+        # The related instance is loaded from the database and then cached
+        # by the field on the model instance state. It can also be pre-cached
+        # by the reverse accessor (ReverseOneToOneDescriptor).
+        try:
+            rel_obj = self.field.get_cached_value(instance)
+        except KeyError:
+            has_value = None not in self.field.get_local_related_value(instance)
+            ancestor_link = (
+                instance._meta.get_ancestor_link(self.field.model)
+                if has_value
+                else None
+            )
+            if ancestor_link and ancestor_link.is_cached(instance):
+                # An ancestor link will exist if this field is defined on a
+                # multi-table inheritance parent of the instance's class.
+                ancestor = ancestor_link.get_cached_value(instance)
+                # The value might be cached on an ancestor if the instance
+                # originated from walking down the inheritance chain.
+                rel_obj = self.field.get_cached_value(ancestor, default=None)
+            else:
+                rel_obj = None
+            if rel_obj is None and has_value:
+                rel_obj = self.get_object(instance)
+                remote_field = self.field.remote_field
+                # If this is a one-to-one relation, set the reverse accessor
+                # cache on the related object to the current instance to avoid
+                # an extra SQL query if it's accessed later on.
+                if not remote_field.multiple:
+                    remote_field.set_cached_value(rel_obj, instance)
+            self.field.set_cached_value(instance, rel_obj)
+
+        if rel_obj is None and not self.field.null:
+            raise self.RelatedObjectDoesNotExist(
+                "%s has no %s." % (self.field.model.__name__, self.field.name)
+            )
+        else:
+            return rel_obj
+
+    def __set__(self, instance, value):
+        """
+        Set the related instance through the forward relation.
+
+        With the example above, when setting ``child.parent = parent``:
+
+        - ``self`` is the descriptor managing the ``parent`` attribute
+        - ``instance`` is the ``child`` instance
+        - ``value`` is the ``parent`` instance on the right of the equal sign
+        """
+        # An object must be an instance of the related class.
+        if value is not None and not isinstance(
+            value, self.field.remote_field.model._meta.concrete_model
+        ):
+            raise ValueError(
+                'Cannot assign "%r": "%s.%s" must be a "%s" instance.'
+                % (
+                    value,
+                    instance._meta.object_name,
+                    self.field.name,
+                    self.field.remote_field.model._meta.object_name,
+                )
+            )
+        elif value is not None:
+            if instance._state.db is None:
+                instance._state.db = router.db_for_write(
+                    instance.__class__, instance=value
+                )
+            if value._state.db is None:
+                value._state.db = router.db_for_write(
+                    value.__class__, instance=instance
+                )
+            if not router.allow_relation(value, instance):
+                raise ValueError(
+                    'Cannot assign "%r": the current database router prevents this '
+                    "relation." % value
+                )
+
+        remote_field = self.field.remote_field
+        # If we're setting the value of a OneToOneField to None, we need to clear
+        # out the cache on any old related object. Otherwise, deleting the
+        # previously-related object will also cause this object to be deleted,
+        # which is wrong.
+        if value is None:
+            # Look up the previously-related object, which may still be available
+            # since we've not yet cleared out the related field.
+            # Use the cache directly, instead of the accessor; if we haven't
+            # populated the cache, then we don't care - we're only accessing
+            # the object to invalidate the accessor cache, so there's no
+            # need to populate the cache just to expire it again.
+            related = self.field.get_cached_value(instance, default=None)
+
+            # If we've got an old related object, we need to clear out its
+            # cache. This cache also might not exist if the related object
+            # hasn't been accessed yet.
+            if related is not None:
+                remote_field.set_cached_value(related, None)
+
+            for lh_field, rh_field in self.field.related_fields:
+                setattr(instance, lh_field.attname, None)
+
+        # Set the values of the related field.
+        else:
+            for lh_field, rh_field in self.field.related_fields:
+                setattr(instance, lh_field.attname, getattr(value, rh_field.attname))
+
+        # Set the related instance cache used by __get__ to avoid an SQL query
+        # when accessing the attribute we just set.
+        self.field.set_cached_value(instance, value)
+
+        # If this is a one-to-one relation, set the reverse accessor cache on
+        # the related object to the current instance to avoid an extra SQL
+        # query if it's accessed later on.
+        if value is not None and not remote_field.multiple:
+            remote_field.set_cached_value(value, instance)
+
+    def __reduce__(self):
+        """
+        Pickling should return the instance attached by self.field on the
+        model, not a new copy of that descriptor. Use getattr() to retrieve
+        the instance directly from the model.
+        """
+        return getattr, (self.field.model, self.field.name)
+
+
+class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor):
+    """
+    Accessor to the related object on the forward side of a one-to-one relation.
+
+    In the example::
+
+        class Restaurant(Model):
+            place = OneToOneField(Place, related_name='restaurant')
+
+    ``Restaurant.place`` is a ``ForwardOneToOneDescriptor`` instance.
+    """
+
+    def get_object(self, instance):
+        if self.field.remote_field.parent_link:
+            deferred = instance.get_deferred_fields()
+            # Because it's a parent link, all the data is available in the
+            # instance, so populate the parent model with this data.
+            rel_model = self.field.remote_field.model
+            fields = [field.attname for field in rel_model._meta.concrete_fields]
+
+            # If any of the related model's fields are deferred, fallback to
+            # fetching all fields from the related model. This avoids a query
+            # on the related model for every deferred field.
+            if not any(field in fields for field in deferred):
+                kwargs = {field: getattr(instance, field) for field in fields}
+                obj = rel_model(**kwargs)
+                obj._state.adding = instance._state.adding
+                obj._state.db = instance._state.db
+                return obj
+        return super().get_object(instance)
+
+    def __set__(self, instance, value):
+        super().__set__(instance, value)
+        # If the primary key is a link to a parent model and a parent instance
+        # is being set, update the value of the inherited pk(s).
+        if self.field.primary_key and self.field.remote_field.parent_link:
+            opts = instance._meta
+            # Inherited primary key fields from this object's base classes.
+            inherited_pk_fields = [
+                field
+                for field in opts.concrete_fields
+                if field.primary_key and field.remote_field
+            ]
+            for field in inherited_pk_fields:
+                rel_model_pk_name = field.remote_field.model._meta.pk.attname
+                raw_value = (
+                    getattr(value, rel_model_pk_name) if value is not None else None
+                )
+                setattr(instance, rel_model_pk_name, raw_value)
+
+
+class ReverseOneToOneDescriptor:
+    """
+    Accessor to the related object on the reverse side of a one-to-one
+    relation.
+
+    In the example::
+
+        class Restaurant(Model):
+            place = OneToOneField(Place, related_name='restaurant')
+
+    ``Place.restaurant`` is a ``ReverseOneToOneDescriptor`` instance.
+    """
+
+    def __init__(self, related):
+        # Following the example above, `related` is an instance of OneToOneRel
+        # which represents the reverse restaurant field (place.restaurant).
+        self.related = related
+
+    @cached_property
+    def RelatedObjectDoesNotExist(self):
+        # The exception isn't created at initialization time for the sake of
+        # consistency with `ForwardManyToOneDescriptor`.
+        return type(
+            "RelatedObjectDoesNotExist",
+            (self.related.related_model.DoesNotExist, AttributeError),
+            {
+                "__module__": self.related.model.__module__,
+                "__qualname__": "%s.%s.RelatedObjectDoesNotExist"
+                % (
+                    self.related.model.__qualname__,
+                    self.related.name,
+                ),
+            },
+        )
+
+    def is_cached(self, instance):
+        return self.related.is_cached(instance)
+
+    def get_queryset(self, **hints):
+        return self.related.related_model._base_manager.db_manager(hints=hints).all()
+
+    def get_prefetch_queryset(self, instances, queryset=None):
+        if queryset is None:
+            queryset = self.get_queryset()
+        queryset._add_hints(instance=instances[0])
+
+        rel_obj_attr = self.related.field.get_local_related_value
+        instance_attr = self.related.field.get_foreign_related_value
+        instances_dict = {instance_attr(inst): inst for inst in instances}
+        query = {"%s__in" % self.related.field.name: instances}
+        queryset = queryset.filter(**query)
+
+        # Since we're going to assign directly in the cache,
+        # we must manage the reverse relation cache manually.
+        for rel_obj in queryset:
+            instance = instances_dict[rel_obj_attr(rel_obj)]
+            self.related.field.set_cached_value(rel_obj, instance)
+        return (
+            queryset,
+            rel_obj_attr,
+            instance_attr,
+            True,
+            self.related.get_cache_name(),
+            False,
+        )
+
+    def __get__(self, instance, cls=None):
+        """
+        Get the related instance through the reverse relation.
+
+        With the example above, when getting ``place.restaurant``:
+
+        - ``self`` is the descriptor managing the ``restaurant`` attribute
+        - ``instance`` is the ``place`` instance
+        - ``cls`` is the ``Place`` class (unused)
+
+        Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
+        """
+        if instance is None:
+            return self
+
+        # The related instance is loaded from the database and then cached
+        # by the field on the model instance state. It can also be pre-cached
+        # by the forward accessor (ForwardManyToOneDescriptor).
+        try:
+            rel_obj = self.related.get_cached_value(instance)
+        except KeyError:
+            related_pk = instance.pk
+            if related_pk is None:
+                rel_obj = None
+            else:
+                filter_args = self.related.field.get_forward_related_filter(instance)
+                try:
+                    rel_obj = self.get_queryset(instance=instance).get(**filter_args)
+                except self.related.related_model.DoesNotExist:
+                    rel_obj = None
+                else:
+                    # Set the forward accessor cache on the related object to
+                    # the current instance to avoid an extra SQL query if it's
+                    # accessed later on.
+                    self.related.field.set_cached_value(rel_obj, instance)
+            self.related.set_cached_value(instance, rel_obj)
+
+        if rel_obj is None:
+            raise self.RelatedObjectDoesNotExist(
+                "%s has no %s."
+                % (instance.__class__.__name__, self.related.get_accessor_name())
+            )
+        else:
+            return rel_obj
+
+    def __set__(self, instance, value):
+        """
+        Set the related instance through the reverse relation.
+
+        With the example above, when setting ``place.restaurant = restaurant``:
+
+        - ``self`` is the descriptor managing the ``restaurant`` attribute
+        - ``instance`` is the ``place`` instance
+        - ``value`` is the ``restaurant`` instance on the right of the equal sign
+
+        Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
+        """
+        # The similarity of the code below to the code in
+        # ForwardManyToOneDescriptor is annoying, but there's a bunch
+        # of small differences that would make a common base class convoluted.
+
+        if value is None:
+            # Update the cached related instance (if any) & clear the cache.
+            # Following the example above, this would be the cached
+            # ``restaurant`` instance (if any).
+            rel_obj = self.related.get_cached_value(instance, default=None)
+            if rel_obj is not None:
+                # Remove the ``restaurant`` instance from the ``place``
+                # instance cache.
+                self.related.delete_cached_value(instance)
+                # Set the ``place`` field on the ``restaurant``
+                # instance to None.
+                setattr(rel_obj, self.related.field.name, None)
+        elif not isinstance(value, self.related.related_model):
+            # An object must be an instance of the related class.
+            raise ValueError(
+                'Cannot assign "%r": "%s.%s" must be a "%s" instance.'
+                % (
+                    value,
+                    instance._meta.object_name,
+                    self.related.get_accessor_name(),
+                    self.related.related_model._meta.object_name,
+                )
+            )
+        else:
+            if instance._state.db is None:
+                instance._state.db = router.db_for_write(
+                    instance.__class__, instance=value
+                )
+            if value._state.db is None:
+                value._state.db = router.db_for_write(
+                    value.__class__, instance=instance
+                )
+            if not router.allow_relation(value, instance):
+                raise ValueError(
+                    'Cannot assign "%r": the current database router prevents this '
+                    "relation." % value
+                )
+
+            related_pk = tuple(
+                getattr(instance, field.attname)
+                for field in self.related.field.foreign_related_fields
+            )
+            # Set the value of the related field to the value of the related
+            # object's related field.
+            for index, field in enumerate(self.related.field.local_related_fields):
+                setattr(value, field.attname, related_pk[index])
+
+            # Set the related instance cache used by __get__ to avoid an SQL query
+            # when accessing the attribute we just set.
+            self.related.set_cached_value(instance, value)
+
+            # Set the forward accessor cache on the related object to the current
+            # instance to avoid an extra SQL query if it's accessed later on.
+            self.related.field.set_cached_value(value, instance)
+
+    def __reduce__(self):
+        # Same purpose as ForwardManyToOneDescriptor.__reduce__().
+        return getattr, (self.related.model, self.related.name)
+
+
+class ReverseManyToOneDescriptor:
+    """
+    Accessor to the related objects manager on the reverse side of a
+    many-to-one relation.
+
+    In the example::
+
+        class Child(Model):
+            parent = ForeignKey(Parent, related_name='children')
+
+    ``Parent.children`` is a ``ReverseManyToOneDescriptor`` instance.
+
+    Most of the implementation is delegated to a dynamically defined manager
+    class built by ``create_forward_many_to_many_manager()`` defined below.
+    """
+
+    def __init__(self, rel):
+        self.rel = rel
+        self.field = rel.field
+
+    @cached_property
+    def related_manager_cls(self):
+        related_model = self.rel.related_model
+
+        return create_reverse_many_to_one_manager(
+            related_model._default_manager.__class__,
+            self.rel,
+        )
+
+    def __get__(self, instance, cls=None):
+        """
+        Get the related objects through the reverse relation.
+
+        With the example above, when getting ``parent.children``:
+
+        - ``self`` is the descriptor managing the ``children`` attribute
+        - ``instance`` is the ``parent`` instance
+        - ``cls`` is the ``Parent`` class (unused)
+        """
+        if instance is None:
+            return self
+
+        return self.related_manager_cls(instance)
+
+    def _get_set_deprecation_msg_params(self):
+        return (
+            "reverse side of a related set",
+            self.rel.get_accessor_name(),
+        )
+
+    def __set__(self, instance, value):
+        raise TypeError(
+            "Direct assignment to the %s is prohibited. Use %s.set() instead."
+            % self._get_set_deprecation_msg_params(),
+        )
+
+
+def create_reverse_many_to_one_manager(superclass, rel):
+    """
+    Create a manager for the reverse side of a many-to-one relation.
+
+    This manager subclasses another manager, generally the default manager of
+    the related model, and adds behaviors specific to many-to-one relations.
+    """
+
+    class RelatedManager(superclass, AltersData):
+        def __init__(self, instance):
+            super().__init__()
+
+            self.instance = instance
+            self.model = rel.related_model
+            self.field = rel.field
+
+            self.core_filters = {self.field.name: instance}
+
+        def __call__(self, *, manager):
+            manager = getattr(self.model, manager)
+            manager_class = create_reverse_many_to_one_manager(manager.__class__, rel)
+            return manager_class(self.instance)
+
+        do_not_call_in_templates = True
+
+        def _check_fk_val(self):
+            for field in self.field.foreign_related_fields:
+                if getattr(self.instance, field.attname) is None:
+                    raise ValueError(
+                        f'"{self.instance!r}" needs to have a value for field '
+                        f'"{field.attname}" before this relationship can be used.'
+                    )
+
+        def _apply_rel_filters(self, queryset):
+            """
+            Filter the queryset for the instance this manager is bound to.
+            """
+            db = self._db or router.db_for_read(self.model, instance=self.instance)
+            empty_strings_as_null = connections[
+                db
+            ].features.interprets_empty_strings_as_nulls
+            queryset._add_hints(instance=self.instance)
+            if self._db:
+                queryset = queryset.using(self._db)
+            queryset._defer_next_filter = True
+            queryset = queryset.filter(**self.core_filters)
+            for field in self.field.foreign_related_fields:
+                val = getattr(self.instance, field.attname)
+                if val is None or (val == "" and empty_strings_as_null):
+                    return queryset.none()
+            if self.field.many_to_one:
+                # Guard against field-like objects such as GenericRelation
+                # that abuse create_reverse_many_to_one_manager() with reverse
+                # one-to-many relationships instead and break known related
+                # objects assignment.
+                try:
+                    target_field = self.field.target_field
+                except FieldError:
+                    # The relationship has multiple target fields. Use a tuple
+                    # for related object id.
+                    rel_obj_id = tuple(
+                        [
+                            getattr(self.instance, target_field.attname)
+                            for target_field in self.field.path_infos[-1].target_fields
+                        ]
+                    )
+                else:
+                    rel_obj_id = getattr(self.instance, target_field.attname)
+                queryset._known_related_objects = {
+                    self.field: {rel_obj_id: self.instance}
+                }
+            return queryset
+
+        def _remove_prefetched_objects(self):
+            try:
+                self.instance._prefetched_objects_cache.pop(
+                    self.field.remote_field.get_cache_name()
+                )
+            except (AttributeError, KeyError):
+                pass  # nothing to clear from cache
+
+        def get_queryset(self):
+            # Even if this relation is not to pk, we require still pk value.
+            # The wish is that the instance has been already saved to DB,
+            # although having a pk value isn't a guarantee of that.
+            if self.instance.pk is None:
+                raise ValueError(
+                    f"{self.instance.__class__.__name__!r} instance needs to have a "
+                    f"primary key value before this relationship can be used."
+                )
+            try:
+                return self.instance._prefetched_objects_cache[
+                    self.field.remote_field.get_cache_name()
+                ]
+            except (AttributeError, KeyError):
+                queryset = super().get_queryset()
+                return self._apply_rel_filters(queryset)
+
+        def get_prefetch_queryset(self, instances, queryset=None):
+            if queryset is None:
+                queryset = super().get_queryset()
+
+            queryset._add_hints(instance=instances[0])
+            queryset = queryset.using(queryset._db or self._db)
+
+            rel_obj_attr = self.field.get_local_related_value
+            instance_attr = self.field.get_foreign_related_value
+            instances_dict = {instance_attr(inst): inst for inst in instances}
+            queryset = _filter_prefetch_queryset(queryset, self.field.name, instances)
+
+            # Since we just bypassed this class' get_queryset(), we must manage
+            # the reverse relation manually.
+            for rel_obj in queryset:
+                if not self.field.is_cached(rel_obj):
+                    instance = instances_dict[rel_obj_attr(rel_obj)]
+                    setattr(rel_obj, self.field.name, instance)
+            cache_name = self.field.remote_field.get_cache_name()
+            return queryset, rel_obj_attr, instance_attr, False, cache_name, False
+
+        def add(self, *objs, bulk=True):
+            self._check_fk_val()
+            self._remove_prefetched_objects()
+            db = router.db_for_write(self.model, instance=self.instance)
+
+            def check_and_update_obj(obj):
+                if not isinstance(obj, self.model):
+                    raise TypeError(
+                        "'%s' instance expected, got %r"
+                        % (
+                            self.model._meta.object_name,
+                            obj,
+                        )
+                    )
+                setattr(obj, self.field.name, self.instance)
+
+            if bulk:
+                pks = []
+                for obj in objs:
+                    check_and_update_obj(obj)
+                    if obj._state.adding or obj._state.db != db:
+                        raise ValueError(
+                            "%r instance isn't saved. Use bulk=False or save "
+                            "the object first." % obj
+                        )
+                    pks.append(obj.pk)
+                self.model._base_manager.using(db).filter(pk__in=pks).update(
+                    **{
+                        self.field.name: self.instance,
+                    }
+                )
+            else:
+                with transaction.atomic(using=db, savepoint=False):
+                    for obj in objs:
+                        check_and_update_obj(obj)
+                        obj.save()
+
+        add.alters_data = True
+
+        async def aadd(self, *objs, bulk=True):
+            return await sync_to_async(self.add)(*objs, bulk=bulk)
+
+        aadd.alters_data = True
+
+        def create(self, **kwargs):
+            self._check_fk_val()
+            kwargs[self.field.name] = self.instance
+            db = router.db_for_write(self.model, instance=self.instance)
+            return super(RelatedManager, self.db_manager(db)).create(**kwargs)
+
+        create.alters_data = True
+
+        async def acreate(self, **kwargs):
+            return await sync_to_async(self.create)(**kwargs)
+
+        acreate.alters_data = True
+
+        def get_or_create(self, **kwargs):
+            self._check_fk_val()
+            kwargs[self.field.name] = self.instance
+            db = router.db_for_write(self.model, instance=self.instance)
+            return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
+
+        get_or_create.alters_data = True
+
+        async def aget_or_create(self, **kwargs):
+            return await sync_to_async(self.get_or_create)(**kwargs)
+
+        aget_or_create.alters_data = True
+
+        def update_or_create(self, **kwargs):
+            self._check_fk_val()
+            kwargs[self.field.name] = self.instance
+            db = router.db_for_write(self.model, instance=self.instance)
+            return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
+
+        update_or_create.alters_data = True
+
+        async def aupdate_or_create(self, **kwargs):
+            return await sync_to_async(self.update_or_create)(**kwargs)
+
+        aupdate_or_create.alters_data = True
+
+        # remove() and clear() are only provided if the ForeignKey can have a
+        # value of null.
+        if rel.field.null:
+
+            def remove(self, *objs, bulk=True):
+                if not objs:
+                    return
+                self._check_fk_val()
+                val = self.field.get_foreign_related_value(self.instance)
+                old_ids = set()
+                for obj in objs:
+                    if not isinstance(obj, self.model):
+                        raise TypeError(
+                            "'%s' instance expected, got %r"
+                            % (
+                                self.model._meta.object_name,
+                                obj,
+                            )
+                        )
+                    # Is obj actually part of this descriptor set?
+                    if self.field.get_local_related_value(obj) == val:
+                        old_ids.add(obj.pk)
+                    else:
+                        raise self.field.remote_field.model.DoesNotExist(
+                            "%r is not related to %r." % (obj, self.instance)
+                        )
+                self._clear(self.filter(pk__in=old_ids), bulk)
+
+            remove.alters_data = True
+
+            async def aremove(self, *objs, bulk=True):
+                return await sync_to_async(self.remove)(*objs, bulk=bulk)
+
+            aremove.alters_data = True
+
+            def clear(self, *, bulk=True):
+                self._check_fk_val()
+                self._clear(self, bulk)
+
+            clear.alters_data = True
+
+            async def aclear(self, *, bulk=True):
+                return await sync_to_async(self.clear)(bulk=bulk)
+
+            aclear.alters_data = True
+
+            def _clear(self, queryset, bulk):
+                self._remove_prefetched_objects()
+                db = router.db_for_write(self.model, instance=self.instance)
+                queryset = queryset.using(db)
+                if bulk:
+                    # `QuerySet.update()` is intrinsically atomic.
+                    queryset.update(**{self.field.name: None})
+                else:
+                    with transaction.atomic(using=db, savepoint=False):
+                        for obj in queryset:
+                            setattr(obj, self.field.name, None)
+                            obj.save(update_fields=[self.field.name])
+
+            _clear.alters_data = True
+
+        def set(self, objs, *, bulk=True, clear=False):
+            self._check_fk_val()
+            # Force evaluation of `objs` in case it's a queryset whose value
+            # could be affected by `manager.clear()`. Refs #19816.
+            objs = tuple(objs)
+
+            if self.field.null:
+                db = router.db_for_write(self.model, instance=self.instance)
+                with transaction.atomic(using=db, savepoint=False):
+                    if clear:
+                        self.clear(bulk=bulk)
+                        self.add(*objs, bulk=bulk)
+                    else:
+                        old_objs = set(self.using(db).all())
+                        new_objs = []
+                        for obj in objs:
+                            if obj in old_objs:
+                                old_objs.remove(obj)
+                            else:
+                                new_objs.append(obj)
+
+                        self.remove(*old_objs, bulk=bulk)
+                        self.add(*new_objs, bulk=bulk)
+            else:
+                self.add(*objs, bulk=bulk)
+
+        set.alters_data = True
+
+        async def aset(self, objs, *, bulk=True, clear=False):
+            return await sync_to_async(self.set)(objs=objs, bulk=bulk, clear=clear)
+
+        aset.alters_data = True
+
+    return RelatedManager
+
+
+class ManyToManyDescriptor(ReverseManyToOneDescriptor):
+    """
+    Accessor to the related objects manager on the forward and reverse sides of
+    a many-to-many relation.
+
+    In the example::
+
+        class Pizza(Model):
+            toppings = ManyToManyField(Topping, related_name='pizzas')
+
+    ``Pizza.toppings`` and ``Topping.pizzas`` are ``ManyToManyDescriptor``
+    instances.
+
+    Most of the implementation is delegated to a dynamically defined manager
+    class built by ``create_forward_many_to_many_manager()`` defined below.
+    """
+
+    def __init__(self, rel, reverse=False):
+        super().__init__(rel)
+
+        self.reverse = reverse
+
+    @property
+    def through(self):
+        # through is provided so that you have easy access to the through
+        # model (Book.authors.through) for inlines, etc. This is done as
+        # a property to ensure that the fully resolved value is returned.
+        return self.rel.through
+
+    @cached_property
+    def related_manager_cls(self):
+        related_model = self.rel.related_model if self.reverse else self.rel.model
+
+        return create_forward_many_to_many_manager(
+            related_model._default_manager.__class__,
+            self.rel,
+            reverse=self.reverse,
+        )
+
+    def _get_set_deprecation_msg_params(self):
+        return (
+            "%s side of a many-to-many set"
+            % ("reverse" if self.reverse else "forward"),
+            self.rel.get_accessor_name() if self.reverse else self.field.name,
+        )
+
+
+def create_forward_many_to_many_manager(superclass, rel, reverse):
+    """
+    Create a manager for the either side of a many-to-many relation.
+
+    This manager subclasses another manager, generally the default manager of
+    the related model, and adds behaviors specific to many-to-many relations.
+    """
+
+    class ManyRelatedManager(superclass, AltersData):
+        def __init__(self, instance=None):
+            super().__init__()
+
+            self.instance = instance
+
+            if not reverse:
+                self.model = rel.model
+                self.query_field_name = rel.field.related_query_name()
+                self.prefetch_cache_name = rel.field.name
+                self.source_field_name = rel.field.m2m_field_name()
+                self.target_field_name = rel.field.m2m_reverse_field_name()
+                self.symmetrical = rel.symmetrical
+            else:
+                self.model = rel.related_model
+                self.query_field_name = rel.field.name
+                self.prefetch_cache_name = rel.field.related_query_name()
+                self.source_field_name = rel.field.m2m_reverse_field_name()
+                self.target_field_name = rel.field.m2m_field_name()
+                self.symmetrical = False
+
+            self.through = rel.through
+            self.reverse = reverse
+
+            self.source_field = self.through._meta.get_field(self.source_field_name)
+            self.target_field = self.through._meta.get_field(self.target_field_name)
+
+            self.core_filters = {}
+            self.pk_field_names = {}
+            for lh_field, rh_field in self.source_field.related_fields:
+                core_filter_key = "%s__%s" % (self.query_field_name, rh_field.name)
+                self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
+                self.pk_field_names[lh_field.name] = rh_field.name
+
+            self.related_val = self.source_field.get_foreign_related_value(instance)
+            if None in self.related_val:
+                raise ValueError(
+                    '"%r" needs to have a value for field "%s" before '
+                    "this many-to-many relationship can be used."
+                    % (instance, self.pk_field_names[self.source_field_name])
+                )
+            # Even if this relation is not to pk, we require still pk value.
+            # The wish is that the instance has been already saved to DB,
+            # although having a pk value isn't a guarantee of that.
+            if instance.pk is None:
+                raise ValueError(
+                    "%r instance needs to have a primary key value before "
+                    "a many-to-many relationship can be used."
+                    % instance.__class__.__name__
+                )
+
+        def __call__(self, *, manager):
+            manager = getattr(self.model, manager)
+            manager_class = create_forward_many_to_many_manager(
+                manager.__class__, rel, reverse
+            )
+            return manager_class(instance=self.instance)
+
+        do_not_call_in_templates = True
+
+        def _build_remove_filters(self, removed_vals):
+            filters = Q.create([(self.source_field_name, self.related_val)])
+            # No need to add a subquery condition if removed_vals is a QuerySet without
+            # filters.
+            removed_vals_filters = (
+                not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
+            )
+            if removed_vals_filters:
+                filters &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
+            if self.symmetrical:
+                symmetrical_filters = Q.create(
+                    [(self.target_field_name, self.related_val)]
+                )
+                if removed_vals_filters:
+                    symmetrical_filters &= Q.create(
+                        [(f"{self.source_field_name}__in", removed_vals)]
+                    )
+                filters |= symmetrical_filters
+            return filters
+
+        def _apply_rel_filters(self, queryset):
+            """
+            Filter the queryset for the instance this manager is bound to.
+            """
+            queryset._add_hints(instance=self.instance)
+            if self._db:
+                queryset = queryset.using(self._db)
+            queryset._defer_next_filter = True
+            return queryset._next_is_sticky().filter(**self.core_filters)
+
+        def _remove_prefetched_objects(self):
+            try:
+                self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
+            except (AttributeError, KeyError):
+                pass  # nothing to clear from cache
+
+        def get_queryset(self):
+            try:
+                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
+            except (AttributeError, KeyError):
+                queryset = super().get_queryset()
+                return self._apply_rel_filters(queryset)
+
+        def get_prefetch_queryset(self, instances, queryset=None):
+            if queryset is None:
+                queryset = super().get_queryset()
+
+            queryset._add_hints(instance=instances[0])
+            queryset = queryset.using(queryset._db or self._db)
+            queryset = _filter_prefetch_queryset(
+                queryset._next_is_sticky(), self.query_field_name, instances
+            )
+
+            # M2M: need to annotate the query in order to get the primary model
+            # that the secondary model was actually related to. We know that
+            # there will already be a join on the join table, so we can just add
+            # the select.
+
+            # For non-autocreated 'through' models, can't assume we are
+            # dealing with PK values.
+            fk = self.through._meta.get_field(self.source_field_name)
+            join_table = fk.model._meta.db_table
+            connection = connections[queryset.db]
+            qn = connection.ops.quote_name
+            queryset = queryset.extra(
+                select={
+                    "_prefetch_related_val_%s"
+                    % f.attname: "%s.%s"
+                    % (qn(join_table), qn(f.column))
+                    for f in fk.local_related_fields
+                }
+            )
+            return (
+                queryset,
+                lambda result: tuple(
+                    getattr(result, "_prefetch_related_val_%s" % f.attname)
+                    for f in fk.local_related_fields
+                ),
+                lambda inst: tuple(
+                    f.get_db_prep_value(getattr(inst, f.attname), connection)
+                    for f in fk.foreign_related_fields
+                ),
+                False,
+                self.prefetch_cache_name,
+                False,
+            )
+
+        def add(self, *objs, through_defaults=None):
+            self._remove_prefetched_objects()
+            db = router.db_for_write(self.through, instance=self.instance)
+            with transaction.atomic(using=db, savepoint=False):
+                self._add_items(
+                    self.source_field_name,
+                    self.target_field_name,
+                    *objs,
+                    through_defaults=through_defaults,
+                )
+                # If this is a symmetrical m2m relation to self, add the mirror
+                # entry in the m2m table.
+                if self.symmetrical:
+                    self._add_items(
+                        self.target_field_name,
+                        self.source_field_name,
+                        *objs,
+                        through_defaults=through_defaults,
+                    )
+
+        add.alters_data = True
+
+        async def aadd(self, *objs, through_defaults=None):
+            return await sync_to_async(self.add)(
+                *objs, through_defaults=through_defaults
+            )
+
+        aadd.alters_data = True
+
+        def remove(self, *objs):
+            self._remove_prefetched_objects()
+            self._remove_items(self.source_field_name, self.target_field_name, *objs)
+
+        remove.alters_data = True
+
+        async def aremove(self, *objs):
+            return await sync_to_async(self.remove)(*objs)
+
+        aremove.alters_data = True
+
+        def clear(self):
+            db = router.db_for_write(self.through, instance=self.instance)
+            with transaction.atomic(using=db, savepoint=False):
+                signals.m2m_changed.send(
+                    sender=self.through,
+                    action="pre_clear",
+                    instance=self.instance,
+                    reverse=self.reverse,
+                    model=self.model,
+                    pk_set=None,
+                    using=db,
+                )
+                self._remove_prefetched_objects()
+                filters = self._build_remove_filters(super().get_queryset().using(db))
+                self.through._default_manager.using(db).filter(filters).delete()
+
+                signals.m2m_changed.send(
+                    sender=self.through,
+                    action="post_clear",
+                    instance=self.instance,
+                    reverse=self.reverse,
+                    model=self.model,
+                    pk_set=None,
+                    using=db,
+                )
+
+        clear.alters_data = True
+
+        async def aclear(self):
+            return await sync_to_async(self.clear)()
+
+        aclear.alters_data = True
+
+        def set(self, objs, *, clear=False, through_defaults=None):
+            # Force evaluation of `objs` in case it's a queryset whose value
+            # could be affected by `manager.clear()`. Refs #19816.
+            objs = tuple(objs)
+
+            db = router.db_for_write(self.through, instance=self.instance)
+            with transaction.atomic(using=db, savepoint=False):
+                if clear:
+                    self.clear()
+                    self.add(*objs, through_defaults=through_defaults)
+                else:
+                    old_ids = set(
+                        self.using(db).values_list(
+                            self.target_field.target_field.attname, flat=True
+                        )
+                    )
+
+                    new_objs = []
+                    for obj in objs:
+                        fk_val = (
+                            self.target_field.get_foreign_related_value(obj)[0]
+                            if isinstance(obj, self.model)
+                            else self.target_field.get_prep_value(obj)
+                        )
+                        if fk_val in old_ids:
+                            old_ids.remove(fk_val)
+                        else:
+                            new_objs.append(obj)
+
+                    self.remove(*old_ids)
+                    self.add(*new_objs, through_defaults=through_defaults)
+
+        set.alters_data = True
+
+        async def aset(self, objs, *, clear=False, through_defaults=None):
+            return await sync_to_async(self.set)(
+                objs=objs, clear=clear, through_defaults=through_defaults
+            )
+
+        aset.alters_data = True
+
+        def create(self, *, through_defaults=None, **kwargs):
+            db = router.db_for_write(self.instance.__class__, instance=self.instance)
+            new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs)
+            self.add(new_obj, through_defaults=through_defaults)
+            return new_obj
+
+        create.alters_data = True
+
+        async def acreate(self, *, through_defaults=None, **kwargs):
+            return await sync_to_async(self.create)(
+                through_defaults=through_defaults, **kwargs
+            )
+
+        acreate.alters_data = True
+
+        def get_or_create(self, *, through_defaults=None, **kwargs):
+            db = router.db_for_write(self.instance.__class__, instance=self.instance)
+            obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(
+                **kwargs
+            )
+            # We only need to add() if created because if we got an object back
+            # from get() then the relationship already exists.
+            if created:
+                self.add(obj, through_defaults=through_defaults)
+            return obj, created
+
+        get_or_create.alters_data = True
+
+        async def aget_or_create(self, *, through_defaults=None, **kwargs):
+            return await sync_to_async(self.get_or_create)(
+                through_defaults=through_defaults, **kwargs
+            )
+
+        aget_or_create.alters_data = True
+
+        def update_or_create(self, *, through_defaults=None, **kwargs):
+            db = router.db_for_write(self.instance.__class__, instance=self.instance)
+            obj, created = super(
+                ManyRelatedManager, self.db_manager(db)
+            ).update_or_create(**kwargs)
+            # We only need to add() if created because if we got an object back
+            # from get() then the relationship already exists.
+            if created:
+                self.add(obj, through_defaults=through_defaults)
+            return obj, created
+
+        update_or_create.alters_data = True
+
+        async def aupdate_or_create(self, *, through_defaults=None, **kwargs):
+            return await sync_to_async(self.update_or_create)(
+                through_defaults=through_defaults, **kwargs
+            )
+
+        aupdate_or_create.alters_data = True
+
+        def _get_target_ids(self, target_field_name, objs):
+            """
+            Return the set of ids of `objs` that the target field references.
+            """
+            from django.db.models import Model
+
+            target_ids = set()
+            target_field = self.through._meta.get_field(target_field_name)
+            for obj in objs:
+                if isinstance(obj, self.model):
+                    if not router.allow_relation(obj, self.instance):
+                        raise ValueError(
+                            'Cannot add "%r": instance is on database "%s", '
+                            'value is on database "%s"'
+                            % (obj, self.instance._state.db, obj._state.db)
+                        )
+                    target_id = target_field.get_foreign_related_value(obj)[0]
+                    if target_id is None:
+                        raise ValueError(
+                            'Cannot add "%r": the value for field "%s" is None'
+                            % (obj, target_field_name)
+                        )
+                    target_ids.add(target_id)
+                elif isinstance(obj, Model):
+                    raise TypeError(
+                        "'%s' instance expected, got %r"
+                        % (self.model._meta.object_name, obj)
+                    )
+                else:
+                    target_ids.add(target_field.get_prep_value(obj))
+            return target_ids
+
+        def _get_missing_target_ids(
+            self, source_field_name, target_field_name, db, target_ids
+        ):
+            """
+            Return the subset of ids of `objs` that aren't already assigned to
+            this relationship.
+            """
+            vals = (
+                self.through._default_manager.using(db)
+                .values_list(target_field_name, flat=True)
+                .filter(
+                    **{
+                        source_field_name: self.related_val[0],
+                        "%s__in" % target_field_name: target_ids,
+                    }
+                )
+            )
+            return target_ids.difference(vals)
+
+        def _get_add_plan(self, db, source_field_name):
+            """
+            Return a boolean triple of the way the add should be performed.
+
+            The first element is whether or not bulk_create(ignore_conflicts)
+            can be used, the second whether or not signals must be sent, and
+            the third element is whether or not the immediate bulk insertion
+            with conflicts ignored can be performed.
+            """
+            # Conflicts can be ignored when the intermediary model is
+            # auto-created as the only possible collision is on the
+            # (source_id, target_id) tuple. The same assertion doesn't hold for
+            # user-defined intermediary models as they could have other fields
+            # causing conflicts which must be surfaced.
+            can_ignore_conflicts = (
+                self.through._meta.auto_created is not False
+                and connections[db].features.supports_ignore_conflicts
+            )
+            # Don't send the signal when inserting duplicate data row
+            # for symmetrical reverse entries.
+            must_send_signals = (
+                self.reverse or source_field_name == self.source_field_name
+            ) and (signals.m2m_changed.has_listeners(self.through))
+            # Fast addition through bulk insertion can only be performed
+            # if no m2m_changed listeners are connected for self.through
+            # as they require the added set of ids to be provided via
+            # pk_set.
+            return (
+                can_ignore_conflicts,
+                must_send_signals,
+                (can_ignore_conflicts and not must_send_signals),
+            )
+
+        def _add_items(
+            self, source_field_name, target_field_name, *objs, through_defaults=None
+        ):
+            # source_field_name: the PK fieldname in join table for the source object
+            # target_field_name: the PK fieldname in join table for the target object
+            # *objs - objects to add. Either object instances, or primary keys
+            # of object instances.
+            if not objs:
+                return
+
+            through_defaults = dict(resolve_callables(through_defaults or {}))
+            target_ids = self._get_target_ids(target_field_name, objs)
+            db = router.db_for_write(self.through, instance=self.instance)
+            can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan(
+                db, source_field_name
+            )
+            if can_fast_add:
+                self.through._default_manager.using(db).bulk_create(
+                    [
+                        self.through(
+                            **{
+                                "%s_id" % source_field_name: self.related_val[0],
+                                "%s_id" % target_field_name: target_id,
+                            }
+                        )
+                        for target_id in target_ids
+                    ],
+                    ignore_conflicts=True,
+                )
+                return
+
+            missing_target_ids = self._get_missing_target_ids(
+                source_field_name, target_field_name, db, target_ids
+            )
+            with transaction.atomic(using=db, savepoint=False):
+                if must_send_signals:
+                    signals.m2m_changed.send(
+                        sender=self.through,
+                        action="pre_add",
+                        instance=self.instance,
+                        reverse=self.reverse,
+                        model=self.model,
+                        pk_set=missing_target_ids,
+                        using=db,
+                    )
+                # Add the ones that aren't there already.
+                self.through._default_manager.using(db).bulk_create(
+                    [
+                        self.through(
+                            **through_defaults,
+                            **{
+                                "%s_id" % source_field_name: self.related_val[0],
+                                "%s_id" % target_field_name: target_id,
+                            },
+                        )
+                        for target_id in missing_target_ids
+                    ],
+                    ignore_conflicts=can_ignore_conflicts,
+                )
+
+                if must_send_signals:
+                    signals.m2m_changed.send(
+                        sender=self.through,
+                        action="post_add",
+                        instance=self.instance,
+                        reverse=self.reverse,
+                        model=self.model,
+                        pk_set=missing_target_ids,
+                        using=db,
+                    )
+
+        def _remove_items(self, source_field_name, target_field_name, *objs):
+            # source_field_name: the PK colname in join table for the source object
+            # target_field_name: the PK colname in join table for the target object
+            # *objs - objects to remove. Either object instances, or primary
+            # keys of object instances.
+            if not objs:
+                return
+
+            # Check that all the objects are of the right type
+            old_ids = set()
+            for obj in objs:
+                if isinstance(obj, self.model):
+                    fk_val = self.target_field.get_foreign_related_value(obj)[0]
+                    old_ids.add(fk_val)
+                else:
+                    old_ids.add(obj)
+
+            db = router.db_for_write(self.through, instance=self.instance)
+            with transaction.atomic(using=db, savepoint=False):
+                # Send a signal to the other end if need be.
+                signals.m2m_changed.send(
+                    sender=self.through,
+                    action="pre_remove",
+                    instance=self.instance,
+                    reverse=self.reverse,
+                    model=self.model,
+                    pk_set=old_ids,
+                    using=db,
+                )
+                target_model_qs = super().get_queryset()
+                if target_model_qs._has_filters():
+                    old_vals = target_model_qs.using(db).filter(
+                        **{"%s__in" % self.target_field.target_field.attname: old_ids}
+                    )
+                else:
+                    old_vals = old_ids
+                filters = self._build_remove_filters(old_vals)
+                self.through._default_manager.using(db).filter(filters).delete()
+
+                signals.m2m_changed.send(
+                    sender=self.through,
+                    action="post_remove",
+                    instance=self.instance,
+                    reverse=self.reverse,
+                    model=self.model,
+                    pk_set=old_ids,
+                    using=db,
+                )
+
+    return ManyRelatedManager
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/django/db/models/query.html b/docs/_build/html/_modules/django/db/models/query.html new file mode 100644 index 00000000..10e89af3 --- /dev/null +++ b/docs/_build/html/_modules/django/db/models/query.html @@ -0,0 +1,2732 @@ + + + + + + django.db.models.query — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for django.db.models.query

+"""
+The main QuerySet implementation. This provides the public API for the ORM.
+"""
+
+import copy
+import operator
+import warnings
+from itertools import chain, islice
+
+from asgiref.sync import sync_to_async
+
+import django
+from django.conf import settings
+from django.core import exceptions
+from django.db import (
+    DJANGO_VERSION_PICKLE_KEY,
+    IntegrityError,
+    NotSupportedError,
+    connections,
+    router,
+    transaction,
+)
+from django.db.models import AutoField, DateField, DateTimeField, Field, sql
+from django.db.models.constants import LOOKUP_SEP, OnConflict
+from django.db.models.deletion import Collector
+from django.db.models.expressions import Case, F, Value, When
+from django.db.models.functions import Cast, Trunc
+from django.db.models.query_utils import FilteredRelation, Q
+from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
+from django.db.models.utils import (
+    AltersData,
+    create_namedtuple_class,
+    resolve_callables,
+)
+from django.utils import timezone
+from django.utils.deprecation import RemovedInDjango50Warning
+from django.utils.functional import cached_property, partition
+
+# The maximum number of results to fetch in a get() query.
+MAX_GET_RESULTS = 21
+
+# The maximum number of items to display in a QuerySet.__repr__
+REPR_OUTPUT_SIZE = 20
+
+
+class BaseIterable:
+    def __init__(
+        self, queryset, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE
+    ):
+        self.queryset = queryset
+        self.chunked_fetch = chunked_fetch
+        self.chunk_size = chunk_size
+
+    async def _async_generator(self):
+        # Generators don't actually start running until the first time you call
+        # next() on them, so make the generator object in the async thread and
+        # then repeatedly dispatch to it in a sync thread.
+        sync_generator = self.__iter__()
+
+        def next_slice(gen):
+            return list(islice(gen, self.chunk_size))
+
+        while True:
+            chunk = await sync_to_async(next_slice)(sync_generator)
+            for item in chunk:
+                yield item
+            if len(chunk) < self.chunk_size:
+                break
+
+    # __aiter__() is a *synchronous* method that has to then return an
+    # *asynchronous* iterator/generator. Thus, nest an async generator inside
+    # it.
+    # This is a generic iterable converter for now, and is going to suffer a
+    # performance penalty on large sets of items due to the cost of crossing
+    # over the sync barrier for each chunk. Custom __aiter__() methods should
+    # be added to each Iterable subclass, but that needs some work in the
+    # Compiler first.
+    def __aiter__(self):
+        return self._async_generator()
+
+
+class ModelIterable(BaseIterable):
+    """Iterable that yields a model instance for each row."""
+
+    def __iter__(self):
+        queryset = self.queryset
+        db = queryset.db
+        compiler = queryset.query.get_compiler(using=db)
+        # Execute the query. This will also fill compiler.select, klass_info,
+        # and annotations.
+        results = compiler.execute_sql(
+            chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
+        )
+        select, klass_info, annotation_col_map = (
+            compiler.select,
+            compiler.klass_info,
+            compiler.annotation_col_map,
+        )
+        model_cls = klass_info["model"]
+        select_fields = klass_info["select_fields"]
+        model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1
+        init_list = [
+            f[0].target.attname for f in select[model_fields_start:model_fields_end]
+        ]
+        related_populators = get_related_populators(klass_info, select, db)
+        known_related_objects = [
+            (
+                field,
+                related_objs,
+                operator.attrgetter(
+                    *[
+                        field.attname
+                        if from_field == "self"
+                        else queryset.model._meta.get_field(from_field).attname
+                        for from_field in field.from_fields
+                    ]
+                ),
+            )
+            for field, related_objs in queryset._known_related_objects.items()
+        ]
+        for row in compiler.results_iter(results):
+            obj = model_cls.from_db(
+                db, init_list, row[model_fields_start:model_fields_end]
+            )
+            for rel_populator in related_populators:
+                rel_populator.populate(row, obj)
+            if annotation_col_map:
+                for attr_name, col_pos in annotation_col_map.items():
+                    setattr(obj, attr_name, row[col_pos])
+
+            # Add the known related objects to the model.
+            for field, rel_objs, rel_getter in known_related_objects:
+                # Avoid overwriting objects loaded by, e.g., select_related().
+                if field.is_cached(obj):
+                    continue
+                rel_obj_id = rel_getter(obj)
+                try:
+                    rel_obj = rel_objs[rel_obj_id]
+                except KeyError:
+                    pass  # May happen in qs1 | qs2 scenarios.
+                else:
+                    setattr(obj, field.name, rel_obj)
+
+            yield obj
+
+
+class RawModelIterable(BaseIterable):
+    """
+    Iterable that yields a model instance for each row from a raw queryset.
+    """
+
+    def __iter__(self):
+        # Cache some things for performance reasons outside the loop.
+        db = self.queryset.db
+        query = self.queryset.query
+        connection = connections[db]
+        compiler = connection.ops.compiler("SQLCompiler")(query, connection, db)
+        query_iterator = iter(query)
+
+        try:
+            (
+                model_init_names,
+                model_init_pos,
+                annotation_fields,
+            ) = self.queryset.resolve_model_init_order()
+            model_cls = self.queryset.model
+            if model_cls._meta.pk.attname not in model_init_names:
+                raise exceptions.FieldDoesNotExist(
+                    "Raw query must include the primary key"
+                )
+            fields = [self.queryset.model_fields.get(c) for c in self.queryset.columns]
+            converters = compiler.get_converters(
+                [f.get_col(f.model._meta.db_table) if f else None for f in fields]
+            )
+            if converters:
+                query_iterator = compiler.apply_converters(query_iterator, converters)
+            for values in query_iterator:
+                # Associate fields to values
+                model_init_values = [values[pos] for pos in model_init_pos]
+                instance = model_cls.from_db(db, model_init_names, model_init_values)
+                if annotation_fields:
+                    for column, pos in annotation_fields:
+                        setattr(instance, column, values[pos])
+                yield instance
+        finally:
+            # Done iterating the Query. If it has its own cursor, close it.
+            if hasattr(query, "cursor") and query.cursor:
+                query.cursor.close()
+
+
+class ValuesIterable(BaseIterable):
+    """
+    Iterable returned by QuerySet.values() that yields a dict for each row.
+    """
+
+    def __iter__(self):
+        queryset = self.queryset
+        query = queryset.query
+        compiler = query.get_compiler(queryset.db)
+
+        # extra(select=...) cols are always at the start of the row.
+        names = [
+            *query.extra_select,
+            *query.values_select,
+            *query.annotation_select,
+        ]
+        indexes = range(len(names))
+        for row in compiler.results_iter(
+            chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
+        ):
+            yield {names[i]: row[i] for i in indexes}
+
+
+class ValuesListIterable(BaseIterable):
+    """
+    Iterable returned by QuerySet.values_list(flat=False) that yields a tuple
+    for each row.
+    """
+
+    def __iter__(self):
+        queryset = self.queryset
+        query = queryset.query
+        compiler = query.get_compiler(queryset.db)
+
+        if queryset._fields:
+            # extra(select=...) cols are always at the start of the row.
+            names = [
+                *query.extra_select,
+                *query.values_select,
+                *query.annotation_select,
+            ]
+            fields = [
+                *queryset._fields,
+                *(f for f in query.annotation_select if f not in queryset._fields),
+            ]
+            if fields != names:
+                # Reorder according to fields.
+                index_map = {name: idx for idx, name in enumerate(names)}
+                rowfactory = operator.itemgetter(*[index_map[f] for f in fields])
+                return map(
+                    rowfactory,
+                    compiler.results_iter(
+                        chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
+                    ),
+                )
+        return compiler.results_iter(
+            tuple_expected=True,
+            chunked_fetch=self.chunked_fetch,
+            chunk_size=self.chunk_size,
+        )
+
+
+class NamedValuesListIterable(ValuesListIterable):
+    """
+    Iterable returned by QuerySet.values_list(named=True) that yields a
+    namedtuple for each row.
+    """
+
+    def __iter__(self):
+        queryset = self.queryset
+        if queryset._fields:
+            names = queryset._fields
+        else:
+            query = queryset.query
+            names = [
+                *query.extra_select,
+                *query.values_select,
+                *query.annotation_select,
+            ]
+        tuple_class = create_namedtuple_class(*names)
+        new = tuple.__new__
+        for row in super().__iter__():
+            yield new(tuple_class, row)
+
+
+class FlatValuesListIterable(BaseIterable):
+    """
+    Iterable returned by QuerySet.values_list(flat=True) that yields single
+    values.
+    """
+
+    def __iter__(self):
+        queryset = self.queryset
+        compiler = queryset.query.get_compiler(queryset.db)
+        for row in compiler.results_iter(
+            chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
+        ):
+            yield row[0]
+
+
+class QuerySet(AltersData):
+    """Represent a lazy database lookup for a set of objects."""
+
+    def __init__(self, model=None, query=None, using=None, hints=None):
+        self.model = model
+        self._db = using
+        self._hints = hints or {}
+        self._query = query or sql.Query(self.model)
+        self._result_cache = None
+        self._sticky_filter = False
+        self._for_write = False
+        self._prefetch_related_lookups = ()
+        self._prefetch_done = False
+        self._known_related_objects = {}  # {rel_field: {pk: rel_obj}}
+        self._iterable_class = ModelIterable
+        self._fields = None
+        self._defer_next_filter = False
+        self._deferred_filter = None
+
+    @property
+    def query(self):
+        if self._deferred_filter:
+            negate, args, kwargs = self._deferred_filter
+            self._filter_or_exclude_inplace(negate, args, kwargs)
+            self._deferred_filter = None
+        return self._query
+
+    @query.setter
+    def query(self, value):
+        if value.values_select:
+            self._iterable_class = ValuesIterable
+        self._query = value
+
+    def as_manager(cls):
+        # Address the circular dependency between `Queryset` and `Manager`.
+        from django.db.models.manager import Manager
+
+        manager = Manager.from_queryset(cls)()
+        manager._built_with_as_manager = True
+        return manager
+
+    as_manager.queryset_only = True
+    as_manager = classmethod(as_manager)
+
+    ########################
+    # PYTHON MAGIC METHODS #
+    ########################
+
+    def __deepcopy__(self, memo):
+        """Don't populate the QuerySet's cache."""
+        obj = self.__class__()
+        for k, v in self.__dict__.items():
+            if k == "_result_cache":
+                obj.__dict__[k] = None
+            else:
+                obj.__dict__[k] = copy.deepcopy(v, memo)
+        return obj
+
+    def __getstate__(self):
+        # Force the cache to be fully populated.
+        self._fetch_all()
+        return {**self.__dict__, DJANGO_VERSION_PICKLE_KEY: django.__version__}
+
+    def __setstate__(self, state):
+        pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY)
+        if pickled_version:
+            if pickled_version != django.__version__:
+                warnings.warn(
+                    "Pickled queryset instance's Django version %s does not "
+                    "match the current version %s."
+                    % (pickled_version, django.__version__),
+                    RuntimeWarning,
+                    stacklevel=2,
+                )
+        else:
+            warnings.warn(
+                "Pickled queryset instance's Django version is not specified.",
+                RuntimeWarning,
+                stacklevel=2,
+            )
+        self.__dict__.update(state)
+
+    def __repr__(self):
+        data = list(self[: REPR_OUTPUT_SIZE + 1])
+        if len(data) > REPR_OUTPUT_SIZE:
+            data[-1] = "...(remaining elements truncated)..."
+        return "<%s %r>" % (self.__class__.__name__, data)
+
+    def __len__(self):
+        self._fetch_all()
+        return len(self._result_cache)
+
+    def __iter__(self):
+        """
+        The queryset iterator protocol uses three nested iterators in the
+        default case:
+            1. sql.compiler.execute_sql()
+               - Returns 100 rows at time (constants.GET_ITERATOR_CHUNK_SIZE)
+                 using cursor.fetchmany(). This part is responsible for
+                 doing some column masking, and returning the rows in chunks.
+            2. sql.compiler.results_iter()
+               - Returns one row at time. At this point the rows are still just
+                 tuples. In some cases the return values are converted to
+                 Python values at this location.
+            3. self.iterator()
+               - Responsible for turning the rows into model objects.
+        """
+        self._fetch_all()
+        return iter(self._result_cache)
+
+    def __aiter__(self):
+        # Remember, __aiter__ itself is synchronous, it's the thing it returns
+        # that is async!
+        async def generator():
+            await sync_to_async(self._fetch_all)()
+            for item in self._result_cache:
+                yield item
+
+        return generator()
+
+    def __bool__(self):
+        self._fetch_all()
+        return bool(self._result_cache)
+
+    def __getitem__(self, k):
+        """Retrieve an item or slice from the set of results."""
+        if not isinstance(k, (int, slice)):
+            raise TypeError(
+                "QuerySet indices must be integers or slices, not %s."
+                % type(k).__name__
+            )
+        if (isinstance(k, int) and k < 0) or (
+            isinstance(k, slice)
+            and (
+                (k.start is not None and k.start < 0)
+                or (k.stop is not None and k.stop < 0)
+            )
+        ):
+            raise ValueError("Negative indexing is not supported.")
+
+        if self._result_cache is not None:
+            return self._result_cache[k]
+
+        if isinstance(k, slice):
+            qs = self._chain()
+            if k.start is not None:
+                start = int(k.start)
+            else:
+                start = None
+            if k.stop is not None:
+                stop = int(k.stop)
+            else:
+                stop = None
+            qs.query.set_limits(start, stop)
+            return list(qs)[:: k.step] if k.step else qs
+
+        qs = self._chain()
+        qs.query.set_limits(k, k + 1)
+        qs._fetch_all()
+        return qs._result_cache[0]
+
+    def __class_getitem__(cls, *args, **kwargs):
+        return cls
+
+    def __and__(self, other):
+        self._check_operator_queryset(other, "&")
+        self._merge_sanity_check(other)
+        if isinstance(other, EmptyQuerySet):
+            return other
+        if isinstance(self, EmptyQuerySet):
+            return self
+        combined = self._chain()
+        combined._merge_known_related_objects(other)
+        combined.query.combine(other.query, sql.AND)
+        return combined
+
+    def __or__(self, other):
+        self._check_operator_queryset(other, "|")
+        self._merge_sanity_check(other)
+        if isinstance(self, EmptyQuerySet):
+            return other
+        if isinstance(other, EmptyQuerySet):
+            return self
+        query = (
+            self
+            if self.query.can_filter()
+            else self.model._base_manager.filter(pk__in=self.values("pk"))
+        )
+        combined = query._chain()
+        combined._merge_known_related_objects(other)
+        if not other.query.can_filter():
+            other = other.model._base_manager.filter(pk__in=other.values("pk"))
+        combined.query.combine(other.query, sql.OR)
+        return combined
+
+    def __xor__(self, other):
+        self._check_operator_queryset(other, "^")
+        self._merge_sanity_check(other)
+        if isinstance(self, EmptyQuerySet):
+            return other
+        if isinstance(other, EmptyQuerySet):
+            return self
+        query = (
+            self
+            if self.query.can_filter()
+            else self.model._base_manager.filter(pk__in=self.values("pk"))
+        )
+        combined = query._chain()
+        combined._merge_known_related_objects(other)
+        if not other.query.can_filter():
+            other = other.model._base_manager.filter(pk__in=other.values("pk"))
+        combined.query.combine(other.query, sql.XOR)
+        return combined
+
+    ####################################
+    # METHODS THAT DO DATABASE QUERIES #
+    ####################################
+
+    def _iterator(self, use_chunked_fetch, chunk_size):
+        iterable = self._iterable_class(
+            self,
+            chunked_fetch=use_chunked_fetch,
+            chunk_size=chunk_size or 2000,
+        )
+        if not self._prefetch_related_lookups or chunk_size is None:
+            yield from iterable
+            return
+
+        iterator = iter(iterable)
+        while results := list(islice(iterator, chunk_size)):
+            prefetch_related_objects(results, *self._prefetch_related_lookups)
+            yield from results
+
+    def iterator(self, chunk_size=None):
+        """
+        An iterator over the results from applying this QuerySet to the
+        database. chunk_size must be provided for QuerySets that prefetch
+        related objects. Otherwise, a default chunk_size of 2000 is supplied.
+        """
+        if chunk_size is None:
+            if self._prefetch_related_lookups:
+                # When the deprecation ends, replace with:
+                # raise ValueError(
+                #     'chunk_size must be provided when using '
+                #     'QuerySet.iterator() after prefetch_related().'
+                # )
+                warnings.warn(
+                    "Using QuerySet.iterator() after prefetch_related() "
+                    "without specifying chunk_size is deprecated.",
+                    category=RemovedInDjango50Warning,
+                    stacklevel=2,
+                )
+        elif chunk_size <= 0:
+            raise ValueError("Chunk size must be strictly positive.")
+        use_chunked_fetch = not connections[self.db].settings_dict.get(
+            "DISABLE_SERVER_SIDE_CURSORS"
+        )
+        return self._iterator(use_chunked_fetch, chunk_size)
+
+    async def aiterator(self, chunk_size=2000):
+        """
+        An asynchronous iterator over the results from applying this QuerySet
+        to the database.
+        """
+        if self._prefetch_related_lookups:
+            raise NotSupportedError(
+                "Using QuerySet.aiterator() after prefetch_related() is not supported."
+            )
+        if chunk_size <= 0:
+            raise ValueError("Chunk size must be strictly positive.")
+        use_chunked_fetch = not connections[self.db].settings_dict.get(
+            "DISABLE_SERVER_SIDE_CURSORS"
+        )
+        async for item in self._iterable_class(
+            self, chunked_fetch=use_chunked_fetch, chunk_size=chunk_size
+        ):
+            yield item
+
+    def aggregate(self, *args, **kwargs):
+        """
+        Return a dictionary containing the calculations (aggregation)
+        over the current queryset.
+
+        If args is present the expression is passed as a kwarg using
+        the Aggregate object's default alias.
+        """
+        if self.query.distinct_fields:
+            raise NotImplementedError("aggregate() + distinct(fields) not implemented.")
+        self._validate_values_are_expressions(
+            (*args, *kwargs.values()), method_name="aggregate"
+        )
+        for arg in args:
+            # The default_alias property raises TypeError if default_alias
+            # can't be set automatically or AttributeError if it isn't an
+            # attribute.
+            try:
+                arg.default_alias
+            except (AttributeError, TypeError):
+                raise TypeError("Complex aggregates require an alias")
+            kwargs[arg.default_alias] = arg
+
+        return self.query.chain().get_aggregation(self.db, kwargs)
+
+    async def aaggregate(self, *args, **kwargs):
+        return await sync_to_async(self.aggregate)(*args, **kwargs)
+
+    def count(self):
+        """
+        Perform a SELECT COUNT() and return the number of records as an
+        integer.
+
+        If the QuerySet is already fully cached, return the length of the
+        cached results set to avoid multiple SELECT COUNT(*) calls.
+        """
+        if self._result_cache is not None:
+            return len(self._result_cache)
+
+        return self.query.get_count(using=self.db)
+
+    async def acount(self):
+        return await sync_to_async(self.count)()
+
+    def get(self, *args, **kwargs):
+        """
+        Perform the query and return a single object matching the given
+        keyword arguments.
+        """
+        if self.query.combinator and (args or kwargs):
+            raise NotSupportedError(
+                "Calling QuerySet.get(...) with filters after %s() is not "
+                "supported." % self.query.combinator
+            )
+        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
+        if self.query.can_filter() and not self.query.distinct_fields:
+            clone = clone.order_by()
+        limit = None
+        if (
+            not clone.query.select_for_update
+            or connections[clone.db].features.supports_select_for_update_with_limit
+        ):
+            limit = MAX_GET_RESULTS
+            clone.query.set_limits(high=limit)
+        num = len(clone)
+        if num == 1:
+            return clone._result_cache[0]
+        if not num:
+            raise self.model.DoesNotExist(
+                "%s matching query does not exist." % self.model._meta.object_name
+            )
+        raise self.model.MultipleObjectsReturned(
+            "get() returned more than one %s -- it returned %s!"
+            % (
+                self.model._meta.object_name,
+                num if not limit or num < limit else "more than %s" % (limit - 1),
+            )
+        )
+
+    async def aget(self, *args, **kwargs):
+        return await sync_to_async(self.get)(*args, **kwargs)
+
+    def create(self, **kwargs):
+        """
+        Create a new object with the given kwargs, saving it to the database
+        and returning the created object.
+        """
+        obj = self.model(**kwargs)
+        self._for_write = True
+        obj.save(force_insert=True, using=self.db)
+        return obj
+
+    async def acreate(self, **kwargs):
+        return await sync_to_async(self.create)(**kwargs)
+
+    def _prepare_for_bulk_create(self, objs):
+        for obj in objs:
+            if obj.pk is None:
+                # Populate new PK values.
+                obj.pk = obj._meta.pk.get_pk_value_on_save(obj)
+            obj._prepare_related_fields_for_save(operation_name="bulk_create")
+
+    def _check_bulk_create_options(
+        self, ignore_conflicts, update_conflicts, update_fields, unique_fields
+    ):
+        if ignore_conflicts and update_conflicts:
+            raise ValueError(
+                "ignore_conflicts and update_conflicts are mutually exclusive."
+            )
+        db_features = connections[self.db].features
+        if ignore_conflicts:
+            if not db_features.supports_ignore_conflicts:
+                raise NotSupportedError(
+                    "This database backend does not support ignoring conflicts."
+                )
+            return OnConflict.IGNORE
+        elif update_conflicts:
+            if not db_features.supports_update_conflicts:
+                raise NotSupportedError(
+                    "This database backend does not support updating conflicts."
+                )
+            if not update_fields:
+                raise ValueError(
+                    "Fields that will be updated when a row insertion fails "
+                    "on conflicts must be provided."
+                )
+            if unique_fields and not db_features.supports_update_conflicts_with_target:
+                raise NotSupportedError(
+                    "This database backend does not support updating "
+                    "conflicts with specifying unique fields that can trigger "
+                    "the upsert."
+                )
+            if not unique_fields and db_features.supports_update_conflicts_with_target:
+                raise ValueError(
+                    "Unique fields that can trigger the upsert must be provided."
+                )
+            # Updating primary keys and non-concrete fields is forbidden.
+            if any(not f.concrete or f.many_to_many for f in update_fields):
+                raise ValueError(
+                    "bulk_create() can only be used with concrete fields in "
+                    "update_fields."
+                )
+            if any(f.primary_key for f in update_fields):
+                raise ValueError(
+                    "bulk_create() cannot be used with primary keys in "
+                    "update_fields."
+                )
+            if unique_fields:
+                if any(not f.concrete or f.many_to_many for f in unique_fields):
+                    raise ValueError(
+                        "bulk_create() can only be used with concrete fields "
+                        "in unique_fields."
+                    )
+            return OnConflict.UPDATE
+        return None
+
+    def bulk_create(
+        self,
+        objs,
+        batch_size=None,
+        ignore_conflicts=False,
+        update_conflicts=False,
+        update_fields=None,
+        unique_fields=None,
+    ):
+        """
+        Insert each of the instances into the database. Do *not* call
+        save() on each of the instances, do not send any pre/post_save
+        signals, and do not set the primary key attribute if it is an
+        autoincrement field (except if features.can_return_rows_from_bulk_insert=True).
+        Multi-table models are not supported.
+        """
+        # When you bulk insert you don't get the primary keys back (if it's an
+        # autoincrement, except if can_return_rows_from_bulk_insert=True), so
+        # you can't insert into the child tables which references this. There
+        # are two workarounds:
+        # 1) This could be implemented if you didn't have an autoincrement pk
+        # 2) You could do it by doing O(n) normal inserts into the parent
+        #    tables to get the primary keys back and then doing a single bulk
+        #    insert into the childmost table.
+        # We currently set the primary keys on the objects when using
+        # PostgreSQL via the RETURNING ID clause. It should be possible for
+        # Oracle as well, but the semantics for extracting the primary keys is
+        # trickier so it's not done yet.
+        if batch_size is not None and batch_size <= 0:
+            raise ValueError("Batch size must be a positive integer.")
+        # Check that the parents share the same concrete model with the our
+        # model to detect the inheritance pattern ConcreteGrandParent ->
+        # MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy
+        # would not identify that case as involving multiple tables.
+        for parent in self.model._meta.get_parent_list():
+            if parent._meta.concrete_model is not self.model._meta.concrete_model:
+                raise ValueError("Can't bulk create a multi-table inherited model")
+        if not objs:
+            return objs
+        opts = self.model._meta
+        if unique_fields:
+            # Primary key is allowed in unique_fields.
+            unique_fields = [
+                self.model._meta.get_field(opts.pk.name if name == "pk" else name)
+                for name in unique_fields
+            ]
+        if update_fields:
+            update_fields = [self.model._meta.get_field(name) for name in update_fields]
+        on_conflict = self._check_bulk_create_options(
+            ignore_conflicts,
+            update_conflicts,
+            update_fields,
+            unique_fields,
+        )
+        self._for_write = True
+        fields = opts.concrete_fields
+        objs = list(objs)
+        self._prepare_for_bulk_create(objs)
+        with transaction.atomic(using=self.db, savepoint=False):
+            objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs)
+            if objs_with_pk:
+                returned_columns = self._batched_insert(
+                    objs_with_pk,
+                    fields,
+                    batch_size,
+                    on_conflict=on_conflict,
+                    update_fields=update_fields,
+                    unique_fields=unique_fields,
+                )
+                for obj_with_pk, results in zip(objs_with_pk, returned_columns):
+                    for result, field in zip(results, opts.db_returning_fields):
+                        if field != opts.pk:
+                            setattr(obj_with_pk, field.attname, result)
+                for obj_with_pk in objs_with_pk:
+                    obj_with_pk._state.adding = False
+                    obj_with_pk._state.db = self.db
+            if objs_without_pk:
+                fields = [f for f in fields if not isinstance(f, AutoField)]
+                returned_columns = self._batched_insert(
+                    objs_without_pk,
+                    fields,
+                    batch_size,
+                    on_conflict=on_conflict,
+                    update_fields=update_fields,
+                    unique_fields=unique_fields,
+                )
+                connection = connections[self.db]
+                if (
+                    connection.features.can_return_rows_from_bulk_insert
+                    and on_conflict is None
+                ):
+                    assert len(returned_columns) == len(objs_without_pk)
+                for obj_without_pk, results in zip(objs_without_pk, returned_columns):
+                    for result, field in zip(results, opts.db_returning_fields):
+                        setattr(obj_without_pk, field.attname, result)
+                    obj_without_pk._state.adding = False
+                    obj_without_pk._state.db = self.db
+
+        return objs
+
+    async def abulk_create(
+        self,
+        objs,
+        batch_size=None,
+        ignore_conflicts=False,
+        update_conflicts=False,
+        update_fields=None,
+        unique_fields=None,
+    ):
+        return await sync_to_async(self.bulk_create)(
+            objs=objs,
+            batch_size=batch_size,
+            ignore_conflicts=ignore_conflicts,
+            update_conflicts=update_conflicts,
+            update_fields=update_fields,
+            unique_fields=unique_fields,
+        )
+
+    def bulk_update(self, objs, fields, batch_size=None):
+        """
+        Update the given fields in each of the given objects in the database.
+        """
+        if batch_size is not None and batch_size <= 0:
+            raise ValueError("Batch size must be a positive integer.")
+        if not fields:
+            raise ValueError("Field names must be given to bulk_update().")
+        objs = tuple(objs)
+        if any(obj.pk is None for obj in objs):
+            raise ValueError("All bulk_update() objects must have a primary key set.")
+        fields = [self.model._meta.get_field(name) for name in fields]
+        if any(not f.concrete or f.many_to_many for f in fields):
+            raise ValueError("bulk_update() can only be used with concrete fields.")
+        if any(f.primary_key for f in fields):
+            raise ValueError("bulk_update() cannot be used with primary key fields.")
+        if not objs:
+            return 0
+        for obj in objs:
+            obj._prepare_related_fields_for_save(
+                operation_name="bulk_update", fields=fields
+            )
+        # PK is used twice in the resulting update query, once in the filter
+        # and once in the WHEN. Each field will also have one CAST.
+        self._for_write = True
+        connection = connections[self.db]
+        max_batch_size = connection.ops.bulk_batch_size(["pk", "pk"] + fields, objs)
+        batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
+        requires_casting = connection.features.requires_casted_case_in_updates
+        batches = (objs[i : i + batch_size] for i in range(0, len(objs), batch_size))
+        updates = []
+        for batch_objs in batches:
+            update_kwargs = {}
+            for field in fields:
+                when_statements = []
+                for obj in batch_objs:
+                    attr = getattr(obj, field.attname)
+                    if not hasattr(attr, "resolve_expression"):
+                        attr = Value(attr, output_field=field)
+                    when_statements.append(When(pk=obj.pk, then=attr))
+                case_statement = Case(*when_statements, output_field=field)
+                if requires_casting:
+                    case_statement = Cast(case_statement, output_field=field)
+                update_kwargs[field.attname] = case_statement
+            updates.append(([obj.pk for obj in batch_objs], update_kwargs))
+        rows_updated = 0
+        queryset = self.using(self.db)
+        with transaction.atomic(using=self.db, savepoint=False):
+            for pks, update_kwargs in updates:
+                rows_updated += queryset.filter(pk__in=pks).update(**update_kwargs)
+        return rows_updated
+
+    bulk_update.alters_data = True
+
+    async def abulk_update(self, objs, fields, batch_size=None):
+        return await sync_to_async(self.bulk_update)(
+            objs=objs,
+            fields=fields,
+            batch_size=batch_size,
+        )
+
+    abulk_update.alters_data = True
+
+    def get_or_create(self, defaults=None, **kwargs):
+        """
+        Look up an object with the given kwargs, creating one if necessary.
+        Return a tuple of (object, created), where created is a boolean
+        specifying whether an object was created.
+        """
+        # The get() needs to be targeted at the write database in order
+        # to avoid potential transaction consistency problems.
+        self._for_write = True
+        try:
+            return self.get(**kwargs), False
+        except self.model.DoesNotExist:
+            params = self._extract_model_params(defaults, **kwargs)
+            # Try to create an object using passed params.
+            try:
+                with transaction.atomic(using=self.db):
+                    params = dict(resolve_callables(params))
+                    return self.create(**params), True
+            except IntegrityError:
+                try:
+                    return self.get(**kwargs), False
+                except self.model.DoesNotExist:
+                    pass
+                raise
+
+    async def aget_or_create(self, defaults=None, **kwargs):
+        return await sync_to_async(self.get_or_create)(
+            defaults=defaults,
+            **kwargs,
+        )
+
+    def update_or_create(self, defaults=None, **kwargs):
+        """
+        Look up an object with the given kwargs, updating one with defaults
+        if it exists, otherwise create a new one.
+        Return a tuple (object, created), where created is a boolean
+        specifying whether an object was created.
+        """
+        defaults = defaults or {}
+        self._for_write = True
+        with transaction.atomic(using=self.db):
+            # Lock the row so that a concurrent update is blocked until
+            # update_or_create() has performed its save.
+            obj, created = self.select_for_update().get_or_create(defaults, **kwargs)
+            if created:
+                return obj, created
+            for k, v in resolve_callables(defaults):
+                setattr(obj, k, v)
+
+            update_fields = set(defaults)
+            concrete_field_names = self.model._meta._non_pk_concrete_field_names
+            # update_fields does not support non-concrete fields.
+            if concrete_field_names.issuperset(update_fields):
+                # Add fields which are set on pre_save(), e.g. auto_now fields.
+                # This is to maintain backward compatibility as these fields
+                # are not updated unless explicitly specified in the
+                # update_fields list.
+                for field in self.model._meta.local_concrete_fields:
+                    if not (
+                        field.primary_key or field.__class__.pre_save is Field.pre_save
+                    ):
+                        update_fields.add(field.name)
+                        if field.name != field.attname:
+                            update_fields.add(field.attname)
+                obj.save(using=self.db, update_fields=update_fields)
+            else:
+                obj.save(using=self.db)
+        return obj, False
+
+    async def aupdate_or_create(self, defaults=None, **kwargs):
+        return await sync_to_async(self.update_or_create)(
+            defaults=defaults,
+            **kwargs,
+        )
+
+    def _extract_model_params(self, defaults, **kwargs):
+        """
+        Prepare `params` for creating a model instance based on the given
+        kwargs; for use by get_or_create().
+        """
+        defaults = defaults or {}
+        params = {k: v for k, v in kwargs.items() if LOOKUP_SEP not in k}
+        params.update(defaults)
+        property_names = self.model._meta._property_names
+        invalid_params = []
+        for param in params:
+            try:
+                self.model._meta.get_field(param)
+            except exceptions.FieldDoesNotExist:
+                # It's okay to use a model's property if it has a setter.
+                if not (param in property_names and getattr(self.model, param).fset):
+                    invalid_params.append(param)
+        if invalid_params:
+            raise exceptions.FieldError(
+                "Invalid field name(s) for model %s: '%s'."
+                % (
+                    self.model._meta.object_name,
+                    "', '".join(sorted(invalid_params)),
+                )
+            )
+        return params
+
+    def _earliest(self, *fields):
+        """
+        Return the earliest object according to fields (if given) or by the
+        model's Meta.get_latest_by.
+        """
+        if fields:
+            order_by = fields
+        else:
+            order_by = getattr(self.model._meta, "get_latest_by")
+            if order_by and not isinstance(order_by, (tuple, list)):
+                order_by = (order_by,)
+        if order_by is None:
+            raise ValueError(
+                "earliest() and latest() require either fields as positional "
+                "arguments or 'get_latest_by' in the model's Meta."
+            )
+        obj = self._chain()
+        obj.query.set_limits(high=1)
+        obj.query.clear_ordering(force=True)
+        obj.query.add_ordering(*order_by)
+        return obj.get()
+
+    def earliest(self, *fields):
+        if self.query.is_sliced:
+            raise TypeError("Cannot change a query once a slice has been taken.")
+        return self._earliest(*fields)
+
+    async def aearliest(self, *fields):
+        return await sync_to_async(self.earliest)(*fields)
+
+    def latest(self, *fields):
+        """
+        Return the latest object according to fields (if given) or by the
+        model's Meta.get_latest_by.
+        """
+        if self.query.is_sliced:
+            raise TypeError("Cannot change a query once a slice has been taken.")
+        return self.reverse()._earliest(*fields)
+
+    async def alatest(self, *fields):
+        return await sync_to_async(self.latest)(*fields)
+
+    def first(self):
+        """Return the first object of a query or None if no match is found."""
+        if self.ordered:
+            queryset = self
+        else:
+            self._check_ordering_first_last_queryset_aggregation(method="first")
+            queryset = self.order_by("pk")
+        for obj in queryset[:1]:
+            return obj
+
+    async def afirst(self):
+        return await sync_to_async(self.first)()
+
+    def last(self):
+        """Return the last object of a query or None if no match is found."""
+        if self.ordered:
+            queryset = self.reverse()
+        else:
+            self._check_ordering_first_last_queryset_aggregation(method="last")
+            queryset = self.order_by("-pk")
+        for obj in queryset[:1]:
+            return obj
+
+    async def alast(self):
+        return await sync_to_async(self.last)()
+
+    def in_bulk(self, id_list=None, *, field_name="pk"):
+        """
+        Return a dictionary mapping each of the given IDs to the object with
+        that ID. If `id_list` isn't provided, evaluate the entire QuerySet.
+        """
+        if self.query.is_sliced:
+            raise TypeError("Cannot use 'limit' or 'offset' with in_bulk().")
+        opts = self.model._meta
+        unique_fields = [
+            constraint.fields[0]
+            for constraint in opts.total_unique_constraints
+            if len(constraint.fields) == 1
+        ]
+        if (
+            field_name != "pk"
+            and not opts.get_field(field_name).unique
+            and field_name not in unique_fields
+            and self.query.distinct_fields != (field_name,)
+        ):
+            raise ValueError(
+                "in_bulk()'s field_name must be a unique field but %r isn't."
+                % field_name
+            )
+        if id_list is not None:
+            if not id_list:
+                return {}
+            filter_key = "{}__in".format(field_name)
+            batch_size = connections[self.db].features.max_query_params
+            id_list = tuple(id_list)
+            # If the database has a limit on the number of query parameters
+            # (e.g. SQLite), retrieve objects in batches if necessary.
+            if batch_size and batch_size < len(id_list):
+                qs = ()
+                for offset in range(0, len(id_list), batch_size):
+                    batch = id_list[offset : offset + batch_size]
+                    qs += tuple(self.filter(**{filter_key: batch}).order_by())
+            else:
+                qs = self.filter(**{filter_key: id_list}).order_by()
+        else:
+            qs = self._chain()
+        return {getattr(obj, field_name): obj for obj in qs}
+
+    async def ain_bulk(self, id_list=None, *, field_name="pk"):
+        return await sync_to_async(self.in_bulk)(
+            id_list=id_list,
+            field_name=field_name,
+        )
+
+    def delete(self):
+        """Delete the records in the current QuerySet."""
+        self._not_support_combined_queries("delete")
+        if self.query.is_sliced:
+            raise TypeError("Cannot use 'limit' or 'offset' with delete().")
+        if self.query.distinct or self.query.distinct_fields:
+            raise TypeError("Cannot call delete() after .distinct().")
+        if self._fields is not None:
+            raise TypeError("Cannot call delete() after .values() or .values_list()")
+
+        del_query = self._chain()
+
+        # The delete is actually 2 queries - one to find related objects,
+        # and one to delete. Make sure that the discovery of related
+        # objects is performed on the same database as the deletion.
+        del_query._for_write = True
+
+        # Disable non-supported fields.
+        del_query.query.select_for_update = False
+        del_query.query.select_related = False
+        del_query.query.clear_ordering(force=True)
+
+        collector = Collector(using=del_query.db, origin=self)
+        collector.collect(del_query)
+        deleted, _rows_count = collector.delete()
+
+        # Clear the result cache, in case this QuerySet gets reused.
+        self._result_cache = None
+        return deleted, _rows_count
+
+    delete.alters_data = True
+    delete.queryset_only = True
+
+    async def adelete(self):
+        return await sync_to_async(self.delete)()
+
+    adelete.alters_data = True
+    adelete.queryset_only = True
+
+    def _raw_delete(self, using):
+        """
+        Delete objects found from the given queryset in single direct SQL
+        query. No signals are sent and there is no protection for cascades.
+        """
+        query = self.query.clone()
+        query.__class__ = sql.DeleteQuery
+        cursor = query.get_compiler(using).execute_sql(CURSOR)
+        if cursor:
+            with cursor:
+                return cursor.rowcount
+        return 0
+
+    _raw_delete.alters_data = True
+
+    def update(self, **kwargs):
+        """
+        Update all elements in the current QuerySet, setting all the given
+        fields to the appropriate values.
+        """
+        self._not_support_combined_queries("update")
+        if self.query.is_sliced:
+            raise TypeError("Cannot update a query once a slice has been taken.")
+        self._for_write = True
+        query = self.query.chain(sql.UpdateQuery)
+        query.add_update_values(kwargs)
+
+        # Inline annotations in order_by(), if possible.
+        new_order_by = []
+        for col in query.order_by:
+            if annotation := query.annotations.get(col):
+                if getattr(annotation, "contains_aggregate", False):
+                    raise exceptions.FieldError(
+                        f"Cannot update when ordering by an aggregate: {annotation}"
+                    )
+                new_order_by.append(annotation)
+            else:
+                new_order_by.append(col)
+        query.order_by = tuple(new_order_by)
+
+        # Clear any annotations so that they won't be present in subqueries.
+        query.annotations = {}
+        with transaction.mark_for_rollback_on_error(using=self.db):
+            rows = query.get_compiler(self.db).execute_sql(CURSOR)
+        self._result_cache = None
+        return rows
+
+    update.alters_data = True
+
+    async def aupdate(self, **kwargs):
+        return await sync_to_async(self.update)(**kwargs)
+
+    aupdate.alters_data = True
+
+    def _update(self, values):
+        """
+        A version of update() that accepts field objects instead of field names.
+        Used primarily for model saving and not intended for use by general
+        code (it requires too much poking around at model internals to be
+        useful at that level).
+        """
+        if self.query.is_sliced:
+            raise TypeError("Cannot update a query once a slice has been taken.")
+        query = self.query.chain(sql.UpdateQuery)
+        query.add_update_fields(values)
+        # Clear any annotations so that they won't be present in subqueries.
+        query.annotations = {}
+        self._result_cache = None
+        return query.get_compiler(self.db).execute_sql(CURSOR)
+
+    _update.alters_data = True
+    _update.queryset_only = False
+
+    def exists(self):
+        """
+        Return True if the QuerySet would have any results, False otherwise.
+        """
+        if self._result_cache is None:
+            return self.query.has_results(using=self.db)
+        return bool(self._result_cache)
+
+    async def aexists(self):
+        return await sync_to_async(self.exists)()
+
+    def contains(self, obj):
+        """
+        Return True if the QuerySet contains the provided obj,
+        False otherwise.
+        """
+        self._not_support_combined_queries("contains")
+        if self._fields is not None:
+            raise TypeError(
+                "Cannot call QuerySet.contains() after .values() or .values_list()."
+            )
+        try:
+            if obj._meta.concrete_model != self.model._meta.concrete_model:
+                return False
+        except AttributeError:
+            raise TypeError("'obj' must be a model instance.")
+        if obj.pk is None:
+            raise ValueError("QuerySet.contains() cannot be used on unsaved objects.")
+        if self._result_cache is not None:
+            return obj in self._result_cache
+        return self.filter(pk=obj.pk).exists()
+
+    async def acontains(self, obj):
+        return await sync_to_async(self.contains)(obj=obj)
+
+    def _prefetch_related_objects(self):
+        # This method can only be called once the result cache has been filled.
+        prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
+        self._prefetch_done = True
+
+    def explain(self, *, format=None, **options):
+        """
+        Runs an EXPLAIN on the SQL query this QuerySet would perform, and
+        returns the results.
+        """
+        return self.query.explain(using=self.db, format=format, **options)
+
+    async def aexplain(self, *, format=None, **options):
+        return await sync_to_async(self.explain)(format=format, **options)
+
+    ##################################################
+    # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
+    ##################################################
+
+    def raw(self, raw_query, params=(), translations=None, using=None):
+        if using is None:
+            using = self.db
+        qs = RawQuerySet(
+            raw_query,
+            model=self.model,
+            params=params,
+            translations=translations,
+            using=using,
+        )
+        qs._prefetch_related_lookups = self._prefetch_related_lookups[:]
+        return qs
+
+    def _values(self, *fields, **expressions):
+        clone = self._chain()
+        if expressions:
+            clone = clone.annotate(**expressions)
+        clone._fields = fields
+        clone.query.set_values(fields)
+        return clone
+
+    def values(self, *fields, **expressions):
+        fields += tuple(expressions)
+        clone = self._values(*fields, **expressions)
+        clone._iterable_class = ValuesIterable
+        return clone
+
+    def values_list(self, *fields, flat=False, named=False):
+        if flat and named:
+            raise TypeError("'flat' and 'named' can't be used together.")
+        if flat and len(fields) > 1:
+            raise TypeError(
+                "'flat' is not valid when values_list is called with more than one "
+                "field."
+            )
+
+        field_names = {f for f in fields if not hasattr(f, "resolve_expression")}
+        _fields = []
+        expressions = {}
+        counter = 1
+        for field in fields:
+            if hasattr(field, "resolve_expression"):
+                field_id_prefix = getattr(
+                    field, "default_alias", field.__class__.__name__.lower()
+                )
+                while True:
+                    field_id = field_id_prefix + str(counter)
+                    counter += 1
+                    if field_id not in field_names:
+                        break
+                expressions[field_id] = field
+                _fields.append(field_id)
+            else:
+                _fields.append(field)
+
+        clone = self._values(*_fields, **expressions)
+        clone._iterable_class = (
+            NamedValuesListIterable
+            if named
+            else FlatValuesListIterable
+            if flat
+            else ValuesListIterable
+        )
+        return clone
+
+    def dates(self, field_name, kind, order="ASC"):
+        """
+        Return a list of date objects representing all available dates for
+        the given field_name, scoped to 'kind'.
+        """
+        if kind not in ("year", "month", "week", "day"):
+            raise ValueError("'kind' must be one of 'year', 'month', 'week', or 'day'.")
+        if order not in ("ASC", "DESC"):
+            raise ValueError("'order' must be either 'ASC' or 'DESC'.")
+        return (
+            self.annotate(
+                datefield=Trunc(field_name, kind, output_field=DateField()),
+                plain_field=F(field_name),
+            )
+            .values_list("datefield", flat=True)
+            .distinct()
+            .filter(plain_field__isnull=False)
+            .order_by(("-" if order == "DESC" else "") + "datefield")
+        )
+
+    # RemovedInDjango50Warning: when the deprecation ends, remove is_dst
+    # argument.
+    def datetimes(
+        self, field_name, kind, order="ASC", tzinfo=None, is_dst=timezone.NOT_PASSED
+    ):
+        """
+        Return a list of datetime objects representing all available
+        datetimes for the given field_name, scoped to 'kind'.
+        """
+        if kind not in ("year", "month", "week", "day", "hour", "minute", "second"):
+            raise ValueError(
+                "'kind' must be one of 'year', 'month', 'week', 'day', "
+                "'hour', 'minute', or 'second'."
+            )
+        if order not in ("ASC", "DESC"):
+            raise ValueError("'order' must be either 'ASC' or 'DESC'.")
+        if settings.USE_TZ:
+            if tzinfo is None:
+                tzinfo = timezone.get_current_timezone()
+        else:
+            tzinfo = None
+        return (
+            self.annotate(
+                datetimefield=Trunc(
+                    field_name,
+                    kind,
+                    output_field=DateTimeField(),
+                    tzinfo=tzinfo,
+                    is_dst=is_dst,
+                ),
+                plain_field=F(field_name),
+            )
+            .values_list("datetimefield", flat=True)
+            .distinct()
+            .filter(plain_field__isnull=False)
+            .order_by(("-" if order == "DESC" else "") + "datetimefield")
+        )
+
+    def none(self):
+        """Return an empty QuerySet."""
+        clone = self._chain()
+        clone.query.set_empty()
+        return clone
+
+    ##################################################################
+    # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
+    ##################################################################
+
+    def all(self):
+        """
+        Return a new QuerySet that is a copy of the current one. This allows a
+        QuerySet to proxy for a model manager in some cases.
+        """
+        return self._chain()
+
+    def filter(self, *args, **kwargs):
+        """
+        Return a new QuerySet instance with the args ANDed to the existing
+        set.
+        """
+        self._not_support_combined_queries("filter")
+        return self._filter_or_exclude(False, args, kwargs)
+
+    def exclude(self, *args, **kwargs):
+        """
+        Return a new QuerySet instance with NOT (args) ANDed to the existing
+        set.
+        """
+        self._not_support_combined_queries("exclude")
+        return self._filter_or_exclude(True, args, kwargs)
+
+    def _filter_or_exclude(self, negate, args, kwargs):
+        if (args or kwargs) and self.query.is_sliced:
+            raise TypeError("Cannot filter a query once a slice has been taken.")
+        clone = self._chain()
+        if self._defer_next_filter:
+            self._defer_next_filter = False
+            clone._deferred_filter = negate, args, kwargs
+        else:
+            clone._filter_or_exclude_inplace(negate, args, kwargs)
+        return clone
+
+    def _filter_or_exclude_inplace(self, negate, args, kwargs):
+        if negate:
+            self._query.add_q(~Q(*args, **kwargs))
+        else:
+            self._query.add_q(Q(*args, **kwargs))
+
+    def complex_filter(self, filter_obj):
+        """
+        Return a new QuerySet instance with filter_obj added to the filters.
+
+        filter_obj can be a Q object or a dictionary of keyword lookup
+        arguments.
+
+        This exists to support framework features such as 'limit_choices_to',
+        and usually it will be more natural to use other methods.
+        """
+        if isinstance(filter_obj, Q):
+            clone = self._chain()
+            clone.query.add_q(filter_obj)
+            return clone
+        else:
+            return self._filter_or_exclude(False, args=(), kwargs=filter_obj)
+
+    def _combinator_query(self, combinator, *other_qs, all=False):
+        # Clone the query to inherit the select list and everything
+        clone = self._chain()
+        # Clear limits and ordering so they can be reapplied
+        clone.query.clear_ordering(force=True)
+        clone.query.clear_limits()
+        clone.query.combined_queries = (self.query,) + tuple(
+            qs.query for qs in other_qs
+        )
+        clone.query.combinator = combinator
+        clone.query.combinator_all = all
+        return clone
+
+    def union(self, *other_qs, all=False):
+        # If the query is an EmptyQuerySet, combine all nonempty querysets.
+        if isinstance(self, EmptyQuerySet):
+            qs = [q for q in other_qs if not isinstance(q, EmptyQuerySet)]
+            if not qs:
+                return self
+            if len(qs) == 1:
+                return qs[0]
+            return qs[0]._combinator_query("union", *qs[1:], all=all)
+        return self._combinator_query("union", *other_qs, all=all)
+
+    def intersection(self, *other_qs):
+        # If any query is an EmptyQuerySet, return it.
+        if isinstance(self, EmptyQuerySet):
+            return self
+        for other in other_qs:
+            if isinstance(other, EmptyQuerySet):
+                return other
+        return self._combinator_query("intersection", *other_qs)
+
+    def difference(self, *other_qs):
+        # If the query is an EmptyQuerySet, return it.
+        if isinstance(self, EmptyQuerySet):
+            return self
+        return self._combinator_query("difference", *other_qs)
+
+    def select_for_update(self, nowait=False, skip_locked=False, of=(), no_key=False):
+        """
+        Return a new QuerySet instance that will select objects with a
+        FOR UPDATE lock.
+        """
+        if nowait and skip_locked:
+            raise ValueError("The nowait option cannot be used with skip_locked.")
+        obj = self._chain()
+        obj._for_write = True
+        obj.query.select_for_update = True
+        obj.query.select_for_update_nowait = nowait
+        obj.query.select_for_update_skip_locked = skip_locked
+        obj.query.select_for_update_of = of
+        obj.query.select_for_no_key_update = no_key
+        return obj
+
+    def select_related(self, *fields):
+        """
+        Return a new QuerySet instance that will select related objects.
+
+        If fields are specified, they must be ForeignKey fields and only those
+        related objects are included in the selection.
+
+        If select_related(None) is called, clear the list.
+        """
+        self._not_support_combined_queries("select_related")
+        if self._fields is not None:
+            raise TypeError(
+                "Cannot call select_related() after .values() or .values_list()"
+            )
+
+        obj = self._chain()
+        if fields == (None,):
+            obj.query.select_related = False
+        elif fields:
+            obj.query.add_select_related(fields)
+        else:
+            obj.query.select_related = True
+        return obj
+
+    def prefetch_related(self, *lookups):
+        """
+        Return a new QuerySet instance that will prefetch the specified
+        Many-To-One and Many-To-Many related objects when the QuerySet is
+        evaluated.
+
+        When prefetch_related() is called more than once, append to the list of
+        prefetch lookups. If prefetch_related(None) is called, clear the list.
+        """
+        self._not_support_combined_queries("prefetch_related")
+        clone = self._chain()
+        if lookups == (None,):
+            clone._prefetch_related_lookups = ()
+        else:
+            for lookup in lookups:
+                if isinstance(lookup, Prefetch):
+                    lookup = lookup.prefetch_to
+                lookup = lookup.split(LOOKUP_SEP, 1)[0]
+                if lookup in self.query._filtered_relations:
+                    raise ValueError(
+                        "prefetch_related() is not supported with FilteredRelation."
+                    )
+            clone._prefetch_related_lookups = clone._prefetch_related_lookups + lookups
+        return clone
+
+    def annotate(self, *args, **kwargs):
+        """
+        Return a query set in which the returned objects have been annotated
+        with extra data or aggregations.
+        """
+        self._not_support_combined_queries("annotate")
+        return self._annotate(args, kwargs, select=True)
+
+    def alias(self, *args, **kwargs):
+        """
+        Return a query set with added aliases for extra data or aggregations.
+        """
+        self._not_support_combined_queries("alias")
+        return self._annotate(args, kwargs, select=False)
+
+    def _annotate(self, args, kwargs, select=True):
+        self._validate_values_are_expressions(
+            args + tuple(kwargs.values()), method_name="annotate"
+        )
+        annotations = {}
+        for arg in args:
+            # The default_alias property may raise a TypeError.
+            try:
+                if arg.default_alias in kwargs:
+                    raise ValueError(
+                        "The named annotation '%s' conflicts with the "
+                        "default name for another annotation." % arg.default_alias
+                    )
+            except TypeError:
+                raise TypeError("Complex annotations require an alias")
+            annotations[arg.default_alias] = arg
+        annotations.update(kwargs)
+
+        clone = self._chain()
+        names = self._fields
+        if names is None:
+            names = set(
+                chain.from_iterable(
+                    (field.name, field.attname)
+                    if hasattr(field, "attname")
+                    else (field.name,)
+                    for field in self.model._meta.get_fields()
+                )
+            )
+
+        for alias, annotation in annotations.items():
+            if alias in names:
+                raise ValueError(
+                    "The annotation '%s' conflicts with a field on "
+                    "the model." % alias
+                )
+            if isinstance(annotation, FilteredRelation):
+                clone.query.add_filtered_relation(annotation, alias)
+            else:
+                clone.query.add_annotation(
+                    annotation,
+                    alias,
+                    select=select,
+                )
+        for alias, annotation in clone.query.annotations.items():
+            if alias in annotations and annotation.contains_aggregate:
+                if clone._fields is None:
+                    clone.query.group_by = True
+                else:
+                    clone.query.set_group_by()
+                break
+
+        return clone
+
+    def order_by(self, *field_names):
+        """Return a new QuerySet instance with the ordering changed."""
+        if self.query.is_sliced:
+            raise TypeError("Cannot reorder a query once a slice has been taken.")
+        obj = self._chain()
+        obj.query.clear_ordering(force=True, clear_default=False)
+        obj.query.add_ordering(*field_names)
+        return obj
+
+    def distinct(self, *field_names):
+        """
+        Return a new QuerySet instance that will select only distinct results.
+        """
+        self._not_support_combined_queries("distinct")
+        if self.query.is_sliced:
+            raise TypeError(
+                "Cannot create distinct fields once a slice has been taken."
+            )
+        obj = self._chain()
+        obj.query.add_distinct_fields(*field_names)
+        return obj
+
+    def extra(
+        self,
+        select=None,
+        where=None,
+        params=None,
+        tables=None,
+        order_by=None,
+        select_params=None,
+    ):
+        """Add extra SQL fragments to the query."""
+        self._not_support_combined_queries("extra")
+        if self.query.is_sliced:
+            raise TypeError("Cannot change a query once a slice has been taken.")
+        clone = self._chain()
+        clone.query.add_extra(select, select_params, where, params, tables, order_by)
+        return clone
+
+    def reverse(self):
+        """Reverse the ordering of the QuerySet."""
+        if self.query.is_sliced:
+            raise TypeError("Cannot reverse a query once a slice has been taken.")
+        clone = self._chain()
+        clone.query.standard_ordering = not clone.query.standard_ordering
+        return clone
+
+    def defer(self, *fields):
+        """
+        Defer the loading of data for certain fields until they are accessed.
+        Add the set of deferred fields to any existing set of deferred fields.
+        The only exception to this is if None is passed in as the only
+        parameter, in which case removal all deferrals.
+        """
+        self._not_support_combined_queries("defer")
+        if self._fields is not None:
+            raise TypeError("Cannot call defer() after .values() or .values_list()")
+        clone = self._chain()
+        if fields == (None,):
+            clone.query.clear_deferred_loading()
+        else:
+            clone.query.add_deferred_loading(fields)
+        return clone
+
+    def only(self, *fields):
+        """
+        Essentially, the opposite of defer(). Only the fields passed into this
+        method and that are not already specified as deferred are loaded
+        immediately when the queryset is evaluated.
+        """
+        self._not_support_combined_queries("only")
+        if self._fields is not None:
+            raise TypeError("Cannot call only() after .values() or .values_list()")
+        if fields == (None,):
+            # Can only pass None to defer(), not only(), as the rest option.
+            # That won't stop people trying to do this, so let's be explicit.
+            raise TypeError("Cannot pass None as an argument to only().")
+        for field in fields:
+            field = field.split(LOOKUP_SEP, 1)[0]
+            if field in self.query._filtered_relations:
+                raise ValueError("only() is not supported with FilteredRelation.")
+        clone = self._chain()
+        clone.query.add_immediate_loading(fields)
+        return clone
+
+    def using(self, alias):
+        """Select which database this QuerySet should execute against."""
+        clone = self._chain()
+        clone._db = alias
+        return clone
+
+    ###################################
+    # PUBLIC INTROSPECTION ATTRIBUTES #
+    ###################################
+
+    @property
+    def ordered(self):
+        """
+        Return True if the QuerySet is ordered -- i.e. has an order_by()
+        clause or a default ordering on the model (or is empty).
+        """
+        if isinstance(self, EmptyQuerySet):
+            return True
+        if self.query.extra_order_by or self.query.order_by:
+            return True
+        elif (
+            self.query.default_ordering
+            and self.query.get_meta().ordering
+            and
+            # A default ordering doesn't affect GROUP BY queries.
+            not self.query.group_by
+        ):
+            return True
+        else:
+            return False
+
+    @property
+    def db(self):
+        """Return the database used if this query is executed now."""
+        if self._for_write:
+            return self._db or router.db_for_write(self.model, **self._hints)
+        return self._db or router.db_for_read(self.model, **self._hints)
+
+    ###################
+    # PRIVATE METHODS #
+    ###################
+
+    def _insert(
+        self,
+        objs,
+        fields,
+        returning_fields=None,
+        raw=False,
+        using=None,
+        on_conflict=None,
+        update_fields=None,
+        unique_fields=None,
+    ):
+        """
+        Insert a new record for the given model. This provides an interface to
+        the InsertQuery class and is how Model.save() is implemented.
+        """
+        self._for_write = True
+        if using is None:
+            using = self.db
+        query = sql.InsertQuery(
+            self.model,
+            on_conflict=on_conflict,
+            update_fields=update_fields,
+            unique_fields=unique_fields,
+        )
+        query.insert_values(fields, objs, raw=raw)
+        return query.get_compiler(using=using).execute_sql(returning_fields)
+
+    _insert.alters_data = True
+    _insert.queryset_only = False
+
+    def _batched_insert(
+        self,
+        objs,
+        fields,
+        batch_size,
+        on_conflict=None,
+        update_fields=None,
+        unique_fields=None,
+    ):
+        """
+        Helper method for bulk_create() to insert objs one batch at a time.
+        """
+        connection = connections[self.db]
+        ops = connection.ops
+        max_batch_size = max(ops.bulk_batch_size(fields, objs), 1)
+        batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
+        inserted_rows = []
+        bulk_return = connection.features.can_return_rows_from_bulk_insert
+        for item in [objs[i : i + batch_size] for i in range(0, len(objs), batch_size)]:
+            if bulk_return and on_conflict is None:
+                inserted_rows.extend(
+                    self._insert(
+                        item,
+                        fields=fields,
+                        using=self.db,
+                        returning_fields=self.model._meta.db_returning_fields,
+                    )
+                )
+            else:
+                self._insert(
+                    item,
+                    fields=fields,
+                    using=self.db,
+                    on_conflict=on_conflict,
+                    update_fields=update_fields,
+                    unique_fields=unique_fields,
+                )
+        return inserted_rows
+
+    def _chain(self):
+        """
+        Return a copy of the current QuerySet that's ready for another
+        operation.
+        """
+        obj = self._clone()
+        if obj._sticky_filter:
+            obj.query.filter_is_sticky = True
+            obj._sticky_filter = False
+        return obj
+
+    def _clone(self):
+        """
+        Return a copy of the current QuerySet. A lightweight alternative
+        to deepcopy().
+        """
+        c = self.__class__(
+            model=self.model,
+            query=self.query.chain(),
+            using=self._db,
+            hints=self._hints,
+        )
+        c._sticky_filter = self._sticky_filter
+        c._for_write = self._for_write
+        c._prefetch_related_lookups = self._prefetch_related_lookups[:]
+        c._known_related_objects = self._known_related_objects
+        c._iterable_class = self._iterable_class
+        c._fields = self._fields
+        return c
+
+    def _fetch_all(self):
+        if self._result_cache is None:
+            self._result_cache = list(self._iterable_class(self))
+        if self._prefetch_related_lookups and not self._prefetch_done:
+            self._prefetch_related_objects()
+
+    def _next_is_sticky(self):
+        """
+        Indicate that the next filter call and the one following that should
+        be treated as a single filter. This is only important when it comes to
+        determining when to reuse tables for many-to-many filters. Required so
+        that we can filter naturally on the results of related managers.
+
+        This doesn't return a clone of the current QuerySet (it returns
+        "self"). The method is only used internally and should be immediately
+        followed by a filter() that does create a clone.
+        """
+        self._sticky_filter = True
+        return self
+
+    def _merge_sanity_check(self, other):
+        """Check that two QuerySet classes may be merged."""
+        if self._fields is not None and (
+            set(self.query.values_select) != set(other.query.values_select)
+            or set(self.query.extra_select) != set(other.query.extra_select)
+            or set(self.query.annotation_select) != set(other.query.annotation_select)
+        ):
+            raise TypeError(
+                "Merging '%s' classes must involve the same values in each case."
+                % self.__class__.__name__
+            )
+
+    def _merge_known_related_objects(self, other):
+        """
+        Keep track of all known related objects from either QuerySet instance.
+        """
+        for field, objects in other._known_related_objects.items():
+            self._known_related_objects.setdefault(field, {}).update(objects)
+
+    def resolve_expression(self, *args, **kwargs):
+        if self._fields and len(self._fields) > 1:
+            # values() queryset can only be used as nested queries
+            # if they are set up to select only a single field.
+            raise TypeError("Cannot use multi-field values as a filter value.")
+        query = self.query.resolve_expression(*args, **kwargs)
+        query._db = self._db
+        return query
+
+    resolve_expression.queryset_only = True
+
+    def _add_hints(self, **hints):
+        """
+        Update hinting information for use by routers. Add new key/values or
+        overwrite existing key/values.
+        """
+        self._hints.update(hints)
+
+    def _has_filters(self):
+        """
+        Check if this QuerySet has any filtering going on. This isn't
+        equivalent with checking if all objects are present in results, for
+        example, qs[1:]._has_filters() -> False.
+        """
+        return self.query.has_filters()
+
+    @staticmethod
+    def _validate_values_are_expressions(values, method_name):
+        invalid_args = sorted(
+            str(arg) for arg in values if not hasattr(arg, "resolve_expression")
+        )
+        if invalid_args:
+            raise TypeError(
+                "QuerySet.%s() received non-expression(s): %s."
+                % (
+                    method_name,
+                    ", ".join(invalid_args),
+                )
+            )
+
+    def _not_support_combined_queries(self, operation_name):
+        if self.query.combinator:
+            raise NotSupportedError(
+                "Calling QuerySet.%s() after %s() is not supported."
+                % (operation_name, self.query.combinator)
+            )
+
+    def _check_operator_queryset(self, other, operator_):
+        if self.query.combinator or other.query.combinator:
+            raise TypeError(f"Cannot use {operator_} operator with combined queryset.")
+
+    def _check_ordering_first_last_queryset_aggregation(self, method):
+        if isinstance(self.query.group_by, tuple) and not any(
+            col.output_field is self.model._meta.pk for col in self.query.group_by
+        ):
+            raise TypeError(
+                f"Cannot use QuerySet.{method}() on an unordered queryset performing "
+                f"aggregation. Add an ordering with order_by()."
+            )
+
+
+class InstanceCheckMeta(type):
+    def __instancecheck__(self, instance):
+        return isinstance(instance, QuerySet) and instance.query.is_empty()
+
+
+class EmptyQuerySet(metaclass=InstanceCheckMeta):
+    """
+    Marker class to checking if a queryset is empty by .none():
+        isinstance(qs.none(), EmptyQuerySet) -> True
+    """
+
+    def __init__(self, *args, **kwargs):
+        raise TypeError("EmptyQuerySet can't be instantiated")
+
+
+class RawQuerySet:
+    """
+    Provide an iterator which converts the results of raw SQL queries into
+    annotated model instances.
+    """
+
+    def __init__(
+        self,
+        raw_query,
+        model=None,
+        query=None,
+        params=(),
+        translations=None,
+        using=None,
+        hints=None,
+    ):
+        self.raw_query = raw_query
+        self.model = model
+        self._db = using
+        self._hints = hints or {}
+        self.query = query or sql.RawQuery(sql=raw_query, using=self.db, params=params)
+        self.params = params
+        self.translations = translations or {}
+        self._result_cache = None
+        self._prefetch_related_lookups = ()
+        self._prefetch_done = False
+
+    def resolve_model_init_order(self):
+        """Resolve the init field names and value positions."""
+        converter = connections[self.db].introspection.identifier_converter
+        model_init_fields = [
+            f for f in self.model._meta.fields if converter(f.column) in self.columns
+        ]
+        annotation_fields = [
+            (column, pos)
+            for pos, column in enumerate(self.columns)
+            if column not in self.model_fields
+        ]
+        model_init_order = [
+            self.columns.index(converter(f.column)) for f in model_init_fields
+        ]
+        model_init_names = [f.attname for f in model_init_fields]
+        return model_init_names, model_init_order, annotation_fields
+
+    def prefetch_related(self, *lookups):
+        """Same as QuerySet.prefetch_related()"""
+        clone = self._clone()
+        if lookups == (None,):
+            clone._prefetch_related_lookups = ()
+        else:
+            clone._prefetch_related_lookups = clone._prefetch_related_lookups + lookups
+        return clone
+
+    def _prefetch_related_objects(self):
+        prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
+        self._prefetch_done = True
+
+    def _clone(self):
+        """Same as QuerySet._clone()"""
+        c = self.__class__(
+            self.raw_query,
+            model=self.model,
+            query=self.query,
+            params=self.params,
+            translations=self.translations,
+            using=self._db,
+            hints=self._hints,
+        )
+        c._prefetch_related_lookups = self._prefetch_related_lookups[:]
+        return c
+
+    def _fetch_all(self):
+        if self._result_cache is None:
+            self._result_cache = list(self.iterator())
+        if self._prefetch_related_lookups and not self._prefetch_done:
+            self._prefetch_related_objects()
+
+    def __len__(self):
+        self._fetch_all()
+        return len(self._result_cache)
+
+    def __bool__(self):
+        self._fetch_all()
+        return bool(self._result_cache)
+
+    def __iter__(self):
+        self._fetch_all()
+        return iter(self._result_cache)
+
+    def __aiter__(self):
+        # Remember, __aiter__ itself is synchronous, it's the thing it returns
+        # that is async!
+        async def generator():
+            await sync_to_async(self._fetch_all)()
+            for item in self._result_cache:
+                yield item
+
+        return generator()
+
+    def iterator(self):
+        yield from RawModelIterable(self)
+
+    def __repr__(self):
+        return "<%s: %s>" % (self.__class__.__name__, self.query)
+
+    def __getitem__(self, k):
+        return list(self)[k]
+
+    @property
+    def db(self):
+        """Return the database used if this query is executed now."""
+        return self._db or router.db_for_read(self.model, **self._hints)
+
+    def using(self, alias):
+        """Select the database this RawQuerySet should execute against."""
+        return RawQuerySet(
+            self.raw_query,
+            model=self.model,
+            query=self.query.chain(using=alias),
+            params=self.params,
+            translations=self.translations,
+            using=alias,
+        )
+
+    @cached_property
+    def columns(self):
+        """
+        A list of model field names in the order they'll appear in the
+        query results.
+        """
+        columns = self.query.get_columns()
+        # Adjust any column names which don't match field names
+        for query_name, model_name in self.translations.items():
+            # Ignore translations for nonexistent column names
+            try:
+                index = columns.index(query_name)
+            except ValueError:
+                pass
+            else:
+                columns[index] = model_name
+        return columns
+
+    @cached_property
+    def model_fields(self):
+        """A dict mapping column names to model field names."""
+        converter = connections[self.db].introspection.identifier_converter
+        model_fields = {}
+        for field in self.model._meta.fields:
+            name, column = field.get_attname_column()
+            model_fields[converter(column)] = field
+        return model_fields
+
+
+class Prefetch:
+    def __init__(self, lookup, queryset=None, to_attr=None):
+        # `prefetch_through` is the path we traverse to perform the prefetch.
+        self.prefetch_through = lookup
+        # `prefetch_to` is the path to the attribute that stores the result.
+        self.prefetch_to = lookup
+        if queryset is not None and (
+            isinstance(queryset, RawQuerySet)
+            or (
+                hasattr(queryset, "_iterable_class")
+                and not issubclass(queryset._iterable_class, ModelIterable)
+            )
+        ):
+            raise ValueError(
+                "Prefetch querysets cannot use raw(), values(), and values_list()."
+            )
+        if to_attr:
+            self.prefetch_to = LOOKUP_SEP.join(
+                lookup.split(LOOKUP_SEP)[:-1] + [to_attr]
+            )
+
+        self.queryset = queryset
+        self.to_attr = to_attr
+
+    def __getstate__(self):
+        obj_dict = self.__dict__.copy()
+        if self.queryset is not None:
+            queryset = self.queryset._chain()
+            # Prevent the QuerySet from being evaluated
+            queryset._result_cache = []
+            queryset._prefetch_done = True
+            obj_dict["queryset"] = queryset
+        return obj_dict
+
+    def add_prefix(self, prefix):
+        self.prefetch_through = prefix + LOOKUP_SEP + self.prefetch_through
+        self.prefetch_to = prefix + LOOKUP_SEP + self.prefetch_to
+
+    def get_current_prefetch_to(self, level):
+        return LOOKUP_SEP.join(self.prefetch_to.split(LOOKUP_SEP)[: level + 1])
+
+    def get_current_to_attr(self, level):
+        parts = self.prefetch_to.split(LOOKUP_SEP)
+        to_attr = parts[level]
+        as_attr = self.to_attr and level == len(parts) - 1
+        return to_attr, as_attr
+
+    def get_current_queryset(self, level):
+        if self.get_current_prefetch_to(level) == self.prefetch_to:
+            return self.queryset
+        return None
+
+    def __eq__(self, other):
+        if not isinstance(other, Prefetch):
+            return NotImplemented
+        return self.prefetch_to == other.prefetch_to
+
+    def __hash__(self):
+        return hash((self.__class__, self.prefetch_to))
+
+
+def normalize_prefetch_lookups(lookups, prefix=None):
+    """Normalize lookups into Prefetch objects."""
+    ret = []
+    for lookup in lookups:
+        if not isinstance(lookup, Prefetch):
+            lookup = Prefetch(lookup)
+        if prefix:
+            lookup.add_prefix(prefix)
+        ret.append(lookup)
+    return ret
+
+
+def prefetch_related_objects(model_instances, *related_lookups):
+    """
+    Populate prefetched object caches for a list of model instances based on
+    the lookups/Prefetch instances given.
+    """
+    if not model_instances:
+        return  # nothing to do
+
+    # We need to be able to dynamically add to the list of prefetch_related
+    # lookups that we look up (see below).  So we need some book keeping to
+    # ensure we don't do duplicate work.
+    done_queries = {}  # dictionary of things like 'foo__bar': [results]
+
+    auto_lookups = set()  # we add to this as we go through.
+    followed_descriptors = set()  # recursion protection
+
+    all_lookups = normalize_prefetch_lookups(reversed(related_lookups))
+    while all_lookups:
+        lookup = all_lookups.pop()
+        if lookup.prefetch_to in done_queries:
+            if lookup.queryset is not None:
+                raise ValueError(
+                    "'%s' lookup was already seen with a different queryset. "
+                    "You may need to adjust the ordering of your lookups."
+                    % lookup.prefetch_to
+                )
+
+            continue
+
+        # Top level, the list of objects to decorate is the result cache
+        # from the primary QuerySet. It won't be for deeper levels.
+        obj_list = model_instances
+
+        through_attrs = lookup.prefetch_through.split(LOOKUP_SEP)
+        for level, through_attr in enumerate(through_attrs):
+            # Prepare main instances
+            if not obj_list:
+                break
+
+            prefetch_to = lookup.get_current_prefetch_to(level)
+            if prefetch_to in done_queries:
+                # Skip any prefetching, and any object preparation
+                obj_list = done_queries[prefetch_to]
+                continue
+
+            # Prepare objects:
+            good_objects = True
+            for obj in obj_list:
+                # Since prefetching can re-use instances, it is possible to have
+                # the same instance multiple times in obj_list, so obj might
+                # already be prepared.
+                if not hasattr(obj, "_prefetched_objects_cache"):
+                    try:
+                        obj._prefetched_objects_cache = {}
+                    except (AttributeError, TypeError):
+                        # Must be an immutable object from
+                        # values_list(flat=True), for example (TypeError) or
+                        # a QuerySet subclass that isn't returning Model
+                        # instances (AttributeError), either in Django or a 3rd
+                        # party. prefetch_related() doesn't make sense, so quit.
+                        good_objects = False
+                        break
+            if not good_objects:
+                break
+
+            # Descend down tree
+
+            # We assume that objects retrieved are homogeneous (which is the premise
+            # of prefetch_related), so what applies to first object applies to all.
+            first_obj = obj_list[0]
+            to_attr = lookup.get_current_to_attr(level)[0]
+            prefetcher, descriptor, attr_found, is_fetched = get_prefetcher(
+                first_obj, through_attr, to_attr
+            )
+
+            if not attr_found:
+                raise AttributeError(
+                    "Cannot find '%s' on %s object, '%s' is an invalid "
+                    "parameter to prefetch_related()"
+                    % (
+                        through_attr,
+                        first_obj.__class__.__name__,
+                        lookup.prefetch_through,
+                    )
+                )
+
+            if level == len(through_attrs) - 1 and prefetcher is None:
+                # Last one, this *must* resolve to something that supports
+                # prefetching, otherwise there is no point adding it and the
+                # developer asking for it has made a mistake.
+                raise ValueError(
+                    "'%s' does not resolve to an item that supports "
+                    "prefetching - this is an invalid parameter to "
+                    "prefetch_related()." % lookup.prefetch_through
+                )
+
+            obj_to_fetch = None
+            if prefetcher is not None:
+                obj_to_fetch = [obj for obj in obj_list if not is_fetched(obj)]
+
+            if obj_to_fetch:
+                obj_list, additional_lookups = prefetch_one_level(
+                    obj_to_fetch,
+                    prefetcher,
+                    lookup,
+                    level,
+                )
+                # We need to ensure we don't keep adding lookups from the
+                # same relationships to stop infinite recursion. So, if we
+                # are already on an automatically added lookup, don't add
+                # the new lookups from relationships we've seen already.
+                if not (
+                    prefetch_to in done_queries
+                    and lookup in auto_lookups
+                    and descriptor in followed_descriptors
+                ):
+                    done_queries[prefetch_to] = obj_list
+                    new_lookups = normalize_prefetch_lookups(
+                        reversed(additional_lookups), prefetch_to
+                    )
+                    auto_lookups.update(new_lookups)
+                    all_lookups.extend(new_lookups)
+                followed_descriptors.add(descriptor)
+            else:
+                # Either a singly related object that has already been fetched
+                # (e.g. via select_related), or hopefully some other property
+                # that doesn't support prefetching but needs to be traversed.
+
+                # We replace the current list of parent objects with the list
+                # of related objects, filtering out empty or missing values so
+                # that we can continue with nullable or reverse relations.
+                new_obj_list = []
+                for obj in obj_list:
+                    if through_attr in getattr(obj, "_prefetched_objects_cache", ()):
+                        # If related objects have been prefetched, use the
+                        # cache rather than the object's through_attr.
+                        new_obj = list(obj._prefetched_objects_cache.get(through_attr))
+                    else:
+                        try:
+                            new_obj = getattr(obj, through_attr)
+                        except exceptions.ObjectDoesNotExist:
+                            continue
+                    if new_obj is None:
+                        continue
+                    # We special-case `list` rather than something more generic
+                    # like `Iterable` because we don't want to accidentally match
+                    # user models that define __iter__.
+                    if isinstance(new_obj, list):
+                        new_obj_list.extend(new_obj)
+                    else:
+                        new_obj_list.append(new_obj)
+                obj_list = new_obj_list
+
+
+def get_prefetcher(instance, through_attr, to_attr):
+    """
+    For the attribute 'through_attr' on the given instance, find
+    an object that has a get_prefetch_queryset().
+    Return a 4 tuple containing:
+    (the object with get_prefetch_queryset (or None),
+     the descriptor object representing this relationship (or None),
+     a boolean that is False if the attribute was not found at all,
+     a function that takes an instance and returns a boolean that is True if
+     the attribute has already been fetched for that instance)
+    """
+
+    def has_to_attr_attribute(instance):
+        return hasattr(instance, to_attr)
+
+    prefetcher = None
+    is_fetched = has_to_attr_attribute
+
+    # For singly related objects, we have to avoid getting the attribute
+    # from the object, as this will trigger the query. So we first try
+    # on the class, in order to get the descriptor object.
+    rel_obj_descriptor = getattr(instance.__class__, through_attr, None)
+    if rel_obj_descriptor is None:
+        attr_found = hasattr(instance, through_attr)
+    else:
+        attr_found = True
+        if rel_obj_descriptor:
+            # singly related object, descriptor object has the
+            # get_prefetch_queryset() method.
+            if hasattr(rel_obj_descriptor, "get_prefetch_queryset"):
+                prefetcher = rel_obj_descriptor
+                is_fetched = rel_obj_descriptor.is_cached
+            else:
+                # descriptor doesn't support prefetching, so we go ahead and get
+                # the attribute on the instance rather than the class to
+                # support many related managers
+                rel_obj = getattr(instance, through_attr)
+                if hasattr(rel_obj, "get_prefetch_queryset"):
+                    prefetcher = rel_obj
+                if through_attr != to_attr:
+                    # Special case cached_property instances because hasattr
+                    # triggers attribute computation and assignment.
+                    if isinstance(
+                        getattr(instance.__class__, to_attr, None), cached_property
+                    ):
+
+                        def has_cached_property(instance):
+                            return to_attr in instance.__dict__
+
+                        is_fetched = has_cached_property
+                else:
+
+                    def in_prefetched_cache(instance):
+                        return through_attr in instance._prefetched_objects_cache
+
+                    is_fetched = in_prefetched_cache
+    return prefetcher, rel_obj_descriptor, attr_found, is_fetched
+
+
+def prefetch_one_level(instances, prefetcher, lookup, level):
+    """
+    Helper function for prefetch_related_objects().
+
+    Run prefetches on all instances using the prefetcher object,
+    assigning results to relevant caches in instance.
+
+    Return the prefetched objects along with any additional prefetches that
+    must be done due to prefetch_related lookups found from default managers.
+    """
+    # prefetcher must have a method get_prefetch_queryset() which takes a list
+    # of instances, and returns a tuple:
+
+    # (queryset of instances of self.model that are related to passed in instances,
+    #  callable that gets value to be matched for returned instances,
+    #  callable that gets value to be matched for passed in instances,
+    #  boolean that is True for singly related objects,
+    #  cache or field name to assign to,
+    #  boolean that is True when the previous argument is a cache name vs a field name).
+
+    # The 'values to be matched' must be hashable as they will be used
+    # in a dictionary.
+
+    (
+        rel_qs,
+        rel_obj_attr,
+        instance_attr,
+        single,
+        cache_name,
+        is_descriptor,
+    ) = prefetcher.get_prefetch_queryset(instances, lookup.get_current_queryset(level))
+    # We have to handle the possibility that the QuerySet we just got back
+    # contains some prefetch_related lookups. We don't want to trigger the
+    # prefetch_related functionality by evaluating the query. Rather, we need
+    # to merge in the prefetch_related lookups.
+    # Copy the lookups in case it is a Prefetch object which could be reused
+    # later (happens in nested prefetch_related).
+    additional_lookups = [
+        copy.copy(additional_lookup)
+        for additional_lookup in getattr(rel_qs, "_prefetch_related_lookups", ())
+    ]
+    if additional_lookups:
+        # Don't need to clone because the manager should have given us a fresh
+        # instance, so we access an internal instead of using public interface
+        # for performance reasons.
+        rel_qs._prefetch_related_lookups = ()
+
+    all_related_objects = list(rel_qs)
+
+    rel_obj_cache = {}
+    for rel_obj in all_related_objects:
+        rel_attr_val = rel_obj_attr(rel_obj)
+        rel_obj_cache.setdefault(rel_attr_val, []).append(rel_obj)
+
+    to_attr, as_attr = lookup.get_current_to_attr(level)
+    # Make sure `to_attr` does not conflict with a field.
+    if as_attr and instances:
+        # We assume that objects retrieved are homogeneous (which is the premise
+        # of prefetch_related), so what applies to first object applies to all.
+        model = instances[0].__class__
+        try:
+            model._meta.get_field(to_attr)
+        except exceptions.FieldDoesNotExist:
+            pass
+        else:
+            msg = "to_attr={} conflicts with a field on the {} model."
+            raise ValueError(msg.format(to_attr, model.__name__))
+
+    # Whether or not we're prefetching the last part of the lookup.
+    leaf = len(lookup.prefetch_through.split(LOOKUP_SEP)) - 1 == level
+
+    for obj in instances:
+        instance_attr_val = instance_attr(obj)
+        vals = rel_obj_cache.get(instance_attr_val, [])
+
+        if single:
+            val = vals[0] if vals else None
+            if as_attr:
+                # A to_attr has been given for the prefetch.
+                setattr(obj, to_attr, val)
+            elif is_descriptor:
+                # cache_name points to a field name in obj.
+                # This field is a descriptor for a related object.
+                setattr(obj, cache_name, val)
+            else:
+                # No to_attr has been given for this prefetch operation and the
+                # cache_name does not point to a descriptor. Store the value of
+                # the field in the object's field cache.
+                obj._state.fields_cache[cache_name] = val
+        else:
+            if as_attr:
+                setattr(obj, to_attr, vals)
+            else:
+                manager = getattr(obj, to_attr)
+                if leaf and lookup.queryset is not None:
+                    qs = manager._apply_rel_filters(lookup.queryset)
+                else:
+                    qs = manager.get_queryset()
+                qs._result_cache = vals
+                # We don't want the individual qs doing prefetch_related now,
+                # since we have merged this into the current work.
+                qs._prefetch_done = True
+                obj._prefetched_objects_cache[cache_name] = qs
+    return all_related_objects, additional_lookups
+
+
+class RelatedPopulator:
+    """
+    RelatedPopulator is used for select_related() object instantiation.
+
+    The idea is that each select_related() model will be populated by a
+    different RelatedPopulator instance. The RelatedPopulator instances get
+    klass_info and select (computed in SQLCompiler) plus the used db as
+    input for initialization. That data is used to compute which columns
+    to use, how to instantiate the model, and how to populate the links
+    between the objects.
+
+    The actual creation of the objects is done in populate() method. This
+    method gets row and from_obj as input and populates the select_related()
+    model instance.
+    """
+
+    def __init__(self, klass_info, select, db):
+        self.db = db
+        # Pre-compute needed attributes. The attributes are:
+        #  - model_cls: the possibly deferred model class to instantiate
+        #  - either:
+        #    - cols_start, cols_end: usually the columns in the row are
+        #      in the same order model_cls.__init__ expects them, so we
+        #      can instantiate by model_cls(*row[cols_start:cols_end])
+        #    - reorder_for_init: When select_related descends to a child
+        #      class, then we want to reuse the already selected parent
+        #      data. However, in this case the parent data isn't necessarily
+        #      in the same order that Model.__init__ expects it to be, so
+        #      we have to reorder the parent data. The reorder_for_init
+        #      attribute contains a function used to reorder the field data
+        #      in the order __init__ expects it.
+        #  - pk_idx: the index of the primary key field in the reordered
+        #    model data. Used to check if a related object exists at all.
+        #  - init_list: the field attnames fetched from the database. For
+        #    deferred models this isn't the same as all attnames of the
+        #    model's fields.
+        #  - related_populators: a list of RelatedPopulator instances if
+        #    select_related() descends to related models from this model.
+        #  - local_setter, remote_setter: Methods to set cached values on
+        #    the object being populated and on the remote object. Usually
+        #    these are Field.set_cached_value() methods.
+        select_fields = klass_info["select_fields"]
+        from_parent = klass_info["from_parent"]
+        if not from_parent:
+            self.cols_start = select_fields[0]
+            self.cols_end = select_fields[-1] + 1
+            self.init_list = [
+                f[0].target.attname for f in select[self.cols_start : self.cols_end]
+            ]
+            self.reorder_for_init = None
+        else:
+            attname_indexes = {
+                select[idx][0].target.attname: idx for idx in select_fields
+            }
+            model_init_attnames = (
+                f.attname for f in klass_info["model"]._meta.concrete_fields
+            )
+            self.init_list = [
+                attname for attname in model_init_attnames if attname in attname_indexes
+            ]
+            self.reorder_for_init = operator.itemgetter(
+                *[attname_indexes[attname] for attname in self.init_list]
+            )
+
+        self.model_cls = klass_info["model"]
+        self.pk_idx = self.init_list.index(self.model_cls._meta.pk.attname)
+        self.related_populators = get_related_populators(klass_info, select, self.db)
+        self.local_setter = klass_info["local_setter"]
+        self.remote_setter = klass_info["remote_setter"]
+
+    def populate(self, row, from_obj):
+        if self.reorder_for_init:
+            obj_data = self.reorder_for_init(row)
+        else:
+            obj_data = row[self.cols_start : self.cols_end]
+        if obj_data[self.pk_idx] is None:
+            obj = None
+        else:
+            obj = self.model_cls.from_db(self.db, self.init_list, obj_data)
+            for rel_iter in self.related_populators:
+                rel_iter.populate(row, obj)
+        self.local_setter(from_obj, obj)
+        if obj is not None:
+            self.remote_setter(obj, from_obj)
+
+
+def get_related_populators(klass_info, select, db):
+    iterators = []
+    related_klass_infos = klass_info.get("related_klass_infos", [])
+    for rel_klass_info in related_klass_infos:
+        rel_cls = RelatedPopulator(rel_klass_info, select, db)
+        iterators.append(rel_cls)
+    return iterators
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/django/db/models/query_utils.html b/docs/_build/html/_modules/django/db/models/query_utils.html new file mode 100644 index 00000000..c9e7fdb6 --- /dev/null +++ b/docs/_build/html/_modules/django/db/models/query_utils.html @@ -0,0 +1,537 @@ + + + + + + django.db.models.query_utils — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for django.db.models.query_utils

+"""
+Various data structures used in query construction.
+
+Factored out from django.db.models.query to avoid making the main module very
+large and/or so that they can be used by other modules without getting into
+circular import difficulties.
+"""
+import functools
+import inspect
+import logging
+from collections import namedtuple
+
+from django.core.exceptions import FieldError
+from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections
+from django.db.models.constants import LOOKUP_SEP
+from django.utils import tree
+
+logger = logging.getLogger("django.db.models")
+
+# PathInfo is used when converting lookups (fk__somecol). The contents
+# describe the relation in Model terms (model Options and Fields for both
+# sides of the relation. The join_field is the field backing the relation.
+PathInfo = namedtuple(
+    "PathInfo",
+    "from_opts to_opts target_fields join_field m2m direct filtered_relation",
+)
+
+
+def subclasses(cls):
+    yield cls
+    for subclass in cls.__subclasses__():
+        yield from subclasses(subclass)
+
+
+class Q(tree.Node):
+    """
+    Encapsulate filters as objects that can then be combined logically (using
+    `&` and `|`).
+    """
+
+    # Connection types
+    AND = "AND"
+    OR = "OR"
+    XOR = "XOR"
+    default = AND
+    conditional = True
+
+    def __init__(self, *args, _connector=None, _negated=False, **kwargs):
+        super().__init__(
+            children=[*args, *sorted(kwargs.items())],
+            connector=_connector,
+            negated=_negated,
+        )
+
+    def _combine(self, other, conn):
+        if getattr(other, "conditional", False) is False:
+            raise TypeError(other)
+        if not self:
+            return other.copy()
+        if not other and isinstance(other, Q):
+            return self.copy()
+
+        obj = self.create(connector=conn)
+        obj.add(self, conn)
+        obj.add(other, conn)
+        return obj
+
+    def __or__(self, other):
+        return self._combine(other, self.OR)
+
+    def __and__(self, other):
+        return self._combine(other, self.AND)
+
+    def __xor__(self, other):
+        return self._combine(other, self.XOR)
+
+    def __invert__(self):
+        obj = self.copy()
+        obj.negate()
+        return obj
+
+    def resolve_expression(
+        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
+    ):
+        # We must promote any new joins to left outer joins so that when Q is
+        # used as an expression, rows aren't filtered due to joins.
+        clause, joins = query._add_q(
+            self,
+            reuse,
+            allow_joins=allow_joins,
+            split_subq=False,
+            check_filterable=False,
+            summarize=summarize,
+        )
+        query.promote_joins(joins)
+        return clause
+
+    def flatten(self):
+        """
+        Recursively yield this Q object and all subexpressions, in depth-first
+        order.
+        """
+        yield self
+        for child in self.children:
+            if isinstance(child, tuple):
+                # Use the lookup.
+                child = child[1]
+            if hasattr(child, "flatten"):
+                yield from child.flatten()
+            else:
+                yield child
+
+    def check(self, against, using=DEFAULT_DB_ALIAS):
+        """
+        Do a database query to check if the expressions of the Q instance
+        matches against the expressions.
+        """
+        # Avoid circular imports.
+        from django.db.models import BooleanField, Value
+        from django.db.models.functions import Coalesce
+        from django.db.models.sql import Query
+        from django.db.models.sql.constants import SINGLE
+
+        query = Query(None)
+        for name, value in against.items():
+            if not hasattr(value, "resolve_expression"):
+                value = Value(value)
+            query.add_annotation(value, name, select=False)
+        query.add_annotation(Value(1), "_check")
+        # This will raise a FieldError if a field is missing in "against".
+        if connections[using].features.supports_comparing_boolean_expr:
+            query.add_q(Q(Coalesce(self, True, output_field=BooleanField())))
+        else:
+            query.add_q(self)
+        compiler = query.get_compiler(using=using)
+        try:
+            return compiler.execute_sql(SINGLE) is not None
+        except DatabaseError as e:
+            logger.warning("Got a database error calling check() on %r: %s", self, e)
+            return True
+
+    def deconstruct(self):
+        path = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
+        if path.startswith("django.db.models.query_utils"):
+            path = path.replace("django.db.models.query_utils", "django.db.models")
+        args = tuple(self.children)
+        kwargs = {}
+        if self.connector != self.default:
+            kwargs["_connector"] = self.connector
+        if self.negated:
+            kwargs["_negated"] = True
+        return path, args, kwargs
+
+
+class DeferredAttribute:
+    """
+    A wrapper for a deferred-loading field. When the value is read from this
+    object the first time, the query is executed.
+    """
+
+    def __init__(self, field):
+        self.field = field
+
+    def __get__(self, instance, cls=None):
+        """
+        Retrieve and caches the value from the datastore on the first lookup.
+        Return the cached value.
+        """
+        if instance is None:
+            return self
+        data = instance.__dict__
+        field_name = self.field.attname
+        if field_name not in data:
+            # Let's see if the field is part of the parent chain. If so we
+            # might be able to reuse the already loaded value. Refs #18343.
+            val = self._check_parent_chain(instance)
+            if val is None:
+                instance.refresh_from_db(fields=[field_name])
+            else:
+                data[field_name] = val
+        return data[field_name]
+
+    def _check_parent_chain(self, instance):
+        """
+        Check if the field value can be fetched from a parent field already
+        loaded in the instance. This can be done if the to-be fetched
+        field is a primary key field.
+        """
+        opts = instance._meta
+        link_field = opts.get_ancestor_link(self.field.model)
+        if self.field.primary_key and self.field != link_field:
+            return getattr(instance, link_field.attname)
+        return None
+
+
+class class_or_instance_method:
+    """
+    Hook used in RegisterLookupMixin to return partial functions depending on
+    the caller type (instance or class of models.Field).
+    """
+
+    def __init__(self, class_method, instance_method):
+        self.class_method = class_method
+        self.instance_method = instance_method
+
+    def __get__(self, instance, owner):
+        if instance is None:
+            return functools.partial(self.class_method, owner)
+        return functools.partial(self.instance_method, instance)
+
+
+class RegisterLookupMixin:
+    def _get_lookup(self, lookup_name):
+        return self.get_lookups().get(lookup_name, None)
+
+    @functools.lru_cache(maxsize=None)
+    def get_class_lookups(cls):
+        class_lookups = [
+            parent.__dict__.get("class_lookups", {}) for parent in inspect.getmro(cls)
+        ]
+        return cls.merge_dicts(class_lookups)
+
+    def get_instance_lookups(self):
+        class_lookups = self.get_class_lookups()
+        if instance_lookups := getattr(self, "instance_lookups", None):
+            return {**class_lookups, **instance_lookups}
+        return class_lookups
+
+    get_lookups = class_or_instance_method(get_class_lookups, get_instance_lookups)
+    get_class_lookups = classmethod(get_class_lookups)
+
+    def get_lookup(self, lookup_name):
+        from django.db.models.lookups import Lookup
+
+        found = self._get_lookup(lookup_name)
+        if found is None and hasattr(self, "output_field"):
+            return self.output_field.get_lookup(lookup_name)
+        if found is not None and not issubclass(found, Lookup):
+            return None
+        return found
+
+    def get_transform(self, lookup_name):
+        from django.db.models.lookups import Transform
+
+        found = self._get_lookup(lookup_name)
+        if found is None and hasattr(self, "output_field"):
+            return self.output_field.get_transform(lookup_name)
+        if found is not None and not issubclass(found, Transform):
+            return None
+        return found
+
+    @staticmethod
+    def merge_dicts(dicts):
+        """
+        Merge dicts in reverse to preference the order of the original list. e.g.,
+        merge_dicts([a, b]) will preference the keys in 'a' over those in 'b'.
+        """
+        merged = {}
+        for d in reversed(dicts):
+            merged.update(d)
+        return merged
+
+    @classmethod
+    def _clear_cached_class_lookups(cls):
+        for subclass in subclasses(cls):
+            subclass.get_class_lookups.cache_clear()
+
+    def register_class_lookup(cls, lookup, lookup_name=None):
+        if lookup_name is None:
+            lookup_name = lookup.lookup_name
+        if "class_lookups" not in cls.__dict__:
+            cls.class_lookups = {}
+        cls.class_lookups[lookup_name] = lookup
+        cls._clear_cached_class_lookups()
+        return lookup
+
+    def register_instance_lookup(self, lookup, lookup_name=None):
+        if lookup_name is None:
+            lookup_name = lookup.lookup_name
+        if "instance_lookups" not in self.__dict__:
+            self.instance_lookups = {}
+        self.instance_lookups[lookup_name] = lookup
+        return lookup
+
+    register_lookup = class_or_instance_method(
+        register_class_lookup, register_instance_lookup
+    )
+    register_class_lookup = classmethod(register_class_lookup)
+
+    def _unregister_class_lookup(cls, lookup, lookup_name=None):
+        """
+        Remove given lookup from cls lookups. For use in tests only as it's
+        not thread-safe.
+        """
+        if lookup_name is None:
+            lookup_name = lookup.lookup_name
+        del cls.class_lookups[lookup_name]
+        cls._clear_cached_class_lookups()
+
+    def _unregister_instance_lookup(self, lookup, lookup_name=None):
+        """
+        Remove given lookup from instance lookups. For use in tests only as
+        it's not thread-safe.
+        """
+        if lookup_name is None:
+            lookup_name = lookup.lookup_name
+        del self.instance_lookups[lookup_name]
+
+    _unregister_lookup = class_or_instance_method(
+        _unregister_class_lookup, _unregister_instance_lookup
+    )
+    _unregister_class_lookup = classmethod(_unregister_class_lookup)
+
+
+def select_related_descend(field, restricted, requested, select_mask, reverse=False):
+    """
+    Return True if this field should be used to descend deeper for
+    select_related() purposes. Used by both the query construction code
+    (compiler.get_related_selections()) and the model instance creation code
+    (compiler.klass_info).
+
+    Arguments:
+     * field - the field to be checked
+     * restricted - a boolean field, indicating if the field list has been
+       manually restricted using a requested clause)
+     * requested - The select_related() dictionary.
+     * select_mask - the dictionary of selected fields.
+     * reverse - boolean, True if we are checking a reverse select related
+    """
+    if not field.remote_field:
+        return False
+    if field.remote_field.parent_link and not reverse:
+        return False
+    if restricted:
+        if reverse and field.related_query_name() not in requested:
+            return False
+        if not reverse and field.name not in requested:
+            return False
+    if not restricted and field.null:
+        return False
+    if (
+        restricted
+        and select_mask
+        and field.name in requested
+        and field not in select_mask
+    ):
+        raise FieldError(
+            f"Field {field.model._meta.object_name}.{field.name} cannot be both "
+            "deferred and traversed using select_related at the same time."
+        )
+    return True
+
+
+def refs_expression(lookup_parts, annotations):
+    """
+    Check if the lookup_parts contains references to the given annotations set.
+    Because the LOOKUP_SEP is contained in the default annotation names, check
+    each prefix of the lookup_parts for a match.
+    """
+    for n in range(1, len(lookup_parts) + 1):
+        level_n_lookup = LOOKUP_SEP.join(lookup_parts[0:n])
+        if annotations.get(level_n_lookup):
+            return level_n_lookup, lookup_parts[n:]
+    return None, ()
+
+
+def check_rel_lookup_compatibility(model, target_opts, field):
+    """
+    Check that self.model is compatible with target_opts. Compatibility
+    is OK if:
+      1) model and opts match (where proxy inheritance is removed)
+      2) model is parent of opts' model or the other way around
+    """
+
+    def check(opts):
+        return (
+            model._meta.concrete_model == opts.concrete_model
+            or opts.concrete_model in model._meta.get_parent_list()
+            or model in opts.get_parent_list()
+        )
+
+    # If the field is a primary key, then doing a query against the field's
+    # model is ok, too. Consider the case:
+    # class Restaurant(models.Model):
+    #     place = OneToOneField(Place, primary_key=True):
+    # Restaurant.objects.filter(pk__in=Restaurant.objects.all()).
+    # If we didn't have the primary key check, then pk__in (== place__in) would
+    # give Place's opts as the target opts, but Restaurant isn't compatible
+    # with that. This logic applies only to primary keys, as when doing __in=qs,
+    # we are going to turn this into __in=qs.values('pk') later on.
+    return check(target_opts) or (
+        getattr(field, "primary_key", False) and check(field.model._meta)
+    )
+
+
+class FilteredRelation:
+    """Specify custom filtering in the ON clause of SQL joins."""
+
+    def __init__(self, relation_name, *, condition=Q()):
+        if not relation_name:
+            raise ValueError("relation_name cannot be empty.")
+        self.relation_name = relation_name
+        self.alias = None
+        if not isinstance(condition, Q):
+            raise ValueError("condition argument must be a Q() instance.")
+        self.condition = condition
+        self.path = []
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+        return (
+            self.relation_name == other.relation_name
+            and self.alias == other.alias
+            and self.condition == other.condition
+        )
+
+    def clone(self):
+        clone = FilteredRelation(self.relation_name, condition=self.condition)
+        clone.alias = self.alias
+        clone.path = self.path[:]
+        return clone
+
+    def resolve_expression(self, *args, **kwargs):
+        """
+        QuerySet.annotate() only accepts expression-like arguments
+        (with a resolve_expression() method).
+        """
+        raise NotImplementedError("FilteredRelation.resolve_expression() is unused.")
+
+    def as_sql(self, compiler, connection):
+        # Resolve the condition in Join.filtered_relation.
+        query = compiler.query
+        where = query.build_filtered_relation_q(self.condition, reuse=set(self.path))
+        return compiler.compile(where)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/django/utils/functional.html b/docs/_build/html/_modules/django/utils/functional.html new file mode 100644 index 00000000..e4ca8435 --- /dev/null +++ b/docs/_build/html/_modules/django/utils/functional.html @@ -0,0 +1,567 @@ + + + + + + django.utils.functional — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for django.utils.functional

+import copy
+import itertools
+import operator
+import warnings
+from functools import total_ordering, wraps
+
+
+class cached_property:
+    """
+    Decorator that converts a method with a single self argument into a
+    property cached on the instance.
+
+    A cached property can be made out of an existing method:
+    (e.g. ``url = cached_property(get_absolute_url)``).
+    """
+
+    name = None
+
+    @staticmethod
+    def func(instance):
+        raise TypeError(
+            "Cannot use cached_property instance without calling "
+            "__set_name__() on it."
+        )
+
+    def __init__(self, func, name=None):
+        from django.utils.deprecation import RemovedInDjango50Warning
+
+        if name is not None:
+            warnings.warn(
+                "The name argument is deprecated as it's unnecessary as of "
+                "Python 3.6.",
+                RemovedInDjango50Warning,
+                stacklevel=2,
+            )
+        self.real_func = func
+        self.__doc__ = getattr(func, "__doc__")
+
+    def __set_name__(self, owner, name):
+        if self.name is None:
+            self.name = name
+            self.func = self.real_func
+        elif name != self.name:
+            raise TypeError(
+                "Cannot assign the same cached_property to two different names "
+                "(%r and %r)." % (self.name, name)
+            )
+
+    def __get__(self, instance, cls=None):
+        """
+        Call the function and put the return value in instance.__dict__ so that
+        subsequent attribute access on the instance returns the cached value
+        instead of calling cached_property.__get__().
+        """
+        if instance is None:
+            return self
+        res = instance.__dict__[self.name] = self.func(instance)
+        return res
+
+
+class classproperty:
+    """
+    Decorator that converts a method with a single cls argument into a property
+    that can be accessed directly from the class.
+    """
+
+    def __init__(self, method=None):
+        self.fget = method
+
+    def __get__(self, instance, cls=None):
+        return self.fget(cls)
+
+    def getter(self, method):
+        self.fget = method
+        return self
+
+
+class Promise:
+    """
+    Base class for the proxy class created in the closure of the lazy function.
+    It's used to recognize promises in code.
+    """
+
+    pass
+
+
+def lazy(func, *resultclasses):
+    """
+    Turn any callable into a lazy evaluated callable. result classes or types
+    is required -- at least one is needed so that the automatic forcing of
+    the lazy evaluation code is triggered. Results are not memoized; the
+    function is evaluated on every access.
+    """
+
+    @total_ordering
+    class __proxy__(Promise):
+        """
+        Encapsulate a function call and act as a proxy for methods that are
+        called on the result of that function. The function is not evaluated
+        until one of the methods on the result is called.
+        """
+
+        __prepared = False
+
+        def __init__(self, args, kw):
+            self.__args = args
+            self.__kw = kw
+            if not self.__prepared:
+                self.__prepare_class__()
+            self.__class__.__prepared = True
+
+        def __reduce__(self):
+            return (
+                _lazy_proxy_unpickle,
+                (func, self.__args, self.__kw) + resultclasses,
+            )
+
+        def __repr__(self):
+            return repr(self.__cast())
+
+        @classmethod
+        def __prepare_class__(cls):
+            for resultclass in resultclasses:
+                for type_ in resultclass.mro():
+                    for method_name in type_.__dict__:
+                        # All __promise__ return the same wrapper method, they
+                        # look up the correct implementation when called.
+                        if hasattr(cls, method_name):
+                            continue
+                        meth = cls.__promise__(method_name)
+                        setattr(cls, method_name, meth)
+            cls._delegate_bytes = bytes in resultclasses
+            cls._delegate_text = str in resultclasses
+            if cls._delegate_bytes and cls._delegate_text:
+                raise ValueError(
+                    "Cannot call lazy() with both bytes and text return types."
+                )
+            if cls._delegate_text:
+                cls.__str__ = cls.__text_cast
+            elif cls._delegate_bytes:
+                cls.__bytes__ = cls.__bytes_cast
+
+        @classmethod
+        def __promise__(cls, method_name):
+            # Builds a wrapper around some magic method
+            def __wrapper__(self, *args, **kw):
+                # Automatically triggers the evaluation of a lazy value and
+                # applies the given magic method of the result type.
+                res = func(*self.__args, **self.__kw)
+                return getattr(res, method_name)(*args, **kw)
+
+            return __wrapper__
+
+        def __text_cast(self):
+            return func(*self.__args, **self.__kw)
+
+        def __bytes_cast(self):
+            return bytes(func(*self.__args, **self.__kw))
+
+        def __bytes_cast_encoded(self):
+            return func(*self.__args, **self.__kw).encode()
+
+        def __cast(self):
+            if self._delegate_bytes:
+                return self.__bytes_cast()
+            elif self._delegate_text:
+                return self.__text_cast()
+            else:
+                return func(*self.__args, **self.__kw)
+
+        def __str__(self):
+            # object defines __str__(), so __prepare_class__() won't overload
+            # a __str__() method from the proxied class.
+            return str(self.__cast())
+
+        def __eq__(self, other):
+            if isinstance(other, Promise):
+                other = other.__cast()
+            return self.__cast() == other
+
+        def __lt__(self, other):
+            if isinstance(other, Promise):
+                other = other.__cast()
+            return self.__cast() < other
+
+        def __hash__(self):
+            return hash(self.__cast())
+
+        def __mod__(self, rhs):
+            if self._delegate_text:
+                return str(self) % rhs
+            return self.__cast() % rhs
+
+        def __add__(self, other):
+            return self.__cast() + other
+
+        def __radd__(self, other):
+            return other + self.__cast()
+
+        def __deepcopy__(self, memo):
+            # Instances of this class are effectively immutable. It's just a
+            # collection of functions. So we don't need to do anything
+            # complicated for copying.
+            memo[id(self)] = self
+            return self
+
+    @wraps(func)
+    def __wrapper__(*args, **kw):
+        # Creates the proxy object, instead of the actual value.
+        return __proxy__(args, kw)
+
+    return __wrapper__
+
+
+def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
+    return lazy(func, *resultclasses)(*args, **kwargs)
+
+
+def lazystr(text):
+    """
+    Shortcut for the common case of a lazy callable that returns str.
+    """
+    return lazy(str, str)(text)
+
+
+def keep_lazy(*resultclasses):
+    """
+    A decorator that allows a function to be called with one or more lazy
+    arguments. If none of the args are lazy, the function is evaluated
+    immediately, otherwise a __proxy__ is returned that will evaluate the
+    function when needed.
+    """
+    if not resultclasses:
+        raise TypeError("You must pass at least one argument to keep_lazy().")
+
+    def decorator(func):
+        lazy_func = lazy(func, *resultclasses)
+
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            if any(
+                isinstance(arg, Promise)
+                for arg in itertools.chain(args, kwargs.values())
+            ):
+                return lazy_func(*args, **kwargs)
+            return func(*args, **kwargs)
+
+        return wrapper
+
+    return decorator
+
+
+def keep_lazy_text(func):
+    """
+    A decorator for functions that accept lazy arguments and return text.
+    """
+    return keep_lazy(str)(func)
+
+
+empty = object()
+
+
+def new_method_proxy(func):
+    def inner(self, *args):
+        if (_wrapped := self._wrapped) is empty:
+            self._setup()
+            _wrapped = self._wrapped
+        return func(_wrapped, *args)
+
+    inner._mask_wrapped = False
+    return inner
+
+
+class LazyObject:
+    """
+    A wrapper for another class that can be used to delay instantiation of the
+    wrapped class.
+
+    By subclassing, you have the opportunity to intercept and alter the
+    instantiation. If you don't need to do that, use SimpleLazyObject.
+    """
+
+    # Avoid infinite recursion when tracing __init__ (#19456).
+    _wrapped = None
+
+    def __init__(self):
+        # Note: if a subclass overrides __init__(), it will likely need to
+        # override __copy__() and __deepcopy__() as well.
+        self._wrapped = empty
+
+    def __getattribute__(self, name):
+        if name == "_wrapped":
+            # Avoid recursion when getting wrapped object.
+            return super().__getattribute__(name)
+        value = super().__getattribute__(name)
+        # If attribute is a proxy method, raise an AttributeError to call
+        # __getattr__() and use the wrapped object method.
+        if not getattr(value, "_mask_wrapped", True):
+            raise AttributeError
+        return value
+
+    __getattr__ = new_method_proxy(getattr)
+
+    def __setattr__(self, name, value):
+        if name == "_wrapped":
+            # Assign to __dict__ to avoid infinite __setattr__ loops.
+            self.__dict__["_wrapped"] = value
+        else:
+            if self._wrapped is empty:
+                self._setup()
+            setattr(self._wrapped, name, value)
+
+    def __delattr__(self, name):
+        if name == "_wrapped":
+            raise TypeError("can't delete _wrapped.")
+        if self._wrapped is empty:
+            self._setup()
+        delattr(self._wrapped, name)
+
+    def _setup(self):
+        """
+        Must be implemented by subclasses to initialize the wrapped object.
+        """
+        raise NotImplementedError(
+            "subclasses of LazyObject must provide a _setup() method"
+        )
+
+    # Because we have messed with __class__ below, we confuse pickle as to what
+    # class we are pickling. We're going to have to initialize the wrapped
+    # object to successfully pickle it, so we might as well just pickle the
+    # wrapped object since they're supposed to act the same way.
+    #
+    # Unfortunately, if we try to simply act like the wrapped object, the ruse
+    # will break down when pickle gets our id(). Thus we end up with pickle
+    # thinking, in effect, that we are a distinct object from the wrapped
+    # object, but with the same __dict__. This can cause problems (see #25389).
+    #
+    # So instead, we define our own __reduce__ method and custom unpickler. We
+    # pickle the wrapped object as the unpickler's argument, so that pickle
+    # will pickle it normally, and then the unpickler simply returns its
+    # argument.
+    def __reduce__(self):
+        if self._wrapped is empty:
+            self._setup()
+        return (unpickle_lazyobject, (self._wrapped,))
+
+    def __copy__(self):
+        if self._wrapped is empty:
+            # If uninitialized, copy the wrapper. Use type(self), not
+            # self.__class__, because the latter is proxied.
+            return type(self)()
+        else:
+            # If initialized, return a copy of the wrapped object.
+            return copy.copy(self._wrapped)
+
+    def __deepcopy__(self, memo):
+        if self._wrapped is empty:
+            # We have to use type(self), not self.__class__, because the
+            # latter is proxied.
+            result = type(self)()
+            memo[id(self)] = result
+            return result
+        return copy.deepcopy(self._wrapped, memo)
+
+    __bytes__ = new_method_proxy(bytes)
+    __str__ = new_method_proxy(str)
+    __bool__ = new_method_proxy(bool)
+
+    # Introspection support
+    __dir__ = new_method_proxy(dir)
+
+    # Need to pretend to be the wrapped class, for the sake of objects that
+    # care about this (especially in equality tests)
+    __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
+    __eq__ = new_method_proxy(operator.eq)
+    __lt__ = new_method_proxy(operator.lt)
+    __gt__ = new_method_proxy(operator.gt)
+    __ne__ = new_method_proxy(operator.ne)
+    __hash__ = new_method_proxy(hash)
+
+    # List/Tuple/Dictionary methods support
+    __getitem__ = new_method_proxy(operator.getitem)
+    __setitem__ = new_method_proxy(operator.setitem)
+    __delitem__ = new_method_proxy(operator.delitem)
+    __iter__ = new_method_proxy(iter)
+    __len__ = new_method_proxy(len)
+    __contains__ = new_method_proxy(operator.contains)
+
+
+def unpickle_lazyobject(wrapped):
+    """
+    Used to unpickle lazy objects. Just return its argument, which will be the
+    wrapped object.
+    """
+    return wrapped
+
+
+class SimpleLazyObject(LazyObject):
+    """
+    A lazy object initialized from any function.
+
+    Designed for compound objects of unknown type. For builtins or objects of
+    known type, use django.utils.functional.lazy.
+    """
+
+    def __init__(self, func):
+        """
+        Pass in a callable that returns the object to be wrapped.
+
+        If copies are made of the resulting SimpleLazyObject, which can happen
+        in various circumstances within Django, then you must ensure that the
+        callable can be safely run more than once and will return the same
+        value.
+        """
+        self.__dict__["_setupfunc"] = func
+        super().__init__()
+
+    def _setup(self):
+        self._wrapped = self._setupfunc()
+
+    # Return a meaningful representation of the lazy object for debugging
+    # without evaluating the wrapped object.
+    def __repr__(self):
+        if self._wrapped is empty:
+            repr_attr = self._setupfunc
+        else:
+            repr_attr = self._wrapped
+        return "<%s: %r>" % (type(self).__name__, repr_attr)
+
+    def __copy__(self):
+        if self._wrapped is empty:
+            # If uninitialized, copy the wrapper. Use SimpleLazyObject, not
+            # self.__class__, because the latter is proxied.
+            return SimpleLazyObject(self._setupfunc)
+        else:
+            # If initialized, return a copy of the wrapped object.
+            return copy.copy(self._wrapped)
+
+    def __deepcopy__(self, memo):
+        if self._wrapped is empty:
+            # We have to use SimpleLazyObject, not self.__class__, because the
+            # latter is proxied.
+            result = SimpleLazyObject(self._setupfunc)
+            memo[id(self)] = result
+            return result
+        return copy.deepcopy(self._wrapped, memo)
+
+    __add__ = new_method_proxy(operator.add)
+
+    @new_method_proxy
+    def __radd__(self, other):
+        return other + self
+
+
+def partition(predicate, values):
+    """
+    Split the values into two sets, based on the return value of the function
+    (True/False). e.g.:
+
+        >>> partition(lambda x: x > 3, range(5))
+        [0, 1, 2, 3], [4]
+    """
+    results = ([], [])
+    for item in values:
+        results[predicate(item)].append(item)
+    return results
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/extensions/abstract_models.html b/docs/_build/html/_modules/extensions/abstract_models.html new file mode 100644 index 00000000..f4f2836a --- /dev/null +++ b/docs/_build/html/_modules/extensions/abstract_models.html @@ -0,0 +1,119 @@ + + + + + + extensions.abstract_models — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for extensions.abstract_models

+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+
+
[docs]class AbstractCreatAtUpdateAt(models.Model): + created_at = models.DateTimeField( + verbose_name=_('Created At'), + auto_now_add=True, + editable=False + ) + updated_at = models.DateTimeField( + verbose_name=_('Updated At'), + auto_now=True, + editable=False + ) + + class Meta: + abstract = True
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/extensions/choices.html b/docs/_build/html/_modules/extensions/choices.html new file mode 100644 index 00000000..adf758ab --- /dev/null +++ b/docs/_build/html/_modules/extensions/choices.html @@ -0,0 +1,137 @@ + + + + + + extensions.choices — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for extensions.choices

+from django.db.models import IntegerChoices
+from django.utils.translation import gettext_lazy as _
+
+
+
[docs]class GenderChoices(IntegerChoices): + """Enum class for the gender fields""" + MALE = 0, _('Male') + FEMALE = 1, _('Female') + + __empty__ = '---------'
+ + +
[docs]class LocationChoices(IntegerChoices): + """Enum class for the location fields""" + A = 0, _('A') + B = 1, _('B') + C = 2, _('C') + VIP = 3, _('VIP')
+ + +
[docs]class InvoiceStatusChoices(IntegerChoices): + """Enum class for the invoice status fields""" + PAID = 0, _('Paid') + UNPAID = 1, _('Unpaid')
+ + +
[docs]class TicketStatusChoices(IntegerChoices): + """Enum class for the ticket status fields""" + SOLD = 0, _('Sold') + RESERVED = 1, _('Reserved')
+ + +
[docs]class PaymentStatusChoices(IntegerChoices): + """Enum class for the payment status fields""" + SUCCESSFUL = 0, _('Successful') + UNSUCCESSFUL = 1, _('Unsuccessful')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/extensions/custom_exception_handlers.html b/docs/_build/html/_modules/extensions/custom_exception_handlers.html new file mode 100644 index 00000000..905af009 --- /dev/null +++ b/docs/_build/html/_modules/extensions/custom_exception_handlers.html @@ -0,0 +1,137 @@ + + + + + + extensions.custom_exception_handlers — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for extensions.custom_exception_handlers

+from django.core.exceptions import ValidationError as DjangoValidationError
+from django.db.models import ProtectedError as DjangoProtectedError
+from django.utils.translation import gettext_lazy as _
+
+from rest_framework.exceptions import ValidationError as DRFValidationError
+from rest_framework.views import exception_handler as drf_exception_handler
+
+import re
+
+
+
[docs]def exception_handler(exc, context): + """Handle Django ValidationError as an accepted exception""" + + if isinstance(exc, DjangoValidationError): + exc = DRFValidationError(detail=exc.message_dict) + + if isinstance(exc, DjangoProtectedError): + matches = re.search(r"\(\"([^()]+)\"", exc.args.__str__()) + exc = DRFValidationError( + detail={ + "error": { + "type": str(exc.__class__.__name__), + "message": matches.group(1) if matches else _('It is not possible to delete this object.') + }, + "protected_elements": [ + { + "id": protected_object.pk, + "model": str(protected_object._meta.model.__name__), + "label": protected_object.__str__() + } + for protected_object in exc.protected_objects + ] + } + ) + + return drf_exception_handler(exc, context)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/extensions/custom_permissions.html b/docs/_build/html/_modules/extensions/custom_permissions.html new file mode 100644 index 00000000..fbc67e01 --- /dev/null +++ b/docs/_build/html/_modules/extensions/custom_permissions.html @@ -0,0 +1,124 @@ + + + + + + extensions.custom_permissions — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for extensions.custom_permissions

+from rest_framework.permissions import DjangoModelPermissions, BasePermission
+
+from copy import deepcopy
+
+
+
[docs]class CustomDjangoModelPermission(DjangoModelPermissions): + """Custom permission class to handle better permission by model permissions""" +
[docs] def __init__(self): + self.perms_map = deepcopy(self.perms_map) + self.perms_map['GET'] = ['%(app_label)s.view_%(model_name)s']
+ + +
[docs]class UnauthenticatedPost(BasePermission): + """Allow to unauthenticated user to send the 'POST' request""" +
[docs] def has_permission(self, request, view): + return request.method in ['POST']
+ + +
[docs]class OwnUserPermission(BasePermission): + """Object-level permission to only allow updating his own user""" +
[docs] def has_object_permission(self, request, view, obj): + # obj here is a User instance + return obj.id == request.user.id
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/functools.html b/docs/_build/html/_modules/functools.html new file mode 100644 index 00000000..fb66aceb --- /dev/null +++ b/docs/_build/html/_modules/functools.html @@ -0,0 +1,1093 @@ + + + + + + functools — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for functools

+"""functools.py - Tools for working with functions and callable objects
+"""
+# Python module wrapper for _functools C module
+# to allow utilities written in Python to be added
+# to the functools module.
+# Written by Nick Coghlan <ncoghlan at gmail.com>,
+# Raymond Hettinger <python at rcn.com>,
+# and Łukasz Langa <lukasz at langa.pl>.
+#   Copyright (C) 2006-2013 Python Software Foundation.
+# See C source code for _functools credits/copyright
+
+__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
+           'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce',
+           'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
+           'cached_property']
+
+from abc import get_cache_token
+from collections import namedtuple
+# import types, weakref  # Deferred to single_dispatch()
+from reprlib import recursive_repr
+from _thread import RLock
+from types import GenericAlias
+
+
+################################################################################
+### update_wrapper() and wraps() decorator
+################################################################################
+
+# update_wrapper() and wraps() are tools to help write
+# wrapper functions that can handle naive introspection
+
+WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
+                       '__annotations__')
+WRAPPER_UPDATES = ('__dict__',)
+def update_wrapper(wrapper,
+                   wrapped,
+                   assigned = WRAPPER_ASSIGNMENTS,
+                   updated = WRAPPER_UPDATES):
+    """Update a wrapper function to look like the wrapped function
+
+       wrapper is the function to be updated
+       wrapped is the original function
+       assigned is a tuple naming the attributes assigned directly
+       from the wrapped function to the wrapper function (defaults to
+       functools.WRAPPER_ASSIGNMENTS)
+       updated is a tuple naming the attributes of the wrapper that
+       are updated with the corresponding attribute from the wrapped
+       function (defaults to functools.WRAPPER_UPDATES)
+    """
+    for attr in assigned:
+        try:
+            value = getattr(wrapped, attr)
+        except AttributeError:
+            pass
+        else:
+            setattr(wrapper, attr, value)
+    for attr in updated:
+        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
+    # from the wrapped function when updating __dict__
+    wrapper.__wrapped__ = wrapped
+    # Return the wrapper so this can be used as a decorator via partial()
+    return wrapper
+
+def wraps(wrapped,
+          assigned = WRAPPER_ASSIGNMENTS,
+          updated = WRAPPER_UPDATES):
+    """Decorator factory to apply update_wrapper() to a wrapper function
+
+       Returns a decorator that invokes update_wrapper() with the decorated
+       function as the wrapper argument and the arguments to wraps() as the
+       remaining arguments. Default arguments are as for update_wrapper().
+       This is a convenience function to simplify applying partial() to
+       update_wrapper().
+    """
+    return partial(update_wrapper, wrapped=wrapped,
+                   assigned=assigned, updated=updated)
+
+
+################################################################################
+### total_ordering class decorator
+################################################################################
+
+# The total ordering functions all invoke the root magic method directly
+# rather than using the corresponding operator.  This avoids possible
+# infinite recursion that could occur when the operator dispatch logic
+# detects a NotImplemented result and then calls a reflected method.
+
+def _gt_from_lt(self, other, NotImplemented=NotImplemented):
+    'Return a > b.  Computed by @total_ordering from (not a < b) and (a != b).'
+    op_result = type(self).__lt__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result and self != other
+
+def _le_from_lt(self, other, NotImplemented=NotImplemented):
+    'Return a <= b.  Computed by @total_ordering from (a < b) or (a == b).'
+    op_result = type(self).__lt__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return op_result or self == other
+
+def _ge_from_lt(self, other, NotImplemented=NotImplemented):
+    'Return a >= b.  Computed by @total_ordering from (not a < b).'
+    op_result = type(self).__lt__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result
+
+def _ge_from_le(self, other, NotImplemented=NotImplemented):
+    'Return a >= b.  Computed by @total_ordering from (not a <= b) or (a == b).'
+    op_result = type(self).__le__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result or self == other
+
+def _lt_from_le(self, other, NotImplemented=NotImplemented):
+    'Return a < b.  Computed by @total_ordering from (a <= b) and (a != b).'
+    op_result = type(self).__le__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return op_result and self != other
+
+def _gt_from_le(self, other, NotImplemented=NotImplemented):
+    'Return a > b.  Computed by @total_ordering from (not a <= b).'
+    op_result = type(self).__le__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result
+
+def _lt_from_gt(self, other, NotImplemented=NotImplemented):
+    'Return a < b.  Computed by @total_ordering from (not a > b) and (a != b).'
+    op_result = type(self).__gt__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result and self != other
+
+def _ge_from_gt(self, other, NotImplemented=NotImplemented):
+    'Return a >= b.  Computed by @total_ordering from (a > b) or (a == b).'
+    op_result = type(self).__gt__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return op_result or self == other
+
+def _le_from_gt(self, other, NotImplemented=NotImplemented):
+    'Return a <= b.  Computed by @total_ordering from (not a > b).'
+    op_result = type(self).__gt__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result
+
+def _le_from_ge(self, other, NotImplemented=NotImplemented):
+    'Return a <= b.  Computed by @total_ordering from (not a >= b) or (a == b).'
+    op_result = type(self).__ge__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result or self == other
+
+def _gt_from_ge(self, other, NotImplemented=NotImplemented):
+    'Return a > b.  Computed by @total_ordering from (a >= b) and (a != b).'
+    op_result = type(self).__ge__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return op_result and self != other
+
+def _lt_from_ge(self, other, NotImplemented=NotImplemented):
+    'Return a < b.  Computed by @total_ordering from (not a >= b).'
+    op_result = type(self).__ge__(self, other)
+    if op_result is NotImplemented:
+        return op_result
+    return not op_result
+
+_convert = {
+    '__lt__': [('__gt__', _gt_from_lt),
+               ('__le__', _le_from_lt),
+               ('__ge__', _ge_from_lt)],
+    '__le__': [('__ge__', _ge_from_le),
+               ('__lt__', _lt_from_le),
+               ('__gt__', _gt_from_le)],
+    '__gt__': [('__lt__', _lt_from_gt),
+               ('__ge__', _ge_from_gt),
+               ('__le__', _le_from_gt)],
+    '__ge__': [('__le__', _le_from_ge),
+               ('__gt__', _gt_from_ge),
+               ('__lt__', _lt_from_ge)]
+}
+
+def total_ordering(cls):
+    """Class decorator that fills in missing ordering methods"""
+    # Find user-defined comparisons (not those inherited from object).
+    roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
+    if not roots:
+        raise ValueError('must define at least one ordering operation: < > <= >=')
+    root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
+    for opname, opfunc in _convert[root]:
+        if opname not in roots:
+            opfunc.__name__ = opname
+            setattr(cls, opname, opfunc)
+    return cls
+
+
+################################################################################
+### cmp_to_key() function converter
+################################################################################
+
+def cmp_to_key(mycmp):
+    """Convert a cmp= function into a key= function"""
+    class K(object):
+        __slots__ = ['obj']
+        def __init__(self, obj):
+            self.obj = obj
+        def __lt__(self, other):
+            return mycmp(self.obj, other.obj) < 0
+        def __gt__(self, other):
+            return mycmp(self.obj, other.obj) > 0
+        def __eq__(self, other):
+            return mycmp(self.obj, other.obj) == 0
+        def __le__(self, other):
+            return mycmp(self.obj, other.obj) <= 0
+        def __ge__(self, other):
+            return mycmp(self.obj, other.obj) >= 0
+        __hash__ = None
+    return K
+
+try:
+    from _functools import cmp_to_key
+except ImportError:
+    pass
+
+
+################################################################################
+### reduce() sequence to a single item
+################################################################################
+
+_initial_missing = object()
+
+def reduce(function, sequence, initial=_initial_missing):
+    """
+    reduce(function, iterable[, initial]) -> value
+
+    Apply a function of two arguments cumulatively to the items of a sequence
+    or iterable, from left to right, so as to reduce the iterable to a single
+    value.  For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
+    ((((1+2)+3)+4)+5).  If initial is present, it is placed before the items
+    of the iterable in the calculation, and serves as a default when the
+    iterable is empty.
+    """
+
+    it = iter(sequence)
+
+    if initial is _initial_missing:
+        try:
+            value = next(it)
+        except StopIteration:
+            raise TypeError(
+                "reduce() of empty iterable with no initial value") from None
+    else:
+        value = initial
+
+    for element in it:
+        value = function(value, element)
+
+    return value
+
+try:
+    from _functools import reduce
+except ImportError:
+    pass
+
+
+################################################################################
+### partial() argument application
+################################################################################
+
+# Purely functional, no descriptor behaviour
+class partial:
+    """New function with partial application of the given arguments
+    and keywords.
+    """
+
+    __slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
+
+    def __new__(cls, func, /, *args, **keywords):
+        if not callable(func):
+            raise TypeError("the first argument must be callable")
+
+        if hasattr(func, "func"):
+            args = func.args + args
+            keywords = {**func.keywords, **keywords}
+            func = func.func
+
+        self = super(partial, cls).__new__(cls)
+
+        self.func = func
+        self.args = args
+        self.keywords = keywords
+        return self
+
+    def __call__(self, /, *args, **keywords):
+        keywords = {**self.keywords, **keywords}
+        return self.func(*self.args, *args, **keywords)
+
+    @recursive_repr()
+    def __repr__(self):
+        qualname = type(self).__qualname__
+        args = [repr(self.func)]
+        args.extend(repr(x) for x in self.args)
+        args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
+        if type(self).__module__ == "functools":
+            return f"functools.{qualname}({', '.join(args)})"
+        return f"{qualname}({', '.join(args)})"
+
+    def __reduce__(self):
+        return type(self), (self.func,), (self.func, self.args,
+               self.keywords or None, self.__dict__ or None)
+
+    def __setstate__(self, state):
+        if not isinstance(state, tuple):
+            raise TypeError("argument to __setstate__ must be a tuple")
+        if len(state) != 4:
+            raise TypeError(f"expected 4 items in state, got {len(state)}")
+        func, args, kwds, namespace = state
+        if (not callable(func) or not isinstance(args, tuple) or
+           (kwds is not None and not isinstance(kwds, dict)) or
+           (namespace is not None and not isinstance(namespace, dict))):
+            raise TypeError("invalid partial state")
+
+        args = tuple(args) # just in case it's a subclass
+        if kwds is None:
+            kwds = {}
+        elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
+            kwds = dict(kwds)
+        if namespace is None:
+            namespace = {}
+
+        self.__dict__ = namespace
+        self.func = func
+        self.args = args
+        self.keywords = kwds
+
+try:
+    from _functools import partial
+except ImportError:
+    pass
+
+# Descriptor version
+class partialmethod(object):
+    """Method descriptor with partial application of the given arguments
+    and keywords.
+
+    Supports wrapping existing descriptors and handles non-descriptor
+    callables as instance methods.
+    """
+
+    def __init__(self, func, /, *args, **keywords):
+        if not callable(func) and not hasattr(func, "__get__"):
+            raise TypeError("{!r} is not callable or a descriptor"
+                                 .format(func))
+
+        # func could be a descriptor like classmethod which isn't callable,
+        # so we can't inherit from partial (it verifies func is callable)
+        if isinstance(func, partialmethod):
+            # flattening is mandatory in order to place cls/self before all
+            # other arguments
+            # it's also more efficient since only one function will be called
+            self.func = func.func
+            self.args = func.args + args
+            self.keywords = {**func.keywords, **keywords}
+        else:
+            self.func = func
+            self.args = args
+            self.keywords = keywords
+
+    def __repr__(self):
+        args = ", ".join(map(repr, self.args))
+        keywords = ", ".join("{}={!r}".format(k, v)
+                                 for k, v in self.keywords.items())
+        format_string = "{module}.{cls}({func}, {args}, {keywords})"
+        return format_string.format(module=self.__class__.__module__,
+                                    cls=self.__class__.__qualname__,
+                                    func=self.func,
+                                    args=args,
+                                    keywords=keywords)
+
+    def _make_unbound_method(self):
+        def _method(cls_or_self, /, *args, **keywords):
+            keywords = {**self.keywords, **keywords}
+            return self.func(cls_or_self, *self.args, *args, **keywords)
+        _method.__isabstractmethod__ = self.__isabstractmethod__
+        _method._partialmethod = self
+        return _method
+
+    def __get__(self, obj, cls=None):
+        get = getattr(self.func, "__get__", None)
+        result = None
+        if get is not None:
+            new_func = get(obj, cls)
+            if new_func is not self.func:
+                # Assume __get__ returning something new indicates the
+                # creation of an appropriate callable
+                result = partial(new_func, *self.args, **self.keywords)
+                try:
+                    result.__self__ = new_func.__self__
+                except AttributeError:
+                    pass
+        if result is None:
+            # If the underlying descriptor didn't do anything, treat this
+            # like an instance method
+            result = self._make_unbound_method().__get__(obj, cls)
+        return result
+
+    @property
+    def __isabstractmethod__(self):
+        return getattr(self.func, "__isabstractmethod__", False)
+
+    __class_getitem__ = classmethod(GenericAlias)
+
+
+# Helper functions
+
+def _unwrap_partial(func):
+    while isinstance(func, partial):
+        func = func.func
+    return func
+
+################################################################################
+### LRU Cache function decorator
+################################################################################
+
+_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
+
+class _HashedSeq(list):
+    """ This class guarantees that hash() will be called no more than once
+        per element.  This is important because the lru_cache() will hash
+        the key multiple times on a cache miss.
+
+    """
+
+    __slots__ = 'hashvalue'
+
+    def __init__(self, tup, hash=hash):
+        self[:] = tup
+        self.hashvalue = hash(tup)
+
+    def __hash__(self):
+        return self.hashvalue
+
+def _make_key(args, kwds, typed,
+             kwd_mark = (object(),),
+             fasttypes = {int, str},
+             tuple=tuple, type=type, len=len):
+    """Make a cache key from optionally typed positional and keyword arguments
+
+    The key is constructed in a way that is flat as possible rather than
+    as a nested structure that would take more memory.
+
+    If there is only a single argument and its data type is known to cache
+    its hash value, then that argument is returned without a wrapper.  This
+    saves space and improves lookup speed.
+
+    """
+    # All of code below relies on kwds preserving the order input by the user.
+    # Formerly, we sorted() the kwds before looping.  The new way is *much*
+    # faster; however, it means that f(x=1, y=2) will now be treated as a
+    # distinct call from f(y=2, x=1) which will be cached separately.
+    key = args
+    if kwds:
+        key += kwd_mark
+        for item in kwds.items():
+            key += item
+    if typed:
+        key += tuple(type(v) for v in args)
+        if kwds:
+            key += tuple(type(v) for v in kwds.values())
+    elif len(key) == 1 and type(key[0]) in fasttypes:
+        return key[0]
+    return _HashedSeq(key)
+
+def lru_cache(maxsize=128, typed=False):
+    """Least-recently-used cache decorator.
+
+    If *maxsize* is set to None, the LRU features are disabled and the cache
+    can grow without bound.
+
+    If *typed* is True, arguments of different types will be cached separately.
+    For example, f(3.0) and f(3) will be treated as distinct calls with
+    distinct results.
+
+    Arguments to the cached function must be hashable.
+
+    View the cache statistics named tuple (hits, misses, maxsize, currsize)
+    with f.cache_info().  Clear the cache and statistics with f.cache_clear().
+    Access the underlying function with f.__wrapped__.
+
+    See:  https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
+
+    """
+
+    # Users should only access the lru_cache through its public API:
+    #       cache_info, cache_clear, and f.__wrapped__
+    # The internals of the lru_cache are encapsulated for thread safety and
+    # to allow the implementation to change (including a possible C version).
+
+    if isinstance(maxsize, int):
+        # Negative maxsize is treated as 0
+        if maxsize < 0:
+            maxsize = 0
+    elif callable(maxsize) and isinstance(typed, bool):
+        # The user_function was passed in directly via the maxsize argument
+        user_function, maxsize = maxsize, 128
+        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
+        wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
+        return update_wrapper(wrapper, user_function)
+    elif maxsize is not None:
+        raise TypeError(
+            'Expected first argument to be an integer, a callable, or None')
+
+    def decorating_function(user_function):
+        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
+        wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
+        return update_wrapper(wrapper, user_function)
+
+    return decorating_function
+
+def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
+    # Constants shared by all lru cache instances:
+    sentinel = object()          # unique object used to signal cache misses
+    make_key = _make_key         # build a key from the function arguments
+    PREV, NEXT, KEY, RESULT = 0, 1, 2, 3   # names for the link fields
+
+    cache = {}
+    hits = misses = 0
+    full = False
+    cache_get = cache.get    # bound method to lookup a key or return None
+    cache_len = cache.__len__  # get cache size without calling len()
+    lock = RLock()           # because linkedlist updates aren't threadsafe
+    root = []                # root of the circular doubly linked list
+    root[:] = [root, root, None, None]     # initialize by pointing to self
+
+    if maxsize == 0:
+
+        def wrapper(*args, **kwds):
+            # No caching -- just a statistics update
+            nonlocal misses
+            misses += 1
+            result = user_function(*args, **kwds)
+            return result
+
+    elif maxsize is None:
+
+        def wrapper(*args, **kwds):
+            # Simple caching without ordering or size limit
+            nonlocal hits, misses
+            key = make_key(args, kwds, typed)
+            result = cache_get(key, sentinel)
+            if result is not sentinel:
+                hits += 1
+                return result
+            misses += 1
+            result = user_function(*args, **kwds)
+            cache[key] = result
+            return result
+
+    else:
+
+        def wrapper(*args, **kwds):
+            # Size limited caching that tracks accesses by recency
+            nonlocal root, hits, misses, full
+            key = make_key(args, kwds, typed)
+            with lock:
+                link = cache_get(key)
+                if link is not None:
+                    # Move the link to the front of the circular queue
+                    link_prev, link_next, _key, result = link
+                    link_prev[NEXT] = link_next
+                    link_next[PREV] = link_prev
+                    last = root[PREV]
+                    last[NEXT] = root[PREV] = link
+                    link[PREV] = last
+                    link[NEXT] = root
+                    hits += 1
+                    return result
+                misses += 1
+            result = user_function(*args, **kwds)
+            with lock:
+                if key in cache:
+                    # Getting here means that this same key was added to the
+                    # cache while the lock was released.  Since the link
+                    # update is already done, we need only return the
+                    # computed result and update the count of misses.
+                    pass
+                elif full:
+                    # Use the old root to store the new key and result.
+                    oldroot = root
+                    oldroot[KEY] = key
+                    oldroot[RESULT] = result
+                    # Empty the oldest link and make it the new root.
+                    # Keep a reference to the old key and old result to
+                    # prevent their ref counts from going to zero during the
+                    # update. That will prevent potentially arbitrary object
+                    # clean-up code (i.e. __del__) from running while we're
+                    # still adjusting the links.
+                    root = oldroot[NEXT]
+                    oldkey = root[KEY]
+                    oldresult = root[RESULT]
+                    root[KEY] = root[RESULT] = None
+                    # Now update the cache dictionary.
+                    del cache[oldkey]
+                    # Save the potentially reentrant cache[key] assignment
+                    # for last, after the root and links have been put in
+                    # a consistent state.
+                    cache[key] = oldroot
+                else:
+                    # Put result in a new link at the front of the queue.
+                    last = root[PREV]
+                    link = [last, root, key, result]
+                    last[NEXT] = root[PREV] = cache[key] = link
+                    # Use the cache_len bound method instead of the len() function
+                    # which could potentially be wrapped in an lru_cache itself.
+                    full = (cache_len() >= maxsize)
+            return result
+
+    def cache_info():
+        """Report cache statistics"""
+        with lock:
+            return _CacheInfo(hits, misses, maxsize, cache_len())
+
+    def cache_clear():
+        """Clear the cache and cache statistics"""
+        nonlocal hits, misses, full
+        with lock:
+            cache.clear()
+            root[:] = [root, root, None, None]
+            hits = misses = 0
+            full = False
+
+    wrapper.cache_info = cache_info
+    wrapper.cache_clear = cache_clear
+    return wrapper
+
+try:
+    from _functools import _lru_cache_wrapper
+except ImportError:
+    pass
+
+
+################################################################################
+### cache -- simplified access to the infinity cache
+################################################################################
+
+def cache(user_function, /):
+    'Simple lightweight unbounded cache.  Sometimes called "memoize".'
+    return lru_cache(maxsize=None)(user_function)
+
+
+################################################################################
+### singledispatch() - single-dispatch generic function decorator
+################################################################################
+
+def _c3_merge(sequences):
+    """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
+
+    Adapted from https://www.python.org/download/releases/2.3/mro/.
+
+    """
+    result = []
+    while True:
+        sequences = [s for s in sequences if s]   # purge empty sequences
+        if not sequences:
+            return result
+        for s1 in sequences:   # find merge candidates among seq heads
+            candidate = s1[0]
+            for s2 in sequences:
+                if candidate in s2[1:]:
+                    candidate = None
+                    break      # reject the current head, it appears later
+            else:
+                break
+        if candidate is None:
+            raise RuntimeError("Inconsistent hierarchy")
+        result.append(candidate)
+        # remove the chosen candidate
+        for seq in sequences:
+            if seq[0] == candidate:
+                del seq[0]
+
+def _c3_mro(cls, abcs=None):
+    """Computes the method resolution order using extended C3 linearization.
+
+    If no *abcs* are given, the algorithm works exactly like the built-in C3
+    linearization used for method resolution.
+
+    If given, *abcs* is a list of abstract base classes that should be inserted
+    into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
+    result. The algorithm inserts ABCs where their functionality is introduced,
+    i.e. issubclass(cls, abc) returns True for the class itself but returns
+    False for all its direct base classes. Implicit ABCs for a given class
+    (either registered or inferred from the presence of a special method like
+    __len__) are inserted directly after the last ABC explicitly listed in the
+    MRO of said class. If two implicit ABCs end up next to each other in the
+    resulting MRO, their ordering depends on the order of types in *abcs*.
+
+    """
+    for i, base in enumerate(reversed(cls.__bases__)):
+        if hasattr(base, '__abstractmethods__'):
+            boundary = len(cls.__bases__) - i
+            break   # Bases up to the last explicit ABC are considered first.
+    else:
+        boundary = 0
+    abcs = list(abcs) if abcs else []
+    explicit_bases = list(cls.__bases__[:boundary])
+    abstract_bases = []
+    other_bases = list(cls.__bases__[boundary:])
+    for base in abcs:
+        if issubclass(cls, base) and not any(
+                issubclass(b, base) for b in cls.__bases__
+            ):
+            # If *cls* is the class that introduces behaviour described by
+            # an ABC *base*, insert said ABC to its MRO.
+            abstract_bases.append(base)
+    for base in abstract_bases:
+        abcs.remove(base)
+    explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
+    abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
+    other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
+    return _c3_merge(
+        [[cls]] +
+        explicit_c3_mros + abstract_c3_mros + other_c3_mros +
+        [explicit_bases] + [abstract_bases] + [other_bases]
+    )
+
+def _compose_mro(cls, types):
+    """Calculates the method resolution order for a given class *cls*.
+
+    Includes relevant abstract base classes (with their respective bases) from
+    the *types* iterable. Uses a modified C3 linearization algorithm.
+
+    """
+    bases = set(cls.__mro__)
+    # Remove entries which are already present in the __mro__ or unrelated.
+    def is_related(typ):
+        return (typ not in bases and hasattr(typ, '__mro__')
+                                 and not isinstance(typ, GenericAlias)
+                                 and issubclass(cls, typ))
+    types = [n for n in types if is_related(n)]
+    # Remove entries which are strict bases of other entries (they will end up
+    # in the MRO anyway.
+    def is_strict_base(typ):
+        for other in types:
+            if typ != other and typ in other.__mro__:
+                return True
+        return False
+    types = [n for n in types if not is_strict_base(n)]
+    # Subclasses of the ABCs in *types* which are also implemented by
+    # *cls* can be used to stabilize ABC ordering.
+    type_set = set(types)
+    mro = []
+    for typ in types:
+        found = []
+        for sub in typ.__subclasses__():
+            if sub not in bases and issubclass(cls, sub):
+                found.append([s for s in sub.__mro__ if s in type_set])
+        if not found:
+            mro.append(typ)
+            continue
+        # Favor subclasses with the biggest number of useful bases
+        found.sort(key=len, reverse=True)
+        for sub in found:
+            for subcls in sub:
+                if subcls not in mro:
+                    mro.append(subcls)
+    return _c3_mro(cls, abcs=mro)
+
+def _find_impl(cls, registry):
+    """Returns the best matching implementation from *registry* for type *cls*.
+
+    Where there is no registered implementation for a specific type, its method
+    resolution order is used to find a more generic implementation.
+
+    Note: if *registry* does not contain an implementation for the base
+    *object* type, this function may return None.
+
+    """
+    mro = _compose_mro(cls, registry.keys())
+    match = None
+    for t in mro:
+        if match is not None:
+            # If *match* is an implicit ABC but there is another unrelated,
+            # equally matching implicit ABC, refuse the temptation to guess.
+            if (t in registry and t not in cls.__mro__
+                              and match not in cls.__mro__
+                              and not issubclass(match, t)):
+                raise RuntimeError("Ambiguous dispatch: {} or {}".format(
+                    match, t))
+            break
+        if t in registry:
+            match = t
+    return registry.get(match)
+
+def singledispatch(func):
+    """Single-dispatch generic function decorator.
+
+    Transforms a function into a generic function, which can have different
+    behaviours depending upon the type of its first argument. The decorated
+    function acts as the default implementation, and additional
+    implementations can be registered using the register() attribute of the
+    generic function.
+    """
+    # There are many programs that use functools without singledispatch, so we
+    # trade-off making singledispatch marginally slower for the benefit of
+    # making start-up of such applications slightly faster.
+    import types, weakref
+
+    registry = {}
+    dispatch_cache = weakref.WeakKeyDictionary()
+    cache_token = None
+
+    def dispatch(cls):
+        """generic_func.dispatch(cls) -> <function implementation>
+
+        Runs the dispatch algorithm to return the best available implementation
+        for the given *cls* registered on *generic_func*.
+
+        """
+        nonlocal cache_token
+        if cache_token is not None:
+            current_token = get_cache_token()
+            if cache_token != current_token:
+                dispatch_cache.clear()
+                cache_token = current_token
+        try:
+            impl = dispatch_cache[cls]
+        except KeyError:
+            try:
+                impl = registry[cls]
+            except KeyError:
+                impl = _find_impl(cls, registry)
+            dispatch_cache[cls] = impl
+        return impl
+
+    def _is_valid_dispatch_type(cls):
+        return isinstance(cls, type) and not isinstance(cls, GenericAlias)
+
+    def register(cls, func=None):
+        """generic_func.register(cls, func) -> func
+
+        Registers a new implementation for the given *cls* on a *generic_func*.
+
+        """
+        nonlocal cache_token
+        if _is_valid_dispatch_type(cls):
+            if func is None:
+                return lambda f: register(cls, f)
+        else:
+            if func is not None:
+                raise TypeError(
+                    f"Invalid first argument to `register()`. "
+                    f"{cls!r} is not a class."
+                )
+            ann = getattr(cls, '__annotations__', {})
+            if not ann:
+                raise TypeError(
+                    f"Invalid first argument to `register()`: {cls!r}. "
+                    f"Use either `@register(some_class)` or plain `@register` "
+                    f"on an annotated function."
+                )
+            func = cls
+
+            # only import typing if annotation parsing is necessary
+            from typing import get_type_hints
+            argname, cls = next(iter(get_type_hints(func).items()))
+            if not _is_valid_dispatch_type(cls):
+                raise TypeError(
+                    f"Invalid annotation for {argname!r}. "
+                    f"{cls!r} is not a class."
+                )
+
+        registry[cls] = func
+        if cache_token is None and hasattr(cls, '__abstractmethods__'):
+            cache_token = get_cache_token()
+        dispatch_cache.clear()
+        return func
+
+    def wrapper(*args, **kw):
+        if not args:
+            raise TypeError(f'{funcname} requires at least '
+                            '1 positional argument')
+
+        return dispatch(args[0].__class__)(*args, **kw)
+
+    funcname = getattr(func, '__name__', 'singledispatch function')
+    registry[object] = func
+    wrapper.register = register
+    wrapper.dispatch = dispatch
+    wrapper.registry = types.MappingProxyType(registry)
+    wrapper._clear_cache = dispatch_cache.clear
+    update_wrapper(wrapper, func)
+    return wrapper
+
+
+# Descriptor version
+class singledispatchmethod:
+    """Single-dispatch generic method descriptor.
+
+    Supports wrapping existing descriptors and handles non-descriptor
+    callables as instance methods.
+    """
+
+    def __init__(self, func):
+        if not callable(func) and not hasattr(func, "__get__"):
+            raise TypeError(f"{func!r} is not callable or a descriptor")
+
+        self.dispatcher = singledispatch(func)
+        self.func = func
+
+    def register(self, cls, method=None):
+        """generic_method.register(cls, func) -> func
+
+        Registers a new implementation for the given *cls* on a *generic_method*.
+        """
+        return self.dispatcher.register(cls, func=method)
+
+    def __get__(self, obj, cls=None):
+        def _method(*args, **kwargs):
+            method = self.dispatcher.dispatch(args[0].__class__)
+            return method.__get__(obj, cls)(*args, **kwargs)
+
+        _method.__isabstractmethod__ = self.__isabstractmethod__
+        _method.register = self.register
+        update_wrapper(_method, self.func)
+        return _method
+
+    @property
+    def __isabstractmethod__(self):
+        return getattr(self.func, '__isabstractmethod__', False)
+
+
+################################################################################
+### cached_property() - computed once per instance, cached as attribute
+################################################################################
+
+_NOT_FOUND = object()
+
+
+class cached_property:
+    def __init__(self, func):
+        self.func = func
+        self.attrname = None
+        self.__doc__ = func.__doc__
+        self.lock = RLock()
+
+    def __set_name__(self, owner, name):
+        if self.attrname is None:
+            self.attrname = name
+        elif name != self.attrname:
+            raise TypeError(
+                "Cannot assign the same cached_property to two different names "
+                f"({self.attrname!r} and {name!r})."
+            )
+
+    def __get__(self, instance, owner=None):
+        if instance is None:
+            return self
+        if self.attrname is None:
+            raise TypeError(
+                "Cannot use cached_property instance without calling __set_name__ on it.")
+        try:
+            cache = instance.__dict__
+        except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
+            msg = (
+                f"No '__dict__' attribute on {type(instance).__name__!r} "
+                f"instance to cache {self.attrname!r} property."
+            )
+            raise TypeError(msg) from None
+        val = cache.get(self.attrname, _NOT_FOUND)
+        if val is _NOT_FOUND:
+            with self.lock:
+                # check if another thread filled cache while we awaited lock
+                val = cache.get(self.attrname, _NOT_FOUND)
+                if val is _NOT_FOUND:
+                    val = self.func(instance)
+                    try:
+                        cache[self.attrname] = val
+                    except TypeError:
+                        msg = (
+                            f"The '__dict__' attribute on {type(instance).__name__!r} instance "
+                            f"does not support item assignment for caching {self.attrname!r} property."
+                        )
+                        raise TypeError(msg) from None
+        return val
+
+    __class_getitem__ = classmethod(GenericAlias)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html new file mode 100644 index 00000000..6a54afd2 --- /dev/null +++ b/docs/_build/html/_modules/index.html @@ -0,0 +1,150 @@ + + + + + + Overview: module code — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/manage.html b/docs/_build/html/_modules/manage.html new file mode 100644 index 00000000..cc83619a --- /dev/null +++ b/docs/_build/html/_modules/manage.html @@ -0,0 +1,123 @@ + + + + + + manage — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for manage

+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+
[docs]def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv)
+ + +if __name__ == '__main__': + main() +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/admin.html b/docs/_build/html/_modules/user_management/admin.html new file mode 100644 index 00000000..050b1834 --- /dev/null +++ b/docs/_build/html/_modules/user_management/admin.html @@ -0,0 +1,175 @@ + + + + + + user_management.admin — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.admin

+from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
+from django.utils.translation import gettext_lazy as _
+from django.contrib import admin
+
+from user_management.models import User, Profile
+
+
+
[docs]class ProfileInlineAdmin(admin.TabularInline): + model = Profile
+ + +
[docs]class UserAdmin(BaseUserAdmin): + list_display = [ + 'username', + 'get_full_name', + 'is_active', + 'is_superuser', + 'is_staff', + 'created_at', + 'updated_at', + 'last_login' + ] + + list_filter = [ + 'is_active', + 'is_superuser', + 'is_staff', + 'created_at', + 'updated_at', + 'last_login', + ] + + search_fields = [ + 'username', + ] + + actions = [ + 'delete_selected', + ] + + fieldsets = ( + (None, {'fields': ('username', 'password')}), + (_("Access Control"), {'fields': ('is_active', 'is_superuser', 'is_staff', 'groups')}), + (_("Important Date"), {'fields': ('created_at', 'updated_at', 'last_login')}), + ) + + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('username', 'password1', 'password2')} + ), + (_("Access Control"), {'fields': ('is_active', 'is_superuser', 'is_staff', 'groups')}), + ) + + readonly_fields = [ + 'created_at', + 'updated_at', + 'last_login', + ] + + inlines = ( + ProfileInlineAdmin, + ) + +
[docs] def get_queryset(self, request): + return self.model.objects.select_related('profile_user')
+ +
[docs] def get_full_name(self, obj): + return obj.profile_user.get_full_name
+ + get_full_name.short_description = _('Full Name')
+ + +admin.site.register(User, UserAdmin) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/apps.html b/docs/_build/html/_modules/user_management/apps.html new file mode 100644 index 00000000..a0ca392d --- /dev/null +++ b/docs/_build/html/_modules/user_management/apps.html @@ -0,0 +1,112 @@ + + + + + + user_management.apps — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.apps

+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+
[docs]class UserManagementConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user_management' + verbose_name = _('User Management') + +
[docs] def ready(self): + import user_management.signals
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/managers.html b/docs/_build/html/_modules/user_management/managers.html new file mode 100644 index 00000000..848ed6e9 --- /dev/null +++ b/docs/_build/html/_modules/user_management/managers.html @@ -0,0 +1,133 @@ + + + + + + user_management.managers — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.managers

+from django.contrib.auth.base_user import BaseUserManager
+
+
+
[docs]class UserManager(BaseUserManager): + """Manager class for the 'user' model""" + use_in_migrations = True + +
[docs] def create_user(self, password, **extra_fields): + """This will create a new regular user""" + user = self.model(**extra_fields) + user.is_superuser = False + user.set_password(password) + user.save(using=self._db) + return user
+ +
[docs] def create_staff(self, password, **extra_fields): + """This will create a new staff user""" + user = self.model(**extra_fields) + user.is_staff = True + user.is_superuser = False + user.set_password(password) + user.save(using=self._db) + return user
+ +
[docs] def create_superuser(self, password, **extra_fields): + """This will create a new super admin user""" + user = self.model(**extra_fields) + user.is_staff = True + user.is_superuser = True + user.set_password(password) + user.save(using=self._db) + return user
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/models/profile.html b/docs/_build/html/_modules/user_management/models/profile.html new file mode 100644 index 00000000..77a049be --- /dev/null +++ b/docs/_build/html/_modules/user_management/models/profile.html @@ -0,0 +1,147 @@ + + + + + + user_management.models.profile — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.models.profile

+from django.utils.translation import gettext_lazy as _
+from django.contrib.auth import get_user_model
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+from extensions.choices import GenderChoices
+
+
+
[docs]class Profile(AbstractCreatAtUpdateAt, models.Model): + user = models.OneToOneField( + get_user_model(), + verbose_name=_('User'), + on_delete=models.CASCADE, + primary_key=True, + related_name='profile_user' + ) + first_name = models.CharField( + verbose_name=_('First Name'), + max_length=200, + blank=True + ) + last_name = models.CharField( + verbose_name=_('Last Name'), + max_length=200, + blank=True + ) + gender = models.PositiveSmallIntegerField( + verbose_name=_('Gender'), + choices=GenderChoices.choices, + blank=True, + null=True + ) + + class Meta: + app_label = 'user_management' + db_table = 'user_management_profiles' + verbose_name = _('Profile') + verbose_name_plural = _('Profiles') + ordering = ['user_id'] + + def __str__(self) -> str: + return f"{self.first_name} {self.last_name}" if self.first_name and self.last_name else self.user.__str__() + + @property + def get_full_name(self) -> str: + return f"{self.first_name} {self.last_name}" if self.first_name and self.last_name else ''
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/models/user.html b/docs/_build/html/_modules/user_management/models/user.html new file mode 100644 index 00000000..f2fd46ff --- /dev/null +++ b/docs/_build/html/_modules/user_management/models/user.html @@ -0,0 +1,149 @@ + + + + + + user_management.models.user — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.models.user

+from django.contrib.auth.base_user import AbstractBaseUser
+from django.contrib.auth.models import PermissionsMixin
+from django.utils.translation import gettext_lazy as _
+from django.db import models
+
+from extensions.abstract_models import AbstractCreatAtUpdateAt
+from user_management.managers import UserManager
+
+
+
[docs]class User(AbstractBaseUser, AbstractCreatAtUpdateAt, PermissionsMixin): + """ + Custom user model to authenticate user by email address, + Verification of users by sending an activation link to their email is not implemented. + """ + username = models.EmailField( + verbose_name=_('Username'), + unique=True, + db_index=True + ) + is_staff = models.BooleanField( + verbose_name=_('Is Staff'), + default=False + ) + # This should change to true after sending the activation email, but is ignored here. + is_active = models.BooleanField( + verbose_name=_('Is Active'), + default=True + ) + + objects = UserManager() + + USERNAME_FIELD = 'username' + EMAIL_FIELD = 'username' + REQUIRED_FIELDS = [] + + class Meta: + app_label = 'user_management' + db_table = 'user_management_users' + verbose_name = _('User') + verbose_name_plural = _('Users') + ordering = ['id'] + +
[docs] def save(self, *args, **kwargs): + self.full_clean() + super(User, self).save(*args, **kwargs)
+ + def __str__(self) -> str: + return self.username.__str__()
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/serializers/group.html b/docs/_build/html/_modules/user_management/serializers/group.html new file mode 100644 index 00000000..3c99372e --- /dev/null +++ b/docs/_build/html/_modules/user_management/serializers/group.html @@ -0,0 +1,170 @@ + + + + + + user_management.serializers.group — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.serializers.group

+from django.contrib.auth.models import Group, Permission
+
+from rest_framework.relations import PrimaryKeyRelatedField
+from rest_framework.serializers import ModelSerializer
+
+from user_management.serializers import PermissionSerializer
+
+
+
[docs]class GroupSerializer(ModelSerializer): + """ + Serializer for the 'Group' model + """ + + permissions = PermissionSerializer(many=True, read_only=True) + + class Meta: + model = Group + fields = ('id', 'name', 'permissions',)
+ + +
[docs]class CreateGroupSerializer(ModelSerializer): + """ + Serializer for creating a new 'Group' instance + """ + + permissions = PermissionSerializer(many=True, read_only=True) + permission_ids = PrimaryKeyRelatedField( + many=True, + write_only=True, + queryset=Permission.objects.all() + ) + + class Meta: + model = Group + fields = ('id', 'name', 'permissions', 'permission_ids',) + +
[docs] def create(self, validated_data): + """ + Create a new instance + :param validated_data: A dict of fields + :return: A new instance + """ + + permission_ids = validated_data.pop('permission_ids') + group = Group.objects.create(**validated_data) + group.permissions.set(permission_ids) + + return group
+ +
[docs] def update(self, instance, validated_data): + """ + Update exists instance + :param instance: Current instance + :param validated_data: A dict of fields + :return: Current instance with changes + """ + + # Get the permission ids + permission_ids = validated_data.pop('permission_ids', [item.id for item in instance.permissions.all()]) + + # Update the 'name' field + instance.name = validated_data.get('name', instance.name) + instance.save() + + # Update the permissions + instance.permissions.clear() + instance.permissions.add(*permission_ids) + + return instance
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/serializers/permission.html b/docs/_build/html/_modules/user_management/serializers/permission.html new file mode 100644 index 00000000..9bc94c63 --- /dev/null +++ b/docs/_build/html/_modules/user_management/serializers/permission.html @@ -0,0 +1,114 @@ + + + + + + user_management.serializers.permission — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.serializers.permission

+from django.contrib.auth.models import Permission
+
+from rest_framework.serializers import ModelSerializer
+
+
+
[docs]class PermissionSerializer(ModelSerializer): + """ + Serializer for the 'Permission' model + """ + + class Meta: + model = Permission + fields = '__all__'
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/serializers/profile.html b/docs/_build/html/_modules/user_management/serializers/profile.html new file mode 100644 index 00000000..183b8adf --- /dev/null +++ b/docs/_build/html/_modules/user_management/serializers/profile.html @@ -0,0 +1,120 @@ + + + + + + user_management.serializers.profile — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.serializers.profile

+from rest_framework.serializers import ModelSerializer
+
+from user_management.models import Profile
+
+
+
[docs]class ProfileSerializer(ModelSerializer): + """ + Serializer for the 'Profile' model + """ + + class Meta: + model = Profile + fields = '__all__' + read_only_fields = ('user', 'created_at', 'updated_at') + +
[docs] def to_representation(self, obj): + data = super(ProfileSerializer, self).to_representation(obj) + data['gender'] = obj.get_gender_display() + return data
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/serializers/user.html b/docs/_build/html/_modules/user_management/serializers/user.html new file mode 100644 index 00000000..018d167c --- /dev/null +++ b/docs/_build/html/_modules/user_management/serializers/user.html @@ -0,0 +1,186 @@ + + + + + + user_management.serializers.user — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.serializers.user

+from django.contrib.auth.password_validation import validate_password
+from django.utils.translation import gettext_lazy as _
+from django.contrib.auth.hashers import make_password
+from django.contrib.auth import get_user_model
+from django.core.exceptions import ValidationError
+
+from rest_framework import serializers
+
+from user_management.serializers import GroupSerializer, PermissionSerializer
+
+
+
[docs]class UserSerializer(serializers.ModelSerializer): + """ + Serializer for the 'User' model + """ + + class Meta: + model = get_user_model() + fields = '__all__' + read_only_fields = ('last_login', 'created_at', 'updated_at') + extra_kwargs = { + 'password': {'write_only': True} + } + +
[docs] def validate_password(self, value: str) -> str: + try: + validate_password(value, get_user_model()) + return make_password(value) + except ValidationError as e: + raise serializers.ValidationError(e.messages)
+ +
[docs] def to_representation(self, obj): + data = super(UserSerializer, self).to_representation(obj) + data['groups'] = GroupSerializer(many=True, instance=obj.groups).data + data['user_permissions'] = PermissionSerializer(many=True, instance=obj.user_permissions).data + return data
+ + +
[docs]class UserPasswordChangeSerializer(serializers.Serializer): + """ + Serializer for user model password change + """ + + old_password = serializers.CharField(max_length=128, write_only=True, required=True) + new_password1 = serializers.CharField(max_length=128, write_only=True, required=True) + new_password2 = serializers.CharField(max_length=128, write_only=True, required=True) + +
[docs] def validate_old_password(self, value): + user = self.context['user'] + if not user.check_password(value): + raise serializers.ValidationError( + _('Your old password was entered incorrectly. Please enter it again.') + ) + return value
+ +
[docs] def validate_new_password1(self, value: str) -> str: + try: + validate_password(value, get_user_model()) + return value + except ValidationError as e: + raise serializers.ValidationError(e.messages)
+ +
[docs] def validate_new_password2(self, value: str) -> str: + try: + validate_password(value, get_user_model()) + return value + except ValidationError as e: + raise serializers.ValidationError(e.messages)
+ +
[docs] def validate(self, data): + if data['new_password1'] != data['new_password2']: + raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")}) + return data
+ +
[docs] def save(self, **kwargs): + user = self.context['user'] + user.set_password(self.validated_data['new_password2']) + user.save() + return user
+ +
[docs] def to_representation(self, obj): + data = super(UserPasswordChangeSerializer, self).to_representation(obj) + data['groups'] = GroupSerializer(many=True, instance=obj.groups).data + data['user_permissions'] = PermissionSerializer(many=True, instance=obj.user_permissions).data + return data
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/signals.html b/docs/_build/html/_modules/user_management/signals.html new file mode 100644 index 00000000..6b96db02 --- /dev/null +++ b/docs/_build/html/_modules/user_management/signals.html @@ -0,0 +1,112 @@ + + + + + + user_management.signals — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.signals

+from django.db.models.signals import post_save
+from django.dispatch import receiver
+
+from user_management.models import User, Profile
+
+
+
[docs]@receiver(post_save, sender=User) +def creating_profile(sender, instance, created, raw, using, **kwargs): + """It will create a new profile instance for the user""" + if created: + Profile.objects.create(user=instance)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/tests/common_functions.html b/docs/_build/html/_modules/user_management/tests/common_functions.html new file mode 100644 index 00000000..eceffc4b --- /dev/null +++ b/docs/_build/html/_modules/user_management/tests/common_functions.html @@ -0,0 +1,138 @@ + + + + + + user_management.tests.common_functions — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.tests.common_functions

+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+
+
+
[docs]def sample_user(**params): + """ + Create and return a sample user object + """ + defaults = { + 'username': 'user@ticketing.sample', + 'password': '1234@Pass' + } + defaults.update(params) + return get_user_model().objects.create_user(**defaults)
+ + +
[docs]def sample_superuser(**params): + """ + Create and return a sample superuser object + """ + defaults = { + 'username': 'superuser@ticketing.sample', + 'password': '1234@Pass' + } + defaults.update(params) + return get_user_model().objects.create_superuser(**defaults)
+ + +
[docs]def sample_group(**params): + """ + Create and return a sample group object + """ + defaults = { + 'name': 'Sample Group', + } + defaults.update(params) + return Group.objects.create(**defaults)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/tests/test_group_model.html b/docs/_build/html/_modules/user_management/tests/test_group_model.html new file mode 100644 index 00000000..29cf868d --- /dev/null +++ b/docs/_build/html/_modules/user_management/tests/test_group_model.html @@ -0,0 +1,138 @@ + + + + + + user_management.tests.test_group_model — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.tests.test_group_model

+from django.contrib.auth.models import Group, Permission
+from django.db.utils import IntegrityError
+from django.test import TestCase
+
+from .common_functions import sample_group
+
+
+
[docs]class GroupModelTest(TestCase): +
[docs] def test_create_group_successful(self): + """Test creating a new group is successful""" + defaults = { + 'name': 'Sample Group' + } + instance = Group.objects.create(**defaults) + self.assertEqual(instance.name, defaults['name'])
+ +
[docs] def test_create_group_with_permissions_successful(self): + """Test creating a new group with permissions is successful""" + permissions = sorted(list(Permission.objects.values_list('id', flat=True))) + defaults = { + 'name': 'Sample Group' + } + instance = Group.objects.create(**defaults) + instance.permissions.set(permissions) + self.assertEqual(instance.name, defaults['name']) + self.assertEqual(sorted([permission.id for permission in instance.permissions.all()]), permissions)
+ +
[docs] def test_creating_group_with_none_name(self): + """Test creating a new group with a none name""" + with self.assertRaises(IntegrityError): + Group.objects.create(name=None)
+ +
[docs] def test_create_new_group_with_duplicate_name_unsuccessful(self): + """Test creating a new group with duplicate name is unsuccessful""" + group = sample_group() + with self.assertRaises(IntegrityError): + Group.objects.create(name=group.name)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/tests/test_user_model.html b/docs/_build/html/_modules/user_management/tests/test_user_model.html new file mode 100644 index 00000000..9d4c10bd --- /dev/null +++ b/docs/_build/html/_modules/user_management/tests/test_user_model.html @@ -0,0 +1,152 @@ + + + + + + user_management.tests.test_user_model — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.tests.test_user_model

+from django.core.exceptions import ValidationError
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+
+from .common_functions import sample_user, sample_superuser
+
+
+
[docs]class UserModelTest(TestCase): +
[docs] def test_create_user_with_username_successful(self): + """Test creating a new user with a username is successful""" + username = 'username@ticketing.sample' + password = 'password@123' + user = get_user_model().objects.create_user( + username=username, + password=password + ) + self.assertEqual(user.username, username) + self.assertFalse(user.is_superuser) + self.assertTrue(user.is_active) + self.assertTrue(user.check_password(password))
+ +
[docs] def test_creating_user_with_none_username(self): + """Test creating a new user with a none username""" + with self.assertRaises(ValidationError): + get_user_model().objects.create_user(username=None, password='1234@passwor')
+ +
[docs] def test_creating_user_with_invalid_username(self): + """Test creating a new user with an invalid username""" + with self.assertRaises(ValidationError): + get_user_model().objects.create_user(username='abcd 1234', password='1234@passwor')
+ +
[docs] def test_creating_new_superuser(self): + """Test creating a new superuser""" + user = get_user_model().objects.create_superuser( + username='superuser@ticketing.sample', + password='1234@password' + ) + self.assertTrue(user.is_superuser) + self.assertTrue(user.is_active)
+ +
[docs] def test_create_new_user_with_duplicate_username_unsuccessful(self): + """Test creating a new user with duplicate username is unsuccessful""" + user = sample_user() + with self.assertRaises(ValidationError): + get_user_model().objects.create_user(username=user.username, password='123@pass')
+ +
[docs] def test_create_new_superuser_with_duplicate_username_unsuccessful(self): + """Test creating a new superuser with duplicate username is unsuccessful""" + user = sample_superuser() + with self.assertRaises(ValidationError): + get_user_model().objects.create_superuser(username=user.username, password='123@pass')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/views/group.html b/docs/_build/html/_modules/user_management/views/group.html new file mode 100644 index 00000000..d5dd241e --- /dev/null +++ b/docs/_build/html/_modules/user_management/views/group.html @@ -0,0 +1,137 @@ + + + + + + user_management.views.group — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.views.group

+from django.db.models import ProtectedError
+from django.contrib.auth.models import Group
+
+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ModelViewSet
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+from user_management.serializers import GroupSerializer, CreateGroupSerializer
+
+
+
[docs]class GroupViewSet(ModelViewSet): + """ + ViewSet for the 'Group' model objects + """ + serializer_class = GroupSerializer + queryset = Group.objects.all().order_by('id') + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ('name',) + search_fields = ['name'] + +
[docs] def get_serializer_class(self): + return CreateGroupSerializer if self.action in ('create', 'partial_update') else GroupSerializer
+ +
[docs] def destroy(self, request, *args, **kwargs): + users_in_this_group = self.get_object().user_set.all() + if users_in_this_group.count() > 0: + raise ProtectedError( + msg="Cannot delete this instance of model 'Group' due to protected foreign keys.", + protected_objects=users_in_this_group + ) + else: + return super().destroy(request, *args, **kwargs)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/views/permission.html b/docs/_build/html/_modules/user_management/views/permission.html new file mode 100644 index 00000000..833ee08e --- /dev/null +++ b/docs/_build/html/_modules/user_management/views/permission.html @@ -0,0 +1,124 @@ + + + + + + user_management.views.permission — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for user_management.views.permission

+from django.contrib.auth.models import Permission
+
+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ReadOnlyModelViewSet
+
+from extensions.custom_permissions import CustomDjangoModelPermission
+
+
+from user_management.serializers import PermissionSerializer
+
+
+
[docs]class PermissionViewSet(ReadOnlyModelViewSet): + """ + ViewSet for the 'Permission' model objects + """ + serializer_class = PermissionSerializer + queryset = Permission.objects.all().order_by('id') + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', 'content_type', 'codename'] + search_fields = ['name']
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/user_management/views/user.html b/docs/_build/html/_modules/user_management/views/user.html new file mode 100644 index 00000000..c6d0dfd7 --- /dev/null +++ b/docs/_build/html/_modules/user_management/views/user.html @@ -0,0 +1,196 @@ + + + + + + user_management.views.user — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for user_management.views.user

+from django.contrib.auth import get_user_model
+
+from django_filters.rest_framework import DjangoFilterBackend
+
+from rest_framework.filters import SearchFilter
+from rest_framework.viewsets import ModelViewSet
+from rest_framework.decorators import action
+from rest_framework.response import Response
+from rest_framework import status
+
+from extensions.custom_permissions import (
+    CustomDjangoModelPermission,
+    UnauthenticatedPost,
+    OwnUserPermission
+)
+
+from user_management.serializers import (
+    UserSerializer,
+    UserPasswordChangeSerializer,
+    ProfileSerializer
+)
+
+
+
[docs]class UserViewSet(ModelViewSet): + """ + ViewSet for the 'User' model objects + """ + serializer_class = UserSerializer + queryset = get_user_model().objects.select_related('profile_user').all() + permission_classes = [CustomDjangoModelPermission | UnauthenticatedPost] + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['username', 'is_active', 'is_superuser', 'is_staff'] + search_fields = ['username'] + +
[docs] @action( + detail=True, + methods=["put"], + url_path='change_password', + url_name='change_password', + permission_classes=[OwnUserPermission] + ) + def change_password(self, request, pk=None): + """ + This will use for change the current user password + :return: The current user information + """ + user = self.get_object() + serializer = UserPasswordChangeSerializer( + user, + data=request.data, + many=False, + context={ + "user": user + } + ) + serializer.is_valid(raise_exception=True) + user = serializer.save() + return Response(self.serializer_class(user).data, status=status.HTTP_200_OK)
+ +
[docs] @action( + detail=True, + methods=["get"], + url_path='user_profile', + url_name='user_profile', + permission_classes=[OwnUserPermission] + ) + def user_profile(self, request, pk=None): + """ + This will use for getting the current user profile + :return: The current user profile + """ + user = self.get_object() + return Response(data=ProfileSerializer(user.profile_user).data, status=status.HTTP_200_OK)
+ +
[docs] @action( + detail=True, + methods=["put"], + url_path='update_user_profile', + url_name='update_user_profile', + permission_classes=[OwnUserPermission] + ) + def update_user_profile(self, request, pk=None): + """ + This will use for change the current user profile + :return: The current user profile + """ + user = self.get_object() + serializer = ProfileSerializer( + user.profile_user, + data=request.data, + many=False, + ) + serializer.is_valid(raise_exception=True) + user_profile = serializer.save() + return Response(ProfileSerializer(user_profile).data, status=status.HTTP_200_OK)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/api.html b/docs/_build/html/_rst/api.html new file mode 100644 index 00000000..df9b187d --- /dev/null +++ b/docs/_build/html/_rst/api.html @@ -0,0 +1,239 @@ + + + + + + + api package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

api package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+
+
+

api.apps module

+
+
+class api.apps.ApiConfig(app_name, app_module)[source]
+

Bases: AppConfig

+
+
+default_auto_field = 'django.db.models.BigAutoField'
+
+ +
+
+name = 'api'
+
+ +
+ +
+
+

api.urls module

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/api.tests.html b/docs/_build/html/_rst/api.tests.html new file mode 100644 index 00000000..34e9bfa3 --- /dev/null +++ b/docs/_build/html/_rst/api.tests.html @@ -0,0 +1,426 @@ + + + + + + + api.tests package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

api.tests package

+
+

Submodules

+
+
+

api.tests.test_group module

+
+
+class api.tests.test_group.PrivateGroupsAPITests(methodName='runTest')[source]
+

Bases: TestCase

+

Test the authorized user groups API

+
+
+setUp() None[source]
+

Hook method for setting up the test fixture before exercising it.

+
+ +
+
+test_create_group_successful()[source]
+

Test creating a new group is successful

+
+ +
+
+test_create_group_with_duplicate_name_unsuccessful()[source]
+

Test creating a new group with duplicate name is unsuccessful

+
+ +
+
+test_create_group_with_invalid_name_unsuccessful()[source]
+

Test creating a new group with invalid name is unsuccessful

+
+ +
+
+test_create_group_with_invalid_permission_unsuccessful()[source]
+

Test creating a new group with invalid permission is unsuccessful

+
+ +
+
+test_create_group_with_permissions_successful()[source]
+

Test creating a new group with permissions is successful

+
+ +
+
+test_delete_group_successful()[source]
+

Test deleting the group is successful

+
+ +
+
+test_delete_group_unsuccessful()[source]
+

Test deleting the group is unsuccessful

+
+ +
+
+test_retrieve_groups_successful()[source]
+

Test retrieving groups is successful

+
+ +
+
+test_retrieve_single_group()[source]
+

Test retrieving a single group is successful

+
+ +
+
+test_update_group_successful()[source]
+

Test updating the group is successful

+
+ +
+
+test_update_group_unsuccessful()[source]
+

Test updating the group is unsuccessful

+
+ +
+ +
+
+class api.tests.test_group.PublicGroupsAPITests(methodName='runTest')[source]
+

Bases: TestCase

+

Test the public available groups API

+
+
+setUp() None[source]
+

Hook method for setting up the test fixture before exercising it.

+
+ +
+
+test_login_required()[source]
+

Test that login is required for retrieving permissions

+
+ +
+ +
+
+

api.tests.test_permission module

+
+
+class api.tests.test_permission.PrivatePermissionsAPITests(methodName='runTest')[source]
+

Bases: TestCase

+

Test the authorized user permissions API

+
+
+setUp() None[source]
+

Hook method for setting up the test fixture before exercising it.

+
+ +
+
+test_create_permission_not_allowed()[source]
+

Test creating a new permission is not allowed

+
+ +
+
+test_delete_permission_not_allowed()[source]
+

Test deleting the permission is not allowed

+
+ +
+
+test_retrieve_permissions()[source]
+

Test retrieving the permissions

+
+ +
+
+test_retrieve_single_permission()[source]
+

Test retrieving a single permission

+
+ +
+
+test_update_permission_not_allowed()[source]
+

Test updating the permission is not allowed

+
+ +
+ +
+
+class api.tests.test_permission.PublicPermissionsAPITests(methodName='runTest')[source]
+

Bases: TestCase

+

Test the public available permissions API

+
+
+setUp() None[source]
+

Hook method for setting up the test fixture before exercising it.

+
+ +
+
+test_login_required()[source]
+

Test that login is required for retrieving permissions

+
+ +
+ +
+
+

api.tests.test_user module

+
+
+class api.tests.test_user.PrivateUsersAPITests(methodName='runTest')[source]
+

Bases: TestCase

+

Test the authorized user users API

+
+
+setUp() None[source]
+

Hook method for setting up the test fixture before exercising it.

+
+ +
+
+test_create_superuser_successful()[source]
+

Test creating a new superuser is successful

+
+ +
+
+test_create_user_successful()[source]
+

Test creating a new user is successful

+
+ +
+
+test_create_user_with_duplicate_username_unsuccessful()[source]
+

Test creating a new user with duplicate username is unsuccessful

+
+ +
+
+test_create_user_with_group_successful()[source]
+

Test creating a new user with group is successful

+
+ +
+
+test_create_user_with_group_with_permission_to_do_something_successful()[source]
+

Test creating a new user with group with the permission to do something is successful

+
+ +
+
+test_create_user_with_group_without_permission_to_do_something_unsuccessful()[source]
+

Test creating a new user with group without the permission to do something is unsuccessful

+
+ +
+
+test_create_user_with_invalid_password_unsuccessful()[source]
+

Test creating a new user with invalid password is unsuccessful

+
+ +
+
+test_create_user_with_invalid_username_unsuccessful()[source]
+

Test creating a new user with invalid username is unsuccessful

+
+ +
+
+test_create_user_with_permission_successful()[source]
+

Test creating a new user with permission is successful

+
+ +
+
+test_delete_user_successful()[source]
+

Test deleting the user is successful

+
+ +
+
+test_delete_user_unsuccessful()[source]
+

Test deleting the user is unsuccessful

+
+ +
+
+test_retrieve_single_user_successful()[source]
+

Test retrieving a single user is successful

+
+ +
+
+test_retrieve_users_successful()[source]
+

Test retrieving users is successful

+
+ +
+
+test_update_user_successful()[source]
+

Test updating the user is successful

+
+ +
+
+test_update_user_unsuccessful()[source]
+

Test updating the user is unsuccessful

+
+ +
+ +
+
+class api.tests.test_user.PublicUsersAPITests(methodName='runTest')[source]
+

Bases: TestCase

+

Test the public available users API

+
+
+setUp() None[source]
+

Hook method for setting up the test fixture before exercising it.

+
+ +
+
+test_login_required()[source]
+

Test that login is required for retrieving permissions

+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/booking.html b/docs/_build/html/_rst/booking.html new file mode 100644 index 00000000..7ae0c9d1 --- /dev/null +++ b/docs/_build/html/_rst/booking.html @@ -0,0 +1,1141 @@ + + + + + + + booking package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

booking package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+
+
+

booking.admin module

+
+
+class booking.admin.InvoiceAdmin(model, admin_site)[source]
+

Bases: ModelAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+get_queryset(request)[source]
+

Return a QuerySet of all model instances that can be edited by the +admin site. This is used by changelist_view.

+
+ +
+
+has_add_permission(request)[source]
+

Return True if the given request has permission to add an object. +Can be overridden by the user in subclasses.

+
+ +
+
+has_change_permission(request, obj=None)[source]
+

Return True if the given request has permission to change the given +Django model instance, the default implementation doesn’t examine the +obj parameter.

+

Can be overridden by the user in subclasses. In such case it should +return True if the given request has permission to change the obj +model instance. If obj is None, this should return True if the given +request has permission to change any object of the given type.

+
+ +
+
+has_delete_permission(request, obj=None)[source]
+

Return True if the given request has permission to delete the given +Django model instance, the default implementation doesn’t examine the +obj parameter.

+

Can be overridden by the user in subclasses. In such case it should +return True if the given request has permission to delete the obj +model instance. If obj is None, this should return True if the given +request has permission to delete any object of the given type.

+
+ +
+
+list_display = ['user', 'status', 'created_at', 'updated_at']
+
+ +
+
+list_filter = ['status', 'created_at', 'updated_at']
+
+ +
+
+property media
+
+ +
+
+search_fields = ['user']
+
+ +
+ +
+
+class booking.admin.MatchAdmin(model, admin_site)[source]
+

Bases: ModelAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+get_queryset(request)[source]
+

Return a QuerySet of all model instances that can be edited by the +admin site. This is used by changelist_view.

+
+ +
+
+list_display = ['stadium', 'host_team', 'guest_team', 'start_time', 'created_at', 'updated_at']
+
+ +
+
+list_filter = ['stadium', 'host_team', 'guest_team', 'start_time', 'created_at', 'updated_at']
+
+ +
+
+property media
+
+ +
+
+search_fields = ['stadium', 'host_team', 'guest_team']
+
+ +
+ +
+
+class booking.admin.PaymentAdmin(model, admin_site)[source]
+

Bases: ModelAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+get_queryset(request)[source]
+

Return a QuerySet of all model instances that can be edited by the +admin site. This is used by changelist_view.

+
+ +
+
+has_add_permission(request)[source]
+

Return True if the given request has permission to add an object. +Can be overridden by the user in subclasses.

+
+ +
+
+has_change_permission(request, obj=None)[source]
+

Return True if the given request has permission to change the given +Django model instance, the default implementation doesn’t examine the +obj parameter.

+

Can be overridden by the user in subclasses. In such case it should +return True if the given request has permission to change the obj +model instance. If obj is None, this should return True if the given +request has permission to change any object of the given type.

+
+ +
+
+has_delete_permission(request, obj=None)[source]
+

Return True if the given request has permission to delete the given +Django model instance, the default implementation doesn’t examine the +obj parameter.

+

Can be overridden by the user in subclasses. In such case it should +return True if the given request has permission to delete the obj +model instance. If obj is None, this should return True if the given +request has permission to delete any object of the given type.

+
+ +
+
+list_display = ['invoice', 'amount', 'status', 'created_at']
+
+ +
+
+list_filter = ['status', 'created_at']
+
+ +
+
+property media
+
+ +
+
+search_fields = ['invoice', 'amount']
+
+ +
+ +
+
+class booking.admin.SectionInlineAdmin(parent_model, admin_site)[source]
+

Bases: TabularInline

+
+
+extra = 0
+
+ +
+
+property media
+
+ +
+
+min_num = 1
+
+ +
+
+model
+

alias of Section

+
+ +
+ +
+
+class booking.admin.StadiumAdmin(model, admin_site)[source]
+

Bases: ModelAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+get_queryset(request)[source]
+

Return a QuerySet of all model instances that can be edited by the +admin site. This is used by changelist_view.

+
+ +
+
+inlines = (<class 'booking.admin.SectionInlineAdmin'>,)
+
+ +
+
+list_display = ['name', 'province', 'city', 'created_at', 'updated_at']
+
+ +
+
+list_filter = ['province', 'city', 'created_at', 'updated_at']
+
+ +
+
+property media
+
+ +
+
+search_fields = ['name', 'province', 'city']
+
+ +
+ +
+
+class booking.admin.TeamAdmin(model, admin_site)[source]
+

Bases: ModelAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+list_display = ['name', 'created_at', 'updated_at']
+
+ +
+
+list_filter = ['created_at', 'updated_at']
+
+ +
+
+property media
+
+ +
+
+search_fields = ['name']
+
+ +
+ +
+
+class booking.admin.TicketAdmin(model, admin_site)[source]
+

Bases: ModelAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+get_queryset(request)[source]
+

Return a QuerySet of all model instances that can be edited by the +admin site. This is used by changelist_view.

+
+ +
+
+has_add_permission(request)[source]
+

Return True if the given request has permission to add an object. +Can be overridden by the user in subclasses.

+
+ +
+
+has_change_permission(request, obj=None)[source]
+

Return True if the given request has permission to change the given +Django model instance, the default implementation doesn’t examine the +obj parameter.

+

Can be overridden by the user in subclasses. In such case it should +return True if the given request has permission to change the obj +model instance. If obj is None, this should return True if the given +request has permission to change any object of the given type.

+
+ +
+
+has_delete_permission(request, obj=None)[source]
+

Return True if the given request has permission to delete the given +Django model instance, the default implementation doesn’t examine the +obj parameter.

+

Can be overridden by the user in subclasses. In such case it should +return True if the given request has permission to delete the obj +model instance. If obj is None, this should return True if the given +request has permission to delete any object of the given type.

+
+ +
+
+list_display = ['match', 'section', 'seat_number', 'status', 'created_at', 'updated_at']
+
+ +
+
+list_filter = ['match', 'section', 'status', 'created_at', 'updated_at']
+
+ +
+
+property media
+
+ +
+
+search_fields = ['match', 'section', 'seat_number']
+
+ +
+ +
+
+

booking.apps module

+
+
+class booking.apps.BookingConfig(app_name, app_module)[source]
+

Bases: AppConfig

+
+
+default_auto_field = 'django.db.models.BigAutoField'
+
+ +
+
+name = 'booking'
+
+ +
+
+verbose_name = 'رزرو'
+
+ +
+ +
+
+

booking.urls module

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/booking.models.html b/docs/_build/html/_rst/booking.models.html new file mode 100644 index 00000000..b4ebcd2c --- /dev/null +++ b/docs/_build/html/_rst/booking.models.html @@ -0,0 +1,2128 @@ + + + + + + + booking.models package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

booking.models package

+
+

Submodules

+
+
+

booking.models.invoice module

+
+
+class booking.models.invoice.Invoice(id, created_at, updated_at, user, status)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_invoices

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

user (ForeignKey to User) – کاربر (related name: invoice_users)

+
+
+

Reverse relationships:

+
+
Parameters:
+
    +
  • ticket_invoices (Reverse ForeignKey from Ticket) – All ticket invoices of this فاکتور (related name of invoice)

  • +
  • payment_invoices (Reverse ForeignKey from Payment) – All payment invoices of this فاکتور (related name of invoice)

  • +
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_status_display(*, field=<django.db.models.PositiveSmallIntegerField: status>)
+

Shows the label of the status. See get_FOO_display() for more information.

+
+ +
+
+property get_total_amount: Decimal
+

It returns total price based on price of the section price

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+payment_invoices
+

Type: Reverse ForeignKey from Payment

+

All payment invoices of this فاکتور (related name of invoice)

+
+ +
+
+set_as_paid() None[source]
+

It will change the status field and save the object and tickets

+
+ +
+
+status
+

Type: PositiveSmallIntegerField

+

وضعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
+
+ +
+
+ticket_invoices
+

Type: Reverse ForeignKey from Ticket

+

All ticket invoices of this فاکتور (related name of invoice)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+
+user
+

Type: ForeignKey to User

+

کاربر (related name: invoice_users)

+
+ +
+
+user_id
+

Internal field, use user instead.

+
+ +
+ +
+
+

booking.models.match module

+
+
+class booking.models.match.Match(id, created_at, updated_at, stadium, host_team, guest_team, start_time)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_matches

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+
+
+
+

Reverse relationships:

+
+
Parameters:
+

ticket_matches (Reverse ForeignKey from Ticket) – All ticket matches of this مسابقه (related name of match)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_start_time(*, field=<django.db.models.DateTimeField: start_time>, is_next=True, **kwargs)
+

Finds next instance based on start_time. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_start_time(*, field=<django.db.models.DateTimeField: start_time>, is_next=False, **kwargs)
+

Finds previous instance based on start_time. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+guest_team
+

Type: ForeignKey to Team

+

تیم مهمان (related name: match_guest_teams)

+
+ +
+
+guest_team_id
+

Internal field, use guest_team instead.

+
+ +
+
+host_team
+

Type: ForeignKey to Team

+

تیم میزبان (related name: match_host_teams)

+
+ +
+
+host_team_id
+

Internal field, use host_team instead.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+stadium
+

Type: ForeignKey to Stadium

+

استادیوم (related name: match_stadiums)

+
+ +
+
+stadium_id
+

Internal field, use stadium instead.

+
+ +
+
+start_time
+

Type: DateTimeField

+

تاریخ آغاز

+
+ +
+
+ticket_matches
+

Type: Reverse ForeignKey from Ticket

+

All ticket matches of this مسابقه (related name of match)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+

booking.models.payment module

+
+
+class booking.models.payment.Payment(id, invoice, amount, status, created_at)[source]
+

Bases: Model

+

Database table: booking_payments

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

invoice (ForeignKey to Invoice) – فاکتور (related name: payment_invoices)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+amount
+

Type: DecimalField

+

مبلغ

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_status_display(*, field=<django.db.models.PositiveSmallIntegerField: status>)
+

Shows the label of the status. See get_FOO_display() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+invoice
+

Type: ForeignKey to Invoice

+

فاکتور (related name: payment_invoices)

+
+ +
+
+invoice_id
+

Internal field, use invoice instead.

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+status
+

Type: PositiveSmallIntegerField

+

وضعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
+
+ +
+ +
+
+

booking.models.section module

+
+
+class booking.models.section.Section(id, created_at, updated_at, stadium, location, capacity, price)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_sections

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

stadium (ForeignKey to Stadium) – استادیوم (related name: section_stadiums)

+
+
+

Reverse relationships:

+
+
Parameters:
+

ticket_sections (Reverse ForeignKey from Ticket) – All ticket sections of this جایگاه (related name of section)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+capacity
+

Type: PositiveSmallIntegerField

+

ظرفیت

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+property get_list_of_seat_numbers: list
+

It returns a list of seat numbers

+
+ +
+
+get_location_display(*, field=<django.db.models.PositiveSmallIntegerField: location>)
+

Shows the label of the location. See get_FOO_display() for more information.

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+location
+

Type: PositiveSmallIntegerField

+

موقعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
  • 2

  • +
  • 3

  • +
+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+price
+

Type: DecimalField

+

قیمت

+
+ +
+
+stadium
+

Type: ForeignKey to Stadium

+

استادیوم (related name: section_stadiums)

+
+ +
+
+stadium_id
+

Internal field, use stadium instead.

+
+ +
+
+ticket_sections
+

Type: Reverse ForeignKey from Ticket

+

All ticket sections of this جایگاه (related name of section)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+

booking.models.stadium module

+
+
+class booking.models.stadium.Stadium(id, created_at, updated_at, name, province, city, address, map_url)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_stadiums

+
+
Parameters:
+
+
+
+

Reverse relationships:

+
+
Parameters:
+
    +
  • section_stadiums (Reverse ForeignKey from Section) – All section stadiums of this استادیوم (related name of stadium)

  • +
  • match_stadiums (Reverse ForeignKey from Match) – All match stadiums of this استادیوم (related name of stadium)

  • +
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+address
+

Type: TextField

+

آدرس

+
+ +
+
+city
+

Type: CharField

+

شهر

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+property get_section_ids: list
+

It returns a list of section ids

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+map_url
+

Type: URLField

+

آدرس نقشه

+
+ +
+
+match_stadiums
+

Type: Reverse ForeignKey from Match

+

All match stadiums of this استادیوم (related name of stadium)

+
+ +
+
+name
+

Type: CharField

+

نام

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+province
+

Type: CharField

+

استان

+
+ +
+
+section_stadiums
+

Type: Reverse ForeignKey from Section

+

All section stadiums of this استادیوم (related name of stadium)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+

booking.models.team module

+
+
+class booking.models.team.Team(id, created_at, updated_at, name)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_teams

+
+
Parameters:
+
+
+
+

Reverse relationships:

+
+
Parameters:
+
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+match_guest_teams
+

Type: Reverse ForeignKey from Match

+

All match guest teams of this تیم (related name of guest_team)

+
+ +
+
+match_host_teams
+

Type: Reverse ForeignKey from Match

+

All match host teams of this تیم (related name of host_team)

+
+ +
+
+name
+

Type: CharField

+

نام

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+

booking.models.ticket module

+
+
+class booking.models.ticket.Ticket(id, created_at, updated_at, invoice, match, section, seat_number, status)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_tickets

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_status_display(*, field=<django.db.models.PositiveSmallIntegerField: status>)
+

Shows the label of the status. See get_FOO_display() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+invoice
+

Type: ForeignKey to Invoice

+

فاکتور (related name: ticket_invoices)

+
+ +
+
+invoice_id
+

Internal field, use invoice instead.

+
+ +
+
+match
+

Type: ForeignKey to Match

+

مسابقه (related name: ticket_matches)

+
+ +
+
+match_id
+

Internal field, use match instead.

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+seat_number
+

Type: PositiveSmallIntegerField

+

شماره صندلی

+
+ +
+
+section
+

Type: ForeignKey to Section

+

جایگاه (related name: ticket_sections)

+
+ +
+
+section_id
+

Internal field, use section instead.

+
+ +
+
+set_as_sold() None[source]
+

It will change the status field and save the object

+
+ +
+
+status
+

Type: PositiveSmallIntegerField

+

وضعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+

Module contents

+
+
+class booking.models.Invoice(id, created_at, updated_at, user, status)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_invoices

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

user (ForeignKey to User) – کاربر (related name: invoice_users)

+
+
+

Reverse relationships:

+
+
Parameters:
+
    +
  • ticket_invoices (Reverse ForeignKey from Ticket) – All ticket invoices of this فاکتور (related name of invoice)

  • +
  • payment_invoices (Reverse ForeignKey from Payment) – All payment invoices of this فاکتور (related name of invoice)

  • +
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_status_display(*, field=<django.db.models.PositiveSmallIntegerField: status>)
+

Shows the label of the status. See get_FOO_display() for more information.

+
+ +
+
+property get_total_amount: Decimal
+

It returns total price based on price of the section price

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+payment_invoices
+

Type: Reverse ForeignKey from Payment

+

All payment invoices of this فاکتور (related name of invoice)

+
+ +
+
+set_as_paid() None[source]
+

It will change the status field and save the object and tickets

+
+ +
+
+status
+

Type: PositiveSmallIntegerField

+

وضعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
+
+ +
+
+ticket_invoices
+

Type: Reverse ForeignKey from Ticket

+

All ticket invoices of this فاکتور (related name of invoice)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+
+user
+

Type: ForeignKey to User

+

کاربر (related name: invoice_users)

+
+ +
+
+user_id
+

Internal field, use user instead.

+
+ +
+ +
+
+class booking.models.Match(id, created_at, updated_at, stadium, host_team, guest_team, start_time)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_matches

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+
+
+
+

Reverse relationships:

+
+
Parameters:
+

ticket_matches (Reverse ForeignKey from Ticket) – All ticket matches of this مسابقه (related name of match)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_start_time(*, field=<django.db.models.DateTimeField: start_time>, is_next=True, **kwargs)
+

Finds next instance based on start_time. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_start_time(*, field=<django.db.models.DateTimeField: start_time>, is_next=False, **kwargs)
+

Finds previous instance based on start_time. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+guest_team
+

Type: ForeignKey to Team

+

تیم مهمان (related name: match_guest_teams)

+
+ +
+
+guest_team_id
+

Internal field, use guest_team instead.

+
+ +
+
+host_team
+

Type: ForeignKey to Team

+

تیم میزبان (related name: match_host_teams)

+
+ +
+
+host_team_id
+

Internal field, use host_team instead.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+stadium
+

Type: ForeignKey to Stadium

+

استادیوم (related name: match_stadiums)

+
+ +
+
+stadium_id
+

Internal field, use stadium instead.

+
+ +
+
+start_time
+

Type: DateTimeField

+

تاریخ آغاز

+
+ +
+
+ticket_matches
+

Type: Reverse ForeignKey from Ticket

+

All ticket matches of this مسابقه (related name of match)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+class booking.models.Payment(id, invoice, amount, status, created_at)[source]
+

Bases: Model

+

Database table: booking_payments

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

invoice (ForeignKey to Invoice) – فاکتور (related name: payment_invoices)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+amount
+

Type: DecimalField

+

مبلغ

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_status_display(*, field=<django.db.models.PositiveSmallIntegerField: status>)
+

Shows the label of the status. See get_FOO_display() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+invoice
+

Type: ForeignKey to Invoice

+

فاکتور (related name: payment_invoices)

+
+ +
+
+invoice_id
+

Internal field, use invoice instead.

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+status
+

Type: PositiveSmallIntegerField

+

وضعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
+
+ +
+ +
+
+class booking.models.Section(id, created_at, updated_at, stadium, location, capacity, price)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_sections

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

stadium (ForeignKey to Stadium) – استادیوم (related name: section_stadiums)

+
+
+

Reverse relationships:

+
+
Parameters:
+

ticket_sections (Reverse ForeignKey from Ticket) – All ticket sections of this جایگاه (related name of section)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+capacity
+

Type: PositiveSmallIntegerField

+

ظرفیت

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+property get_list_of_seat_numbers: list
+

It returns a list of seat numbers

+
+ +
+
+get_location_display(*, field=<django.db.models.PositiveSmallIntegerField: location>)
+

Shows the label of the location. See get_FOO_display() for more information.

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+location
+

Type: PositiveSmallIntegerField

+

موقعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
  • 2

  • +
  • 3

  • +
+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+price
+

Type: DecimalField

+

قیمت

+
+ +
+
+stadium
+

Type: ForeignKey to Stadium

+

استادیوم (related name: section_stadiums)

+
+ +
+
+stadium_id
+

Internal field, use stadium instead.

+
+ +
+
+ticket_sections
+

Type: Reverse ForeignKey from Ticket

+

All ticket sections of this جایگاه (related name of section)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+class booking.models.Stadium(id, created_at, updated_at, name, province, city, address, map_url)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_stadiums

+
+
Parameters:
+
+
+
+

Reverse relationships:

+
+
Parameters:
+
    +
  • section_stadiums (Reverse ForeignKey from Section) – All section stadiums of this استادیوم (related name of stadium)

  • +
  • match_stadiums (Reverse ForeignKey from Match) – All match stadiums of this استادیوم (related name of stadium)

  • +
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+address
+

Type: TextField

+

آدرس

+
+ +
+
+city
+

Type: CharField

+

شهر

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+property get_section_ids: list
+

It returns a list of section ids

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+map_url
+

Type: URLField

+

آدرس نقشه

+
+ +
+
+match_stadiums
+

Type: Reverse ForeignKey from Match

+

All match stadiums of this استادیوم (related name of stadium)

+
+ +
+
+name
+

Type: CharField

+

نام

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+province
+

Type: CharField

+

استان

+
+ +
+
+section_stadiums
+

Type: Reverse ForeignKey from Section

+

All section stadiums of this استادیوم (related name of stadium)

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+class booking.models.Team(id, created_at, updated_at, name)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_teams

+
+
Parameters:
+
+
+
+

Reverse relationships:

+
+
Parameters:
+
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+match_guest_teams
+

Type: Reverse ForeignKey from Match

+

All match guest teams of this تیم (related name of guest_team)

+
+ +
+
+match_host_teams
+

Type: Reverse ForeignKey from Match

+

All match host teams of this تیم (related name of host_team)

+
+ +
+
+name
+

Type: CharField

+

نام

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+class booking.models.Ticket(id, created_at, updated_at, invoice, match, section, seat_number, status)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: booking_tickets

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_status_display(*, field=<django.db.models.PositiveSmallIntegerField: status>)
+

Shows the label of the status. See get_FOO_display() for more information.

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+invoice
+

Type: ForeignKey to Invoice

+

فاکتور (related name: ticket_invoices)

+
+ +
+
+invoice_id
+

Internal field, use invoice instead.

+
+ +
+
+match
+

Type: ForeignKey to Match

+

مسابقه (related name: ticket_matches)

+
+ +
+
+match_id
+

Internal field, use match instead.

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+seat_number
+

Type: PositiveSmallIntegerField

+

شماره صندلی

+
+ +
+
+section
+

Type: ForeignKey to Section

+

جایگاه (related name: ticket_sections)

+
+ +
+
+section_id
+

Internal field, use section instead.

+
+ +
+
+set_as_sold() None[source]
+

It will change the status field and save the object

+
+ +
+
+status
+

Type: PositiveSmallIntegerField

+

وضعیت

+

Choices:

+
    +
  • 0

  • +
  • 1

  • +
+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/booking.serializers.html b/docs/_build/html/_rst/booking.serializers.html new file mode 100644 index 00000000..097a7d57 --- /dev/null +++ b/docs/_build/html/_rst/booking.serializers.html @@ -0,0 +1,370 @@ + + + + + + + booking.serializers package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

booking.serializers package

+
+

Submodules

+
+
+

booking.serializers.invoice module

+
+
+class booking.serializers.invoice.InvoiceSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Invoice’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+ +
+
+

booking.serializers.match module

+
+
+class booking.serializers.match.MatchSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Match’ model

+
+ +
+
+

booking.serializers.payment module

+
+
+class booking.serializers.payment.PaymentSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Payment’ model

+
+
+create(validated_data)[source]
+

Create a new instance +:param validated_data: A dict of fields +:return: A new instance

+
+ +
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate(data)[source]
+

It will use for making the validation on data +:param data: A dict of fields +:return: A valid data or errors

+
+ +
+ +
+
+

booking.serializers.section module

+
+
+class booking.serializers.section.SectionSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Section’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+ +
+
+

booking.serializers.stadium module

+
+
+class booking.serializers.stadium.StadiumSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Stadium’ model

+
+ +
+
+

booking.serializers.team module

+
+
+class booking.serializers.team.TeamSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Team’ model

+
+ +
+
+

booking.serializers.ticket module

+
+
+class booking.serializers.ticket.TicketSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Ticket’ model

+
+
+create(validated_data)[source]
+

Create a new instance +:param validated_data: A dict of fields +:return: A new instance

+
+ +
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate(data)[source]
+

It will use for making the validation on data +:param data: A dict of fields +:return: A valid data or errors

+
+ +
+ +
+
+

Module contents

+
+
+class booking.serializers.InvoiceSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Invoice’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+ +
+
+class booking.serializers.MatchSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Match’ model

+
+ +
+
+class booking.serializers.PaymentSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Payment’ model

+
+
+create(validated_data)[source]
+

Create a new instance +:param validated_data: A dict of fields +:return: A new instance

+
+ +
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate(data)[source]
+

It will use for making the validation on data +:param data: A dict of fields +:return: A valid data or errors

+
+ +
+ +
+
+class booking.serializers.SectionSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Section’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+ +
+
+class booking.serializers.StadiumSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Stadium’ model

+
+ +
+
+class booking.serializers.TeamSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Team’ model

+
+ +
+
+class booking.serializers.TicketSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Ticket’ model

+
+
+create(validated_data)[source]
+

Create a new instance +:param validated_data: A dict of fields +:return: A new instance

+
+ +
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate(data)[source]
+

It will use for making the validation on data +:param data: A dict of fields +:return: A valid data or errors

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/booking.views.html b/docs/_build/html/_rst/booking.views.html new file mode 100644 index 00000000..1f42673e --- /dev/null +++ b/docs/_build/html/_rst/booking.views.html @@ -0,0 +1,1056 @@ + + + + + + + booking.views package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

booking.views package

+
+

Submodules

+
+
+

booking.views.invoice module

+
+
+class booking.views.invoice.InvoiceViewSet(**kwargs)[source]
+

Bases: ReadOnlyModelViewSet

+

ViewSet for the ‘Invoice’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['user', 'status']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['user__username']
+
+ +
+
+serializer_class
+

alias of InvoiceSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

booking.views.match module

+
+
+class booking.views.match.MatchViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Match’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['stadium', 'host_team', 'guest_team', 'start_time']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['stadium__name', 'host_team__name', 'guest_team__name', 'start_time']
+
+ +
+
+seats(request, pk=None)[source]
+

This will use for show the current match seats +:return: The current match seats

+
+ +
+
+serializer_class
+

alias of MatchSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

booking.views.payment module

+
+
+class booking.views.payment.PaymentViewSet(**kwargs)[source]
+

Bases: ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet

+

ViewSet for the ‘Payment’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['invoice', 'amount', 'status', 'created_at']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['invoice', 'invoice__user__username', 'amount', 'created_at']
+
+ +
+
+serializer_class
+

alias of PaymentSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

booking.views.section module

+
+
+class booking.views.section.SectionViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Section’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['stadium', 'location', 'capacity', 'price']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['stadium__name', 'location', 'capacity', 'price']
+
+ +
+
+serializer_class
+

alias of SectionSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

booking.views.stadium module

+
+
+class booking.views.stadium.StadiumViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Stadium’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['name', 'province', 'city']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name', 'province', 'city', 'address']
+
+ +
+
+serializer_class
+

alias of StadiumSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

booking.views.team module

+
+
+class booking.views.team.TeamViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Team’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['name']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name']
+
+ +
+
+serializer_class
+

alias of TeamSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

booking.views.ticket module

+
+
+class booking.views.ticket.TicketViewSet(**kwargs)[source]
+

Bases: ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet

+

ViewSet for the ‘Ticket’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['invoice', 'match', 'section', 'seat_number', 'status']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['invoice', 'invoice__user__username', 'match', 'section', 'seat_number']
+
+ +
+
+serializer_class
+

alias of TicketSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

Module contents

+
+
+class booking.views.InvoiceViewSet(**kwargs)[source]
+

Bases: ReadOnlyModelViewSet

+

ViewSet for the ‘Invoice’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['user', 'status']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['user__username']
+
+ +
+
+serializer_class
+

alias of InvoiceSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class booking.views.MatchViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Match’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['stadium', 'host_team', 'guest_team', 'start_time']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['stadium__name', 'host_team__name', 'guest_team__name', 'start_time']
+
+ +
+
+seats(request, pk=None)[source]
+

This will use for show the current match seats +:return: The current match seats

+
+ +
+
+serializer_class
+

alias of MatchSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class booking.views.PaymentViewSet(**kwargs)[source]
+

Bases: ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet

+

ViewSet for the ‘Payment’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['invoice', 'amount', 'status', 'created_at']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['invoice', 'invoice__user__username', 'amount', 'created_at']
+
+ +
+
+serializer_class
+

alias of PaymentSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class booking.views.SectionViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Section’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['stadium', 'location', 'capacity', 'price']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['stadium__name', 'location', 'capacity', 'price']
+
+ +
+
+serializer_class
+

alias of SectionSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class booking.views.StadiumViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Stadium’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['name', 'province', 'city']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name', 'province', 'city', 'address']
+
+ +
+
+serializer_class
+

alias of StadiumSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class booking.views.TeamViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Team’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['name']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name']
+
+ +
+
+serializer_class
+

alias of TeamSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class booking.views.TicketViewSet(**kwargs)[source]
+

Bases: ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet

+

ViewSet for the ‘Ticket’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['invoice', 'match', 'section', 'seat_number', 'status']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['invoice', 'invoice__user__username', 'match', 'section', 'seat_number']
+
+ +
+
+serializer_class
+

alias of TicketSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/commands.html b/docs/_build/html/_rst/commands.html new file mode 100644 index 00000000..e3af6f69 --- /dev/null +++ b/docs/_build/html/_rst/commands.html @@ -0,0 +1,156 @@ + + + + + + + commands package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

commands package

+
+

Submodules

+
+
+

commands.apps module

+
+
+class commands.apps.CommandsConfig(app_name, app_module)[source]
+

Bases: AppConfig

+
+
+default_auto_field = 'django.db.models.BigAutoField'
+
+ +
+
+name = 'commands'
+
+ +
+
+verbose_name = 'دستورات'
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/core.html b/docs/_build/html/_rst/core.html new file mode 100644 index 00000000..f7b01d36 --- /dev/null +++ b/docs/_build/html/_rst/core.html @@ -0,0 +1,184 @@ + + + + + + + core package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

core package

+
+

Submodules

+
+
+

core.asgi module

+

ASGI config for core project.

+

It exposes the ASGI callable as a module-level variable named application.

+

For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/

+
+
+

core.settings module

+

Django settings for core project.

+

Generated by ‘django-admin startproject’ using Django 4.2.6.

+

For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/

+

For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/

+
+
+

core.urls module

+

URL configuration for core project.

+
+
The urlpatterns list routes URLs to views. For more information please see:

https://docs.djangoproject.com/en/4.2/topics/http/urls/

+
+
+

Examples: +Function views

+
+
    +
  1. Add an import: from my_app import views

  2. +
  3. Add a URL to urlpatterns: path(‘’, views.home, name=’home’)

  4. +
+
+
+
Class-based views
    +
  1. Add an import: from other_app.views import Home

  2. +
  3. Add a URL to urlpatterns: path(‘’, Home.as_view(), name=’home’)

  4. +
+
+
Including another URLconf
    +
  1. Import the include() function: from django.urls import include, path

  2. +
  3. Add a URL to urlpatterns: path(‘blog/’, include(‘blog.urls’))

  4. +
+
+
+
+
+

core.wsgi module

+

WSGI config for core project.

+

It exposes the WSGI callable as a module-level variable named application.

+

For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/extensions.html b/docs/_build/html/_rst/extensions.html new file mode 100644 index 00000000..16af7c3c --- /dev/null +++ b/docs/_build/html/_rst/extensions.html @@ -0,0 +1,354 @@ + + + + + + + extensions package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

extensions package

+
+

Submodules

+
+
+

extensions.abstract_models module

+
+
+class extensions.abstract_models.AbstractCreatAtUpdateAt(*args, **kwargs)[source]
+

Bases: Model

+
+
Parameters:
+
+
+
+
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+ +
+
+

extensions.choices module

+
+
+class extensions.choices.GenderChoices(value)[source]
+

Bases: IntegerChoices

+

Enum class for the gender fields

+
+
+FEMALE = 1
+
+ +
+
+MALE = 0
+
+ +
+ +
+
+class extensions.choices.InvoiceStatusChoices(value)[source]
+

Bases: IntegerChoices

+

Enum class for the invoice status fields

+
+
+PAID = 0
+
+ +
+
+UNPAID = 1
+
+ +
+ +
+
+class extensions.choices.LocationChoices(value)[source]
+

Bases: IntegerChoices

+

Enum class for the location fields

+
+
+A = 0
+
+ +
+
+B = 1
+
+ +
+
+C = 2
+
+ +
+
+VIP = 3
+
+ +
+ +
+
+class extensions.choices.PaymentStatusChoices(value)[source]
+

Bases: IntegerChoices

+

Enum class for the payment status fields

+
+
+SUCCESSFUL = 0
+
+ +
+
+UNSUCCESSFUL = 1
+
+ +
+ +
+
+class extensions.choices.TicketStatusChoices(value)[source]
+

Bases: IntegerChoices

+

Enum class for the ticket status fields

+
+
+RESERVED = 1
+
+ +
+
+SOLD = 0
+
+ +
+ +
+
+

extensions.custom_exception_handlers module

+
+
+extensions.custom_exception_handlers.exception_handler(exc, context)[source]
+

Handle Django ValidationError as an accepted exception

+
+ +
+
+

extensions.custom_permissions module

+
+
+class extensions.custom_permissions.CustomDjangoModelPermission[source]
+

Bases: DjangoModelPermissions

+

Custom permission class to handle better permission by model permissions

+
+
+__init__()[source]
+
+ +
+ +
+
+class extensions.custom_permissions.OwnUserPermission[source]
+

Bases: BasePermission

+

Object-level permission to only allow updating his own user

+
+
+has_object_permission(request, view, obj)[source]
+

Return True if permission is granted, False otherwise.

+
+ +
+ +
+
+class extensions.custom_permissions.UnauthenticatedPost[source]
+

Bases: BasePermission

+

Allow to unauthenticated user to send the ‘POST’ request

+
+
+has_permission(request, view)[source]
+

Return True if permission is granted, False otherwise.

+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/manage.html b/docs/_build/html/_rst/manage.html new file mode 100644 index 00000000..58ae2cb8 --- /dev/null +++ b/docs/_build/html/_rst/manage.html @@ -0,0 +1,128 @@ + + + + + + + manage module — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

manage module

+

Django’s command-line utility for administrative tasks.

+
+
+manage.main()[source]
+

Run administrative tasks.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/modules.html b/docs/_build/html/_rst/modules.html new file mode 100644 index 00000000..4885ecff --- /dev/null +++ b/docs/_build/html/_rst/modules.html @@ -0,0 +1,453 @@ + + + + + + + src — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

src

+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/user_management.html b/docs/_build/html/_rst/user_management.html new file mode 100644 index 00000000..11a757f4 --- /dev/null +++ b/docs/_build/html/_rst/user_management.html @@ -0,0 +1,635 @@ + + + + + + + user_management package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

user_management package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+
+
+

user_management.admin module

+
+
+class user_management.admin.ProfileInlineAdmin(parent_model, admin_site)[source]
+

Bases: TabularInline

+
+
+property media
+
+ +
+
+model
+

alias of Profile

+
+ +
+ +
+
+class user_management.admin.UserAdmin(model, admin_site)[source]
+

Bases: UserAdmin

+
+
+actions = ['delete_selected']
+
+ +
+
+add_fieldsets = ((None, {'classes': ('wide',), 'fields': ('username', 'password1', 'password2')}), ('کنترل دسترسی', {'fields': ('is_active', 'is_superuser', 'is_staff', 'groups')}))
+
+ +
+
+get_full_name(obj)[source]
+
+ +
+
+get_queryset(request)[source]
+

Return a QuerySet of all model instances that can be edited by the +admin site. This is used by changelist_view.

+
+ +
+
+inlines = (<class 'user_management.admin.ProfileInlineAdmin'>,)
+
+ +
+
+list_display = ['username', 'get_full_name', 'is_active', 'is_superuser', 'is_staff', 'created_at', 'updated_at', 'last_login']
+
+ +
+
+list_filter = ['is_active', 'is_superuser', 'is_staff', 'created_at', 'updated_at', 'last_login']
+
+ +
+
+property media
+
+ +
+
+readonly_fields = ['created_at', 'updated_at', 'last_login']
+
+ +
+
+search_fields = ['username']
+
+ +
+ +
+
+

user_management.apps module

+
+
+class user_management.apps.UserManagementConfig(app_name, app_module)[source]
+

Bases: AppConfig

+
+
+default_auto_field = 'django.db.models.BigAutoField'
+
+ +
+
+name = 'user_management'
+
+ +
+
+ready()[source]
+

Override this method in subclasses to run code when Django starts.

+
+ +
+
+verbose_name = 'مدیریت کاربر'
+
+ +
+ +
+
+

user_management.managers module

+
+
+class user_management.managers.UserManager(*args, **kwargs)[source]
+

Bases: BaseUserManager

+

Manager class for the ‘user’ model

+
+
+create_staff(password, **extra_fields)[source]
+

This will create a new staff user

+
+ +
+
+create_superuser(password, **extra_fields)[source]
+

This will create a new super admin user

+
+ +
+
+create_user(password, **extra_fields)[source]
+

This will create a new regular user

+
+ +
+
+use_in_migrations = True
+

If set to True the manager will be serialized into migrations and will +thus be available in e.g. RunPython operations.

+
+ +
+ +
+
+

user_management.signals module

+
+
+user_management.signals.creating_profile(sender, instance, created, raw, using, **kwargs)[source]
+

It will create a new profile instance for the user

+
+ +
+
+

user_management.urls module

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/user_management.models.html b/docs/_build/html/_rst/user_management.models.html new file mode 100644 index 00000000..05e7fe96 --- /dev/null +++ b/docs/_build/html/_rst/user_management.models.html @@ -0,0 +1,822 @@ + + + + + + + user_management.models package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

user_management.models package

+
+

Submodules

+
+
+

user_management.models.profile module

+
+
+class user_management.models.profile.Profile(created_at, updated_at, user, first_name, last_name, gender)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: user_management_profiles

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

user (OneToOneField to User) – Primary key: کاربر (related name: profile_user)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+first_name
+

Type: CharField

+

نام

+
+ +
+
+gender
+

Type: PositiveSmallIntegerField

+

جنسیت

+

Choices:

+
    +
  • None

  • +
  • 0

  • +
  • 1

  • +
+
+ +
+
+property get_full_name: str
+
+ +
+
+get_gender_display(*, field=<django.db.models.PositiveSmallIntegerField: gender>)
+

Shows the label of the gender. See get_FOO_display() for more information.

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+last_name
+

Type: CharField

+

نام خانوادگی

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+
+user
+

Type: OneToOneField to User

+

Primary key: کاربر (related name: profile_user)

+
+ +
+
+user_id
+

Internal field, use user instead.

+
+ +
+ +
+
+

user_management.models.user module

+
+
+class user_management.models.user.User(*args, **kwargs)[source]
+

Bases: AbstractBaseUser, AbstractCreatAtUpdateAt, PermissionsMixin

+

Database table: user_management_users

+

Custom user model to authenticate user by email address, +Verification of users by sending an activation link to their email is not implemented.

+
+
Parameters:
+
    +
  • id (BigAutoField) – Primary key: ID

  • +
  • password (CharField) – گذرواژه

  • +
  • last_login (DateTimeField) – آخرین ورود

  • +
  • is_superuser (BooleanField) – ابرکاربر. نشان می‌دهد که این کاربر همهٔ اجازه‌ها را دارد بدون آنکه به صراحت به او اختصاص داده شده باشد.

  • +
  • created_at (DateTimeField) – ایجاد شده در

  • +
  • updated_at (DateTimeField) – به روز شده در

  • +
  • username (EmailField) – نام کاربری

  • +
  • is_staff (BooleanField) – دسترسی به بخش مدیریت

  • +
  • is_active (BooleanField) – فعال بودن

  • +
+
+
+

Relationship fields:

+
+
Parameters:
+
    +
  • groups (ManyToManyField to Group) – گروه‌ها. گروه‌هایی که این کاربر به آنها تعلق دارد. کاربر تمام اجازه‌های مرتبط با این گروه‌ها را دریافت خواهد کرد. (related name: user_set)

  • +
  • user_permissions (ManyToManyField to Permission) – اجازه‌های کاربر. اجازه‌های خاص این کاربر. (related name: user_set)

  • +
+
+
+

Reverse relationships:

+
+
Parameters:
+
    +
  • logentry (Reverse ForeignKey from LogEntry) – All موارد اتفاقات of this کاربر (related name of user)

  • +
  • profile_user (Reverse OneToOneField from Profile) – The profile user of this کاربر (related name of user)

  • +
  • invoice_users (Reverse ForeignKey from Invoice) – All invoice users of this کاربر (related name of user)

  • +
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+EMAIL_FIELD = 'username'
+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+REQUIRED_FIELDS = []
+
+ +
+
+USERNAME_FIELD = 'username'
+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+groups
+

Type: ManyToManyField to Group

+

گروه‌ها. گروه‌هایی که این کاربر به آنها تعلق دارد. کاربر تمام اجازه‌های مرتبط با این گروه‌ها را دریافت خواهد کرد. (related name: user_set)

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+invoice_users
+

Type: Reverse ForeignKey from Invoice

+

All invoice users of this کاربر (related name of user)

+
+ +
+
+is_active
+

Type: BooleanField

+

فعال بودن

+
+ +
+
+is_staff
+

Type: BooleanField

+

دسترسی به بخش مدیریت

+
+ +
+
+is_superuser
+

Type: BooleanField

+

ابرکاربر. نشان می‌دهد که این کاربر همهٔ اجازه‌ها را دارد بدون آنکه به صراحت به او اختصاص داده شده باشد.

+
+ +
+
+last_login
+

Type: DateTimeField

+

آخرین ورود

+
+ +
+
+logentry_set
+

Type: Reverse ForeignKey from LogEntry

+

All موارد اتفاقات of this کاربر (related name of user)

+
+ +
+
+objects = <user_management.managers.UserManager object>
+
+ +
+
+password
+

Type: CharField

+

گذرواژه

+
+ +
+
+profile_user
+

Type: Reverse OneToOneField from Profile

+

The profile user of this کاربر (related name of user)

+
+ +
+
+save(*args, **kwargs)[source]
+

Save the current instance. Override this in a subclass if you want to +control the saving process.

+

The ‘force_insert’ and ‘force_update’ parameters can be used to insist +that the “save” must be an SQL insert or update (or equivalent for +non-SQL backends), respectively. Normally, they should not be set.

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+
+user_permissions
+

Type: ManyToManyField to Permission

+

اجازه‌های کاربر. اجازه‌های خاص این کاربر. (related name: user_set)

+
+ +
+
+username
+

Type: EmailField

+

نام کاربری

+
+ +
+ +
+
+

Module contents

+
+
+class user_management.models.Profile(created_at, updated_at, user, first_name, last_name, gender)[source]
+

Bases: AbstractCreatAtUpdateAt, Model

+

Database table: user_management_profiles

+
+
Parameters:
+
+
+
+

Relationship fields:

+
+
Parameters:
+

user (OneToOneField to User) – Primary key: کاربر (related name: profile_user)

+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+first_name
+

Type: CharField

+

نام

+
+ +
+
+gender
+

Type: PositiveSmallIntegerField

+

جنسیت

+

Choices:

+
    +
  • None

  • +
  • 0

  • +
  • 1

  • +
+
+ +
+
+property get_full_name: str
+
+ +
+
+get_gender_display(*, field=<django.db.models.PositiveSmallIntegerField: gender>)
+

Shows the label of the gender. See get_FOO_display() for more information.

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+last_name
+

Type: CharField

+

نام خانوادگی

+
+ +
+
+objects = <django.db.models.Manager object>
+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+
+user
+

Type: OneToOneField to User

+

Primary key: کاربر (related name: profile_user)

+
+ +
+
+user_id
+

Internal field, use user instead.

+
+ +
+ +
+
+class user_management.models.User(*args, **kwargs)[source]
+

Bases: AbstractBaseUser, AbstractCreatAtUpdateAt, PermissionsMixin

+

Database table: user_management_users

+

Custom user model to authenticate user by email address, +Verification of users by sending an activation link to their email is not implemented.

+
+
Parameters:
+
    +
  • id (BigAutoField) – Primary key: ID

  • +
  • password (CharField) – گذرواژه

  • +
  • last_login (DateTimeField) – آخرین ورود

  • +
  • is_superuser (BooleanField) – ابرکاربر. نشان می‌دهد که این کاربر همهٔ اجازه‌ها را دارد بدون آنکه به صراحت به او اختصاص داده شده باشد.

  • +
  • created_at (DateTimeField) – ایجاد شده در

  • +
  • updated_at (DateTimeField) – به روز شده در

  • +
  • username (EmailField) – نام کاربری

  • +
  • is_staff (BooleanField) – دسترسی به بخش مدیریت

  • +
  • is_active (BooleanField) – فعال بودن

  • +
+
+
+

Relationship fields:

+
+
Parameters:
+
    +
  • groups (ManyToManyField to Group) – گروه‌ها. گروه‌هایی که این کاربر به آنها تعلق دارد. کاربر تمام اجازه‌های مرتبط با این گروه‌ها را دریافت خواهد کرد. (related name: user_set)

  • +
  • user_permissions (ManyToManyField to Permission) – اجازه‌های کاربر. اجازه‌های خاص این کاربر. (related name: user_set)

  • +
+
+
+

Reverse relationships:

+
+
Parameters:
+
    +
  • logentry (Reverse ForeignKey from LogEntry) – All موارد اتفاقات of this کاربر (related name of user)

  • +
  • profile_user (Reverse OneToOneField from Profile) – The profile user of this کاربر (related name of user)

  • +
  • invoice_users (Reverse ForeignKey from Invoice) – All invoice users of this کاربر (related name of user)

  • +
+
+
+
+
+exception DoesNotExist
+

Bases: ObjectDoesNotExist

+
+ +
+
+EMAIL_FIELD = 'username'
+
+ +
+
+exception MultipleObjectsReturned
+

Bases: MultipleObjectsReturned

+
+ +
+
+REQUIRED_FIELDS = []
+
+ +
+
+USERNAME_FIELD = 'username'
+
+ +
+
+created_at
+

Type: DateTimeField

+

ایجاد شده در

+
+ +
+
+get_next_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=True, **kwargs)
+

Finds next instance based on created_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_next_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=True, **kwargs)
+

Finds next instance based on updated_at. See get_next_by_FOO() for more information.

+
+ +
+
+get_previous_by_created_at(*, field=<django.db.models.DateTimeField: created_at>, is_next=False, **kwargs)
+

Finds previous instance based on created_at. See get_previous_by_FOO() for more information.

+
+ +
+
+get_previous_by_updated_at(*, field=<django.db.models.DateTimeField: updated_at>, is_next=False, **kwargs)
+

Finds previous instance based on updated_at. See get_previous_by_FOO() for more information.

+
+ +
+
+groups
+

Type: ManyToManyField to Group

+

گروه‌ها. گروه‌هایی که این کاربر به آنها تعلق دارد. کاربر تمام اجازه‌های مرتبط با این گروه‌ها را دریافت خواهد کرد. (related name: user_set)

+
+ +
+
+id
+

Type: BigAutoField

+

Primary key: ID

+
+ +
+
+invoice_users
+

Type: Reverse ForeignKey from Invoice

+

All invoice users of this کاربر (related name of user)

+
+ +
+
+is_active
+

Type: BooleanField

+

فعال بودن

+
+ +
+
+is_staff
+

Type: BooleanField

+

دسترسی به بخش مدیریت

+
+ +
+
+is_superuser
+

Type: BooleanField

+

ابرکاربر. نشان می‌دهد که این کاربر همهٔ اجازه‌ها را دارد بدون آنکه به صراحت به او اختصاص داده شده باشد.

+
+ +
+
+last_login
+

Type: DateTimeField

+

آخرین ورود

+
+ +
+
+logentry_set
+

Type: Reverse ForeignKey from LogEntry

+

All موارد اتفاقات of this کاربر (related name of user)

+
+ +
+
+objects = <user_management.managers.UserManager object>
+
+ +
+
+password
+

Type: CharField

+

گذرواژه

+
+ +
+
+profile_user
+

Type: Reverse OneToOneField from Profile

+

The profile user of this کاربر (related name of user)

+
+ +
+
+save(*args, **kwargs)[source]
+

Save the current instance. Override this in a subclass if you want to +control the saving process.

+

The ‘force_insert’ and ‘force_update’ parameters can be used to insist +that the “save” must be an SQL insert or update (or equivalent for +non-SQL backends), respectively. Normally, they should not be set.

+
+ +
+
+updated_at
+

Type: DateTimeField

+

به روز شده در

+
+ +
+
+user_permissions
+

Type: ManyToManyField to Permission

+

اجازه‌های کاربر. اجازه‌های خاص این کاربر. (related name: user_set)

+
+ +
+
+username
+

Type: EmailField

+

نام کاربری

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/user_management.serializers.html b/docs/_build/html/_rst/user_management.serializers.html new file mode 100644 index 00000000..6c5b3a37 --- /dev/null +++ b/docs/_build/html/_rst/user_management.serializers.html @@ -0,0 +1,367 @@ + + + + + + + user_management.serializers package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

user_management.serializers package

+
+

Submodules

+
+
+

user_management.serializers.group module

+
+
+class user_management.serializers.group.CreateGroupSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for creating a new ‘Group’ instance

+
+
+create(validated_data)[source]
+

Create a new instance +:param validated_data: A dict of fields +:return: A new instance

+
+ +
+
+update(instance, validated_data)[source]
+

Update exists instance +:param instance: Current instance +:param validated_data: A dict of fields +:return: Current instance with changes

+
+ +
+ +
+
+class user_management.serializers.group.GroupSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Group’ model

+
+ +
+
+

user_management.serializers.permission module

+
+
+class user_management.serializers.permission.PermissionSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Permission’ model

+
+ +
+
+

user_management.serializers.profile module

+
+
+class user_management.serializers.profile.ProfileSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Profile’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+ +
+
+

user_management.serializers.user module

+
+
+class user_management.serializers.user.UserPasswordChangeSerializer(*args, **kwargs)[source]
+

Bases: Serializer

+

Serializer for user model password change

+
+
+save(**kwargs)[source]
+
+ +
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate(data)[source]
+
+ +
+
+validate_new_password1(value: str) str[source]
+
+ +
+
+validate_new_password2(value: str) str[source]
+
+ +
+
+validate_old_password(value)[source]
+
+ +
+ +
+
+class user_management.serializers.user.UserSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘User’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate_password(value: str) str[source]
+
+ +
+ +
+
+

Module contents

+
+
+class user_management.serializers.CreateGroupSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for creating a new ‘Group’ instance

+
+
+create(validated_data)[source]
+

Create a new instance +:param validated_data: A dict of fields +:return: A new instance

+
+ +
+
+update(instance, validated_data)[source]
+

Update exists instance +:param instance: Current instance +:param validated_data: A dict of fields +:return: Current instance with changes

+
+ +
+ +
+
+class user_management.serializers.GroupSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Group’ model

+
+ +
+
+class user_management.serializers.PermissionSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Permission’ model

+
+ +
+
+class user_management.serializers.ProfileSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘Profile’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+ +
+
+class user_management.serializers.UserPasswordChangeSerializer(*args, **kwargs)[source]
+

Bases: Serializer

+

Serializer for user model password change

+
+
+save(**kwargs)[source]
+
+ +
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate(data)[source]
+
+ +
+
+validate_new_password1(value: str) str[source]
+
+ +
+
+validate_new_password2(value: str) str[source]
+
+ +
+
+validate_old_password(value)[source]
+
+ +
+ +
+
+class user_management.serializers.UserSerializer(*args, **kwargs)[source]
+

Bases: ModelSerializer

+

Serializer for the ‘User’ model

+
+
+to_representation(obj)[source]
+

Object instance -> Dict of primitive datatypes.

+
+ +
+
+validate_password(value: str) str[source]
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/user_management.tests.html b/docs/_build/html/_rst/user_management.tests.html new file mode 100644 index 00000000..2a2ab2c8 --- /dev/null +++ b/docs/_build/html/_rst/user_management.tests.html @@ -0,0 +1,240 @@ + + + + + + + user_management.tests package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

user_management.tests package

+
+

Submodules

+
+
+

user_management.tests.common_functions module

+
+
+user_management.tests.common_functions.sample_group(**params)[source]
+

Create and return a sample group object

+
+ +
+
+user_management.tests.common_functions.sample_superuser(**params)[source]
+

Create and return a sample superuser object

+
+ +
+
+user_management.tests.common_functions.sample_user(**params)[source]
+

Create and return a sample user object

+
+ +
+
+

user_management.tests.test_group_model module

+
+
+class user_management.tests.test_group_model.GroupModelTest(methodName='runTest')[source]
+

Bases: TestCase

+
+
+test_create_group_successful()[source]
+

Test creating a new group is successful

+
+ +
+
+test_create_group_with_permissions_successful()[source]
+

Test creating a new group with permissions is successful

+
+ +
+
+test_create_new_group_with_duplicate_name_unsuccessful()[source]
+

Test creating a new group with duplicate name is unsuccessful

+
+ +
+
+test_creating_group_with_none_name()[source]
+

Test creating a new group with a none name

+
+ +
+ +
+
+

user_management.tests.test_user_model module

+
+
+class user_management.tests.test_user_model.UserModelTest(methodName='runTest')[source]
+

Bases: TestCase

+
+
+test_create_new_superuser_with_duplicate_username_unsuccessful()[source]
+

Test creating a new superuser with duplicate username is unsuccessful

+
+ +
+
+test_create_new_user_with_duplicate_username_unsuccessful()[source]
+

Test creating a new user with duplicate username is unsuccessful

+
+ +
+
+test_create_user_with_username_successful()[source]
+

Test creating a new user with a username is successful

+
+ +
+
+test_creating_new_superuser()[source]
+

Test creating a new superuser

+
+ +
+
+test_creating_user_with_invalid_username()[source]
+

Test creating a new user with an invalid username

+
+ +
+
+test_creating_user_with_none_username()[source]
+

Test creating a new user with a none username

+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_rst/user_management.views.html b/docs/_build/html/_rst/user_management.views.html new file mode 100644 index 00000000..fc47d61a --- /dev/null +++ b/docs/_build/html/_rst/user_management.views.html @@ -0,0 +1,598 @@ + + + + + + + user_management.views package — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

user_management.views package

+
+

Submodules

+
+
+

user_management.views.group module

+
+
+class user_management.views.group.GroupViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Group’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+destroy(request, *args, **kwargs)[source]
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ('name',)
+
+ +
+
+get_serializer_class()[source]
+

Return the class to use for the serializer. +Defaults to using self.serializer_class.

+

You may want to override this if you need to provide different +serializations depending on the incoming request.

+

(Eg. admins get full serialization, others get basic serialization)

+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name']
+
+ +
+
+serializer_class
+

alias of GroupSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

user_management.views.permission module

+
+
+class user_management.views.permission.PermissionViewSet(**kwargs)[source]
+

Bases: ReadOnlyModelViewSet

+

ViewSet for the ‘Permission’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['name', 'content_type', 'codename']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name']
+
+ +
+
+serializer_class
+

alias of PermissionSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+

user_management.views.user module

+
+
+class user_management.views.user.UserViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘User’ model objects

+
+
+basename = None
+
+ +
+
+change_password(request, pk=None)[source]
+

This will use for change the current user password +:return: The current user information

+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['username', 'is_active', 'is_superuser', 'is_staff']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = [<rest_framework.permissions.OperandHolder object>]
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['username']
+
+ +
+
+serializer_class
+

alias of UserSerializer

+
+ +
+
+suffix = None
+
+ +
+
+update_user_profile(request, pk=None)[source]
+

This will use for change the current user profile +:return: The current user profile

+
+ +
+
+user_profile(request, pk=None)[source]
+

This will use for getting the current user profile +:return: The current user profile

+
+ +
+ +
+
+

Module contents

+
+
+class user_management.views.GroupViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘Group’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+destroy(request, *args, **kwargs)[source]
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ('name',)
+
+ +
+
+get_serializer_class()[source]
+

Return the class to use for the serializer. +Defaults to using self.serializer_class.

+

You may want to override this if you need to provide different +serializations depending on the incoming request.

+

(Eg. admins get full serialization, others get basic serialization)

+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name']
+
+ +
+
+serializer_class
+

alias of GroupSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class user_management.views.PermissionViewSet(**kwargs)[source]
+

Bases: ReadOnlyModelViewSet

+

ViewSet for the ‘Permission’ model objects

+
+
+basename = None
+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['name', 'content_type', 'codename']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = (<class 'extensions.custom_permissions.CustomDjangoModelPermission'>,)
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['name']
+
+ +
+
+serializer_class
+

alias of PermissionSerializer

+
+ +
+
+suffix = None
+
+ +
+ +
+
+class user_management.views.UserViewSet(**kwargs)[source]
+

Bases: ModelViewSet

+

ViewSet for the ‘User’ model objects

+
+
+basename = None
+
+ +
+
+change_password(request, pk=None)[source]
+

This will use for change the current user password +:return: The current user information

+
+ +
+
+description = None
+
+ +
+
+detail = None
+
+ +
+
+filter_backends = [<class 'django_filters.rest_framework.backends.DjangoFilterBackend'>, <class 'rest_framework.filters.SearchFilter'>]
+
+ +
+
+filterset_fields = ['username', 'is_active', 'is_superuser', 'is_staff']
+
+ +
+
+name = None
+
+ +
+
+permission_classes = [<rest_framework.permissions.OperandHolder object>]
+
+ +
+
+queryset = QuerySet
+
+ +
+
+search_fields = ['username']
+
+ +
+
+serializer_class
+

alias of UserSerializer

+
+ +
+
+suffix = None
+
+ +
+
+update_user_profile(request, pk=None)[source]
+

This will use for change the current user profile +:return: The current user profile

+
+ +
+
+user_profile(request, pk=None)[source]
+

This will use for getting the current user profile +:return: The current user profile

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_sources/_rst/api.rst.txt b/docs/_build/html/_sources/_rst/api.rst.txt new file mode 100644 index 00000000..3ac8f213 --- /dev/null +++ b/docs/_build/html/_sources/_rst/api.rst.txt @@ -0,0 +1,37 @@ +api package +=========== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + api.tests + +Submodules +---------- + +api.apps module +--------------- + +.. automodule:: api.apps + :members: + :undoc-members: + :show-inheritance: + +api.urls module +--------------- + +.. automodule:: api.urls + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/api.tests.rst.txt b/docs/_build/html/_sources/_rst/api.tests.rst.txt new file mode 100644 index 00000000..1f3f72d5 --- /dev/null +++ b/docs/_build/html/_sources/_rst/api.tests.rst.txt @@ -0,0 +1,37 @@ +api.tests package +================= + +Submodules +---------- + +api.tests.test\_group module +---------------------------- + +.. automodule:: api.tests.test_group + :members: + :undoc-members: + :show-inheritance: + +api.tests.test\_permission module +--------------------------------- + +.. automodule:: api.tests.test_permission + :members: + :undoc-members: + :show-inheritance: + +api.tests.test\_user module +--------------------------- + +.. automodule:: api.tests.test_user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/booking.models.rst.txt b/docs/_build/html/_sources/_rst/booking.models.rst.txt new file mode 100644 index 00000000..a0196bca --- /dev/null +++ b/docs/_build/html/_sources/_rst/booking.models.rst.txt @@ -0,0 +1,69 @@ +booking.models package +====================== + +Submodules +---------- + +booking.models.invoice module +----------------------------- + +.. automodule:: booking.models.invoice + :members: + :undoc-members: + :show-inheritance: + +booking.models.match module +--------------------------- + +.. automodule:: booking.models.match + :members: + :undoc-members: + :show-inheritance: + +booking.models.payment module +----------------------------- + +.. automodule:: booking.models.payment + :members: + :undoc-members: + :show-inheritance: + +booking.models.section module +----------------------------- + +.. automodule:: booking.models.section + :members: + :undoc-members: + :show-inheritance: + +booking.models.stadium module +----------------------------- + +.. automodule:: booking.models.stadium + :members: + :undoc-members: + :show-inheritance: + +booking.models.team module +-------------------------- + +.. automodule:: booking.models.team + :members: + :undoc-members: + :show-inheritance: + +booking.models.ticket module +---------------------------- + +.. automodule:: booking.models.ticket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.models + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/booking.rst.txt b/docs/_build/html/_sources/_rst/booking.rst.txt new file mode 100644 index 00000000..44e9afec --- /dev/null +++ b/docs/_build/html/_sources/_rst/booking.rst.txt @@ -0,0 +1,48 @@ +booking package +=============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + booking.migrations + booking.models + booking.serializers + booking.views + +Submodules +---------- + +booking.admin module +-------------------- + +.. automodule:: booking.admin + :members: + :undoc-members: + :show-inheritance: + +booking.apps module +------------------- + +.. automodule:: booking.apps + :members: + :undoc-members: + :show-inheritance: + +booking.urls module +------------------- + +.. automodule:: booking.urls + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/booking.serializers.rst.txt b/docs/_build/html/_sources/_rst/booking.serializers.rst.txt new file mode 100644 index 00000000..2379d0cf --- /dev/null +++ b/docs/_build/html/_sources/_rst/booking.serializers.rst.txt @@ -0,0 +1,69 @@ +booking.serializers package +=========================== + +Submodules +---------- + +booking.serializers.invoice module +---------------------------------- + +.. automodule:: booking.serializers.invoice + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.match module +-------------------------------- + +.. automodule:: booking.serializers.match + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.payment module +---------------------------------- + +.. automodule:: booking.serializers.payment + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.section module +---------------------------------- + +.. automodule:: booking.serializers.section + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.stadium module +---------------------------------- + +.. automodule:: booking.serializers.stadium + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.team module +------------------------------- + +.. automodule:: booking.serializers.team + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.ticket module +--------------------------------- + +.. automodule:: booking.serializers.ticket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.serializers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/booking.views.rst.txt b/docs/_build/html/_sources/_rst/booking.views.rst.txt new file mode 100644 index 00000000..2fc6a16f --- /dev/null +++ b/docs/_build/html/_sources/_rst/booking.views.rst.txt @@ -0,0 +1,69 @@ +booking.views package +===================== + +Submodules +---------- + +booking.views.invoice module +---------------------------- + +.. automodule:: booking.views.invoice + :members: + :undoc-members: + :show-inheritance: + +booking.views.match module +-------------------------- + +.. automodule:: booking.views.match + :members: + :undoc-members: + :show-inheritance: + +booking.views.payment module +---------------------------- + +.. automodule:: booking.views.payment + :members: + :undoc-members: + :show-inheritance: + +booking.views.section module +---------------------------- + +.. automodule:: booking.views.section + :members: + :undoc-members: + :show-inheritance: + +booking.views.stadium module +---------------------------- + +.. automodule:: booking.views.stadium + :members: + :undoc-members: + :show-inheritance: + +booking.views.team module +------------------------- + +.. automodule:: booking.views.team + :members: + :undoc-members: + :show-inheritance: + +booking.views.ticket module +--------------------------- + +.. automodule:: booking.views.ticket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.views + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/commands.rst.txt b/docs/_build/html/_sources/_rst/commands.rst.txt new file mode 100644 index 00000000..dfd7af1d --- /dev/null +++ b/docs/_build/html/_sources/_rst/commands.rst.txt @@ -0,0 +1,21 @@ +commands package +================ + +Submodules +---------- + +commands.apps module +-------------------- + +.. automodule:: commands.apps + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: commands + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/core.rst.txt b/docs/_build/html/_sources/_rst/core.rst.txt new file mode 100644 index 00000000..5dc8ee22 --- /dev/null +++ b/docs/_build/html/_sources/_rst/core.rst.txt @@ -0,0 +1,45 @@ +core package +============ + +Submodules +---------- + +core.asgi module +---------------- + +.. automodule:: core.asgi + :members: + :undoc-members: + :show-inheritance: + +core.settings module +-------------------- + +.. automodule:: core.settings + :members: + :undoc-members: + :show-inheritance: + +core.urls module +---------------- + +.. automodule:: core.urls + :members: + :undoc-members: + :show-inheritance: + +core.wsgi module +---------------- + +.. automodule:: core.wsgi + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/extensions.rst.txt b/docs/_build/html/_sources/_rst/extensions.rst.txt new file mode 100644 index 00000000..7bd4a1b4 --- /dev/null +++ b/docs/_build/html/_sources/_rst/extensions.rst.txt @@ -0,0 +1,45 @@ +extensions package +================== + +Submodules +---------- + +extensions.abstract\_models module +---------------------------------- + +.. automodule:: extensions.abstract_models + :members: + :undoc-members: + :show-inheritance: + +extensions.choices module +------------------------- + +.. automodule:: extensions.choices + :members: + :undoc-members: + :show-inheritance: + +extensions.custom\_exception\_handlers module +--------------------------------------------- + +.. automodule:: extensions.custom_exception_handlers + :members: + :undoc-members: + :show-inheritance: + +extensions.custom\_permissions module +------------------------------------- + +.. automodule:: extensions.custom_permissions + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: extensions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/manage.rst.txt b/docs/_build/html/_sources/_rst/manage.rst.txt new file mode 100644 index 00000000..776b9e34 --- /dev/null +++ b/docs/_build/html/_sources/_rst/manage.rst.txt @@ -0,0 +1,7 @@ +manage module +============= + +.. automodule:: manage + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/modules.rst.txt b/docs/_build/html/_sources/_rst/modules.rst.txt new file mode 100644 index 00000000..1668a9bd --- /dev/null +++ b/docs/_build/html/_sources/_rst/modules.rst.txt @@ -0,0 +1,13 @@ +src +=== + +.. toctree:: + :maxdepth: 4 + + api + booking + commands + core + extensions + manage + user_management diff --git a/docs/_build/html/_sources/_rst/user_management.models.rst.txt b/docs/_build/html/_sources/_rst/user_management.models.rst.txt new file mode 100644 index 00000000..901f8724 --- /dev/null +++ b/docs/_build/html/_sources/_rst/user_management.models.rst.txt @@ -0,0 +1,29 @@ +user\_management.models package +=============================== + +Submodules +---------- + +user\_management.models.profile module +-------------------------------------- + +.. automodule:: user_management.models.profile + :members: + :undoc-members: + :show-inheritance: + +user\_management.models.user module +----------------------------------- + +.. automodule:: user_management.models.user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.models + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/user_management.rst.txt b/docs/_build/html/_sources/_rst/user_management.rst.txt new file mode 100644 index 00000000..50a29c26 --- /dev/null +++ b/docs/_build/html/_sources/_rst/user_management.rst.txt @@ -0,0 +1,65 @@ +user\_management package +======================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + user_management.migrations + user_management.models + user_management.serializers + user_management.tests + user_management.views + +Submodules +---------- + +user\_management.admin module +----------------------------- + +.. automodule:: user_management.admin + :members: + :undoc-members: + :show-inheritance: + +user\_management.apps module +---------------------------- + +.. automodule:: user_management.apps + :members: + :undoc-members: + :show-inheritance: + +user\_management.managers module +-------------------------------- + +.. automodule:: user_management.managers + :members: + :undoc-members: + :show-inheritance: + +user\_management.signals module +------------------------------- + +.. automodule:: user_management.signals + :members: + :undoc-members: + :show-inheritance: + +user\_management.urls module +---------------------------- + +.. automodule:: user_management.urls + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/user_management.serializers.rst.txt b/docs/_build/html/_sources/_rst/user_management.serializers.rst.txt new file mode 100644 index 00000000..43a222bc --- /dev/null +++ b/docs/_build/html/_sources/_rst/user_management.serializers.rst.txt @@ -0,0 +1,45 @@ +user\_management.serializers package +==================================== + +Submodules +---------- + +user\_management.serializers.group module +----------------------------------------- + +.. automodule:: user_management.serializers.group + :members: + :undoc-members: + :show-inheritance: + +user\_management.serializers.permission module +---------------------------------------------- + +.. automodule:: user_management.serializers.permission + :members: + :undoc-members: + :show-inheritance: + +user\_management.serializers.profile module +------------------------------------------- + +.. automodule:: user_management.serializers.profile + :members: + :undoc-members: + :show-inheritance: + +user\_management.serializers.user module +---------------------------------------- + +.. automodule:: user_management.serializers.user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.serializers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/user_management.tests.rst.txt b/docs/_build/html/_sources/_rst/user_management.tests.rst.txt new file mode 100644 index 00000000..2e4ce934 --- /dev/null +++ b/docs/_build/html/_sources/_rst/user_management.tests.rst.txt @@ -0,0 +1,37 @@ +user\_management.tests package +============================== + +Submodules +---------- + +user\_management.tests.common\_functions module +----------------------------------------------- + +.. automodule:: user_management.tests.common_functions + :members: + :undoc-members: + :show-inheritance: + +user\_management.tests.test\_group\_model module +------------------------------------------------ + +.. automodule:: user_management.tests.test_group_model + :members: + :undoc-members: + :show-inheritance: + +user\_management.tests.test\_user\_model module +----------------------------------------------- + +.. automodule:: user_management.tests.test_user_model + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/_rst/user_management.views.rst.txt b/docs/_build/html/_sources/_rst/user_management.views.rst.txt new file mode 100644 index 00000000..f60d4f19 --- /dev/null +++ b/docs/_build/html/_sources/_rst/user_management.views.rst.txt @@ -0,0 +1,37 @@ +user\_management.views package +============================== + +Submodules +---------- + +user\_management.views.group module +----------------------------------- + +.. automodule:: user_management.views.group + :members: + :undoc-members: + :show-inheritance: + +user\_management.views.permission module +---------------------------------------- + +.. automodule:: user_management.views.permission + :members: + :undoc-members: + :show-inheritance: + +user\_management.views.user module +---------------------------------- + +.. automodule:: user_management.views.user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.views + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt new file mode 100644 index 00000000..3cd1d8ff --- /dev/null +++ b/docs/_build/html/_sources/index.rst.txt @@ -0,0 +1,22 @@ +.. Ticketing system documentation master file, created by + sphinx-quickstart on Wed Oct 25 21:55:00 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Ticketing system's documentation! +============================================ + +.. toctree:: + :maxdepth: 6 + :caption: Contents: + + _rst/modules + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css new file mode 100644 index 00000000..7577acb1 --- /dev/null +++ b/docs/_build/html/_static/basic.css @@ -0,0 +1,903 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_build/html/_static/css/badge_only.css b/docs/_build/html/_static/css/badge_only.css new file mode 100644 index 00000000..c718cee4 --- /dev/null +++ b/docs/_build/html/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff b/docs/_build/html/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff2 b/docs/_build/html/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff b/docs/_build/html/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff2 b/docs/_build/html/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/_build/html/_static/css/theme.css b/docs/_build/html/_static/css/theme.css new file mode 100644 index 00000000..19a446a0 --- /dev/null +++ b/docs/_build/html/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/docs/_build/html/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js new file mode 100644 index 00000000..ac431fff --- /dev/null +++ b/docs/_build/html/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '0.0.1', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/_build/html/_static/file.png differ diff --git a/docs/_build/html/_static/jquery.js b/docs/_build/html/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/docs/_build/html/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_build/html/_static/js/html5shiv.min.js b/docs/_build/html/_static/js/html5shiv.min.js new file mode 100644 index 00000000..cd1c674f --- /dev/null +++ b/docs/_build/html/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_build/html/_static/js/theme.js b/docs/_build/html/_static/js/theme.js new file mode 100644 index 00000000..1fddb6ee --- /dev/null +++ b/docs/_build/html/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/docs/_build/html/_static/minus.png differ diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/docs/_build/html/_static/plus.png differ diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css new file mode 100644 index 00000000..84ab3030 --- /dev/null +++ b/docs/_build/html/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/searchtools.js b/docs/_build/html/_static/searchtools.js new file mode 100644 index 00000000..97d56a74 --- /dev/null +++ b/docs/_build/html/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/_build/html/_static/sphinx_highlight.js b/docs/_build/html/_static/sphinx_highlight.js new file mode 100644 index 00000000..aae669d7 --- /dev/null +++ b/docs/_build/html/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html new file mode 100644 index 00000000..87f99362 --- /dev/null +++ b/docs/_build/html/genindex.html @@ -0,0 +1,2851 @@ + + + + + + Index — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ _ + | A + | B + | C + | D + | E + | F + | G + | H + | I + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + +
+

_

+ + +
+ +

A

+ + + +
+ +

B

+ + + +
    +
  • + booking.models.team + +
  • +
  • + booking.models.ticket + +
  • +
  • + booking.serializers + +
  • +
  • + booking.serializers.invoice + +
  • +
  • + booking.serializers.match + +
  • +
  • + booking.serializers.payment + +
  • +
  • + booking.serializers.section + +
  • +
  • + booking.serializers.stadium + +
  • +
  • + booking.serializers.team + +
  • +
  • + booking.serializers.ticket + +
  • +
  • + booking.urls + +
  • +
  • + booking.views + +
  • +
  • + booking.views.invoice + +
  • +
  • + booking.views.match + +
  • +
  • + booking.views.payment + +
  • +
  • + booking.views.section + +
  • +
  • + booking.views.stadium + +
  • +
  • + booking.views.team + +
  • +
  • + booking.views.ticket + +
  • +
  • BookingConfig (class in booking.apps) +
  • +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

L

+ + + +
+ +

M

+ + +
+ +

N

+ + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html new file mode 100644 index 00000000..c35f867d --- /dev/null +++ b/docs/_build/html/index.html @@ -0,0 +1,613 @@ + + + + + + + Welcome to Ticketing system’s documentation! — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Welcome to Ticketing system’s documentation!

+
+

Contents:

+ +
+
+
+

Indices and tables

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv new file mode 100644 index 00000000..db6fe5e1 Binary files /dev/null and b/docs/_build/html/objects.inv differ diff --git a/docs/_build/html/py-modindex.html b/docs/_build/html/py-modindex.html new file mode 100644 index 00000000..fc651cfe --- /dev/null +++ b/docs/_build/html/py-modindex.html @@ -0,0 +1,490 @@ + + + + + + Python Module Index — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ a | + b | + c | + e | + m | + u +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ a
+ api +
    + api.apps +
    + api.tests +
    + api.tests.test_group +
    + api.tests.test_permission +
    + api.tests.test_user +
    + api.urls +
 
+ b
+ booking +
    + booking.admin +
    + booking.apps +
    + booking.models +
    + booking.models.invoice +
    + booking.models.match +
    + booking.models.payment +
    + booking.models.section +
    + booking.models.stadium +
    + booking.models.team +
    + booking.models.ticket +
    + booking.serializers +
    + booking.serializers.invoice +
    + booking.serializers.match +
    + booking.serializers.payment +
    + booking.serializers.section +
    + booking.serializers.stadium +
    + booking.serializers.team +
    + booking.serializers.ticket +
    + booking.urls +
    + booking.views +
    + booking.views.invoice +
    + booking.views.match +
    + booking.views.payment +
    + booking.views.section +
    + booking.views.stadium +
    + booking.views.team +
    + booking.views.ticket +
 
+ c
+ commands +
    + commands.apps +
+ core +
    + core.asgi +
    + core.settings +
    + core.urls +
    + core.wsgi +
 
+ e
+ extensions +
    + extensions.abstract_models +
    + extensions.choices +
    + extensions.custom_exception_handlers +
    + extensions.custom_permissions +
 
+ m
+ manage +
 
+ u
+ user_management +
    + user_management.admin +
    + user_management.apps +
    + user_management.managers +
    + user_management.models +
    + user_management.models.profile +
    + user_management.models.user +
    + user_management.serializers +
    + user_management.serializers.group +
    + user_management.serializers.permission +
    + user_management.serializers.profile +
    + user_management.serializers.user +
    + user_management.signals +
    + user_management.tests +
    + user_management.tests.common_functions +
    + user_management.tests.test_group_model +
    + user_management.tests.test_user_model +
    + user_management.urls +
    + user_management.views +
    + user_management.views.group +
    + user_management.views.permission +
    + user_management.views.user +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html new file mode 100644 index 00000000..87671b88 --- /dev/null +++ b/docs/_build/html/search.html @@ -0,0 +1,120 @@ + + + + + + Search — Ticketing system 0.0.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js new file mode 100644 index 00000000..3c5f4a4d --- /dev/null +++ b/docs/_build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["_rst/api", "_rst/api.tests", "_rst/booking", "_rst/booking.models", "_rst/booking.serializers", "_rst/booking.views", "_rst/commands", "_rst/core", "_rst/extensions", "_rst/manage", "_rst/modules", "_rst/user_management", "_rst/user_management.models", "_rst/user_management.serializers", "_rst/user_management.tests", "_rst/user_management.views", "index"], "filenames": ["_rst/api.rst", "_rst/api.tests.rst", "_rst/booking.rst", "_rst/booking.models.rst", "_rst/booking.serializers.rst", "_rst/booking.views.rst", "_rst/commands.rst", "_rst/core.rst", "_rst/extensions.rst", "_rst/manage.rst", "_rst/modules.rst", "_rst/user_management.rst", "_rst/user_management.models.rst", "_rst/user_management.serializers.rst", "_rst/user_management.tests.rst", "_rst/user_management.views.rst", "index.rst"], "titles": ["api package", "api.tests package", "booking package", "booking.models package", "booking.serializers package", "booking.views package", "commands package", "core package", "extensions package", "manage module", "src", "user_management package", "user_management.models package", "user_management.serializers package", "user_management.tests package", "user_management.views package", "Welcome to Ticketing system\u2019s documentation!"], "terms": {"config": 7, "project": 7, "It": [3, 4, 7, 11], "expos": 7, "callabl": 7, "level": [7, 8], "variabl": 7, "name": [0, 1, 2, 3, 5, 6, 7, 10, 11, 12, 14, 15, 16], "applic": 7, "For": 7, "more": [3, 7, 8, 12], "inform": [3, 7, 8, 12, 15], "thi": [2, 3, 5, 7, 11, 12, 15], "file": 7, "see": [3, 7, 8, 12], "http": 7, "doc": 7, "djangoproject": 7, "com": 7, "en": 7, "4": 7, "2": [3, 7, 8], "howto": 7, "deploy": 7, "django": [0, 2, 3, 6, 7, 8, 9, 11, 12], "gener": 7, "admin": [7, 10, 15, 16], "startproject": 7, "us": [2, 3, 4, 5, 7, 11, 12, 15], "6": 7, "topic": 7, "full": [7, 15], "list": [3, 7], "valu": [7, 8, 13], "ref": 7, "configur": 7, "The": [5, 7, 12, 15], "urlpattern": 7, "rout": 7, "view": [2, 7, 8, 10, 11, 16], "pleas": 7, "exampl": 7, "function": 7, "add": [2, 7], "an": [2, 7, 8, 12, 14], "import": 7, "from": [3, 7, 12], "my_app": 7, "path": 7, "home": 7, "class": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15], "base": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15], "other_app": 7, "as_view": 7, "includ": 7, "anoth": 7, "urlconf": 7, "blog": 7, "": 9, "command": [9, 10, 16], "line": 9, "util": 9, "administr": 9, "task": 9, "main": [9, 10, 16], "sourc": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "run": [9, 11], "core": [10, 16], "packag": [10, 16], "submodul": [10, 16], "asgi": [10, 16], "modul": [10, 16], "set": [1, 10, 11, 12, 16], "url": [10, 16], "wsgi": [10, 16], "content": 10, "manag": [3, 10, 12, 16], "src": 16, "index": 16, "search": 16, "page": 16, "test": [0, 10, 11, 16], "test_group": [0, 10, 16], "privategroupsapitest": [0, 1, 16], "setup": [0, 1], "test_create_group_success": [0, 1, 11, 14], "test_create_group_with_duplicate_name_unsuccess": [0, 1], "test_create_group_with_invalid_name_unsuccess": [0, 1], "test_create_group_with_invalid_permission_unsuccess": [0, 1], "test_create_group_with_permissions_success": [0, 1, 11, 14], "test_delete_group_success": [0, 1], "test_delete_group_unsuccess": [0, 1], "test_retrieve_groups_success": [0, 1], "test_retrieve_single_group": [0, 1], "test_update_group_success": [0, 1], "test_update_group_unsuccess": [0, 1], "publicgroupsapitest": [0, 1, 16], "test_login_requir": [0, 1], "test_permiss": [0, 10, 16], "privatepermissionsapitest": [0, 1, 16], "test_create_permission_not_allow": [0, 1], "test_delete_permission_not_allow": [0, 1], "test_retrieve_permiss": [0, 1], "test_retrieve_single_permiss": [0, 1], "test_update_permission_not_allow": [0, 1], "publicpermissionsapitest": [0, 1, 16], "test_us": [0, 10, 16], "privateusersapitest": [0, 1, 16], "test_create_superuser_success": [0, 1], "test_create_user_success": [0, 1], "test_create_user_with_duplicate_username_unsuccess": [0, 1], "test_create_user_with_group_success": [0, 1], "test_create_user_with_group_with_permission_to_do_something_success": [0, 1], "test_create_user_with_group_without_permission_to_do_something_unsuccess": [0, 1], "test_create_user_with_invalid_password_unsuccess": [0, 1], "test_create_user_with_invalid_username_unsuccess": [0, 1], "test_create_user_with_permission_success": [0, 1], "test_delete_user_success": [0, 1], "test_delete_user_unsuccess": [0, 1], "test_retrieve_single_user_success": [0, 1], "test_retrieve_users_success": [0, 1], "test_update_user_success": [0, 1], "test_update_user_unsuccess": [0, 1], "publicusersapitest": [0, 1, 16], "apiconfig": [0, 10, 16], "app_nam": [0, 2, 6, 11], "app_modul": [0, 2, 6, 11], "appconfig": [0, 2, 6, 11], "default_auto_field": [0, 2, 6, 10, 11, 16], "db": [0, 2, 3, 6, 8, 11, 12], "model": [0, 2, 4, 5, 6, 8, 10, 11, 13, 15, 16], "bigautofield": [0, 2, 3, 6, 11, 12], "methodnam": [1, 14], "runtest": [1, 14], "testcas": [1, 14], "author": 1, "user": [1, 2, 3, 5, 8, 10, 11, 14, 16], "group": [1, 10, 11, 12, 14, 16], "none": [1, 2, 3, 5, 11, 12, 14, 15], "hook": 1, "method": [1, 11], "up": 1, "fixtur": 1, "befor": 1, "exercis": 1, "creat": [1, 2, 4, 11, 13, 14], "new": [1, 4, 11, 13, 14], "i": [1, 2, 8, 11, 12, 14], "success": [1, 8, 10, 14, 16], "duplic": [1, 14], "unsuccess": [1, 8, 10, 14, 16], "invalid": [1, 14], "permiss": [1, 2, 8, 10, 11, 12, 14, 16], "delet": [1, 2], "retriev": 1, "singl": 1, "updat": [1, 8, 11, 12, 13], "public": 1, "avail": [1, 11], "login": 1, "requir": 1, "allow": [1, 8], "superus": [1, 14], "usernam": [1, 11, 12, 14, 15], "do": 1, "someth": 1, "without": 1, "password": [1, 11, 12, 13, 15], "commandsconfig": [6, 10, 16], "verbose_nam": [2, 6, 10, 11, 16], "\u062f\u0633\u062a\u0648\u0631\u0627\u062a": 6, "abstractcreatatupdateat": [3, 8, 10, 12, 16], "arg": [4, 8, 11, 12, 13, 15], "kwarg": [3, 4, 5, 8, 11, 12, 13, 15], "paramet": [2, 3, 8, 12], "created_at": [2, 3, 5, 8, 10, 11, 12, 16], "datetimefield": [3, 8, 12], "\u0627\u06cc\u062c\u0627\u062f": [3, 8, 12], "\u0634\u062f\u0647": [3, 8, 12], "\u062f\u0631": [3, 8, 12], "updated_at": [2, 3, 8, 10, 11, 12, 16], "\u0628\u0647": [3, 8, 12], "\u0631\u0648\u0632": [3, 8, 12], "type": [2, 3, 8, 12], "get_next_by_created_at": [2, 3, 8, 10, 11, 12, 16], "field": [3, 4, 8, 11, 12, 13], "is_next": [3, 8, 12], "true": [2, 3, 8, 11, 12], "find": [3, 8, 12], "next": [3, 8, 12], "instanc": [2, 3, 4, 8, 11, 12, 13], "get_next_by_foo": [3, 8, 12], "get_next_by_updated_at": [2, 3, 8, 10, 11, 12, 16], "get_previous_by_created_at": [2, 3, 8, 10, 11, 12, 16], "fals": [3, 8, 12], "previou": [3, 8, 12], "get_previous_by_foo": [3, 8, 12], "get_previous_by_updated_at": [2, 3, 8, 10, 11, 12, 16], "genderchoic": [8, 10, 16], "integerchoic": 8, "enum": 8, "gender": [8, 11, 12], "femal": [8, 10, 16], "1": [2, 3, 8, 12], "male": [8, 10, 16], "0": [2, 3, 8, 12], "exception_handl": [8, 10, 16], "exc": 8, "context": 8, "handl": 8, "validationerror": 8, "accept": 8, "except": [3, 8, 12], "customdjangomodelpermiss": [5, 8, 10, 15, 16], "djangomodelpermiss": 8, "__init__": [8, 10, 16], "ownprofilepermiss": [], "basepermiss": 8, "object": [2, 3, 4, 5, 8, 11, 12, 13, 14, 15], "onli": 8, "hi": 8, "own": 8, "profil": [10, 11, 15, 16], "has_object_permiss": [8, 10, 16], "request": [2, 5, 8, 11, 15], "obj": [2, 4, 8, 11, 13], "return": [2, 3, 4, 5, 8, 11, 13, 14, 15], "grant": 8, "otherwis": 8, "ownuserpermiss": [8, 10, 16], "unauthenticatedpost": [8, 10, 16], "has_permiss": [8, 10, 16], "api": [10, 16], "subpackag": [10, 16], "app": [10, 16], "extens": [5, 10, 15, 16], "abstract_model": [10, 16], "choic": [3, 10, 12, 16], "custom_exception_handl": [10, 16], "custom_permiss": [5, 10, 15, 16], "user_manag": [10, 16], "serial": [2, 10, 11, 15, 16], "common_funct": [10, 11, 16], "test_group_model": [10, 11, 16], "test_user_model": [10, 11, 16], "profileinlineadmin": [10, 11, 16], "media": [2, 10, 11, 16], "useradmin": [10, 11, 16], "action": [2, 10, 11, 16], "add_fieldset": [10, 11, 16], "get_full_nam": [10, 11, 12, 16], "get_queryset": [2, 10, 11, 16], "inlin": [2, 10, 11, 16], "list_displai": [2, 10, 11, 16], "list_filt": [2, 10, 11, 16], "readonly_field": [10, 11, 16], "search_field": [2, 5, 10, 11, 15, 16], "usermanagementconfig": [10, 11, 16], "readi": [10, 11, 16], "usermanag": [10, 11, 12, 16], "create_staff": [10, 11, 16], "create_superus": [10, 11, 16], "create_us": [10, 11, 16], "use_in_migr": [10, 11, 16], "signal": [10, 16], "creating_profil": [10, 11, 16], "doesnotexist": [2, 3, 11, 12], "multipleobjectsreturn": [2, 3, 11, 12], "first_nam": [11, 12], "get_gender_displai": [11, 12], "last_nam": [11, 12], "user_id": [2, 3, 11, 12], "email_field": [11, 12], "required_field": [11, 12], "username_field": [11, 12], "id": [2, 3, 11, 12], "is_act": [11, 12, 15], "is_staff": [11, 12, 15], "is_superus": [11, 12, 15], "last_login": [11, 12], "logentry_set": [11, 12], "profile_us": [11, 12], "save": [3, 11, 12, 13], "user_permiss": [11, 12], "creategroupseri": [11, 13, 16], "groupseri": [11, 13, 15, 16], "permissionseri": [11, 13, 15, 16], "profileseri": [11, 13, 16], "to_represent": [2, 4, 11, 13], "userpasswordchangeseri": [11, 13, 16], "valid": [2, 4, 11, 13], "validate_new_password1": [11, 13], "validate_new_password2": [11, 13], "validate_old_password": [11, 13], "userseri": [11, 13, 15, 16], "validate_password": [11, 13], "sample_group": [11, 14, 16], "sample_superus": [11, 14, 16], "sample_us": [11, 14, 16], "groupmodeltest": [11, 14, 16], "test_create_new_group_with_duplicate_name_unsuccess": [11, 14], "test_creating_group_with_none_nam": [11, 14], "usermodeltest": [11, 14, 16], "test_create_new_superuser_with_duplicate_username_unsuccess": [11, 14], "test_create_new_user_with_duplicate_username_unsuccess": [11, 14], "test_create_user_with_username_success": [11, 14], "test_creating_new_superus": [11, 14], "test_creating_user_with_invalid_usernam": [11, 14], "test_creating_user_with_none_usernam": [11, 14], "groupviewset": [11, 15, 16], "basenam": [2, 5, 11, 15], "descript": [2, 5, 11, 15], "destroi": [11, 15], "detail": [2, 5, 11, 15], "filter_backend": [2, 5, 11, 15], "filterset_field": [2, 5, 11, 15], "get_serializer_class": [11, 15], "permission_class": [2, 5, 11, 15], "queryset": [2, 5, 11, 15], "serializer_class": [2, 5, 11, 15], "suffix": [2, 5, 11, 15], "permissionviewset": [11, 15, 16], "userviewset": [11, 15, 16], "change_password": [11, 15], "update_user_profil": [11, 15], "user_profil": [11, 15], "parent_model": [2, 11], "admin_sit": [2, 11], "tabularinlin": [2, 11], "properti": [2, 3, 11, 12], "alia": [2, 5, 11, 15], "delete_select": [2, 11], "wide": 11, "password1": 11, "password2": 11, "\u06a9\u0646\u062a\u0631\u0644": 11, "\u062f\u0633\u062a\u0631\u0633\u06cc": [11, 12], "all": [2, 3, 11, 12], "can": [2, 11, 12], "edit": [2, 11], "site": [2, 11], "changelist_view": [2, 11], "overrid": [11, 12, 15], "subclass": [2, 11, 12], "code": 11, "when": 11, "start": 11, "\u0645\u062f\u06cc\u0631\u06cc\u062a": [11, 12], "\u06a9\u0627\u0631\u0628\u0631": [3, 11, 12], "baseusermanag": 11, "extra_field": 11, "staff": 11, "super": 11, "regular": 11, "If": [2, 11], "migrat": 11, "thu": 11, "e": 11, "g": 11, "runpython": 11, "oper": 11, "sender": 11, "raw": 11, "databas": [3, 12], "tabl": [3, 12], "user_management_profil": 12, "charfield": [3, 12], "\u0646\u0627\u0645": [3, 12], "\u062e\u0627\u0646\u0648\u0627\u062f\u06af\u06cc": 12, "positivesmallintegerfield": [3, 12], "\u062c\u0646\u0633\u06cc\u062a": 12, "relationship": [3, 12], "onetoonefield": 12, "primari": [3, 12], "kei": [3, 12], "relat": [3, 12], "objectdoesnotexist": [3, 12], "str": [12, 13], "show": [3, 5, 12], "label": [3, 12], "get_foo_displai": [3, 12], "intern": [3, 12], "instead": [3, 12], "abstractbaseus": 12, "permissionsmixin": 12, "user_management_us": 12, "\u06af\u0630\u0631\u0648\u0627\u0698\u0647": 12, "\u0622\u062e\u0631\u06cc\u0646": 12, "\u0648\u0631\u0648\u062f": 12, "booleanfield": 12, "\u0627\u0628\u0631\u06a9\u0627\u0631\u0628\u0631": 12, "\u0646\u0634\u0627\u0646": 12, "\u0645\u06cc": 12, "\u062f\u0647\u062f": 12, "\u06a9\u0647": 12, "\u0627\u06cc\u0646": 12, "\u0647\u0645\u0647": 12, "\u0627\u062c\u0627\u0632\u0647": 12, "\u0647\u0627": 12, "\u0631\u0627": 12, "\u062f\u0627\u0631\u062f": 12, "\u0628\u062f\u0648\u0646": 12, "\u0622\u0646\u06a9\u0647": 12, "\u0635\u0631\u0627\u062d\u062a": 12, "\u0627\u0648": 12, "\u0627\u062e\u062a\u0635\u0627\u0635": 12, "\u062f\u0627\u062f\u0647": 12, "\u0628\u0627\u0634\u062f": 12, "emailfield": 12, "\u06a9\u0627\u0631\u0628\u0631\u06cc": 12, "\u0628\u062e\u0634": 12, "\u0641\u0639\u0627\u0644": 12, "\u0628\u0648\u062f\u0646": 12, "manytomanyfield": 12, "\u06af\u0631\u0648\u0647": 12, "\u0647\u0627\u06cc\u06cc": 12, "\u0622\u0646\u0647\u0627": 12, "\u062a\u0639\u0644\u0642": 12, "\u062a\u0645\u0627\u0645": 12, "\u0647\u0627\u06cc": 12, "\u0645\u0631\u062a\u0628\u0637": 12, "\u0628\u0627": 12, "\u062f\u0631\u06cc\u0627\u0641\u062a": 12, "\u062e\u0648\u0627\u0647\u062f": 12, "\u06a9\u0631\u062f": 12, "user_set": 12, "\u062e\u0627\u0635": 12, "revers": [3, 12], "logentri": 12, "foreignkei": [3, 12], "\u0645\u0648\u0627\u0631\u062f": 12, "\u0627\u062a\u0641\u0627\u0642\u0627\u062a": 12, "current": [5, 12, 13, 15], "you": [12, 15], "want": [12, 15], "control": 12, "process": 12, "force_insert": 12, "force_upd": 12, "insist": 12, "must": 12, "sql": 12, "insert": 12, "equival": 12, "non": 12, "backend": [5, 12, 15], "respect": 12, "normal": 12, "thei": 12, "should": [2, 12], "modelseri": [4, 13], "validated_data": [4, 13], "param": [4, 13, 14], "A": [4, 8, 10, 13, 16], "dict": [4, 13], "exist": 13, "chang": [2, 3, 13, 15], "primit": [4, 13], "datatyp": [4, 13], "data": [4, 13], "sampl": 14, "modelviewset": [5, 15], "viewset": [5, 15], "django_filt": [5, 15], "rest_framework": [5, 15], "djangofilterbackend": [5, 15], "filter": [5, 15], "searchfilt": [5, 15], "default": [2, 15], "self": 15, "mai": 15, "need": 15, "provid": 15, "differ": 15, "depend": 15, "incom": 15, "eg": 15, "get": 15, "other": 15, "basic": 15, "readonlymodelviewset": [5, 15], "content_typ": 15, "codenam": 15, "pk": [5, 15], "operandhold": 15, "custom": [8, 12], "better": 8, "unauthent": 8, "send": [8, 12], "post": 8, "authent": 12, "email": 12, "address": [2, 3, 5, 12], "verif": 12, "activ": 12, "link": 12, "implement": [2, 12], "invoic": [2, 8, 10, 12, 16], "get_status_displai": [2, 3], "get_total_amount": [2, 3], "payment_invoic": [2, 3], "set_as_paid": [2, 3], "statu": [2, 3, 5, 8], "ticket_invoic": [2, 3], "match": [2, 10, 16], "get_next_by_start_tim": [2, 3], "get_previous_by_start_tim": [2, 3], "guest_team": [2, 3, 5], "guest_team_id": [2, 3], "host_team": [2, 3, 5], "host_team_id": [2, 3], "stadium": [2, 10, 16], "stadium_id": [2, 3], "start_tim": [2, 3, 5], "ticket_match": [2, 3], "payment": [2, 8, 10, 16], "amount": [2, 3, 5], "invoice_id": [2, 3], "section": [2, 10, 16], "capac": [2, 3, 5], "get_list_of_seat_numb": [2, 3], "get_location_displai": [2, 3], "locat": [2, 3, 5, 8], "price": [2, 3, 5], "ticket_sect": [2, 3], "citi": [2, 3, 5], "get_section_id": [2, 3], "map_url": [2, 3], "match_stadium": [2, 3], "provinc": [2, 3, 5], "section_stadium": [2, 3], "team": [2, 10, 16], "match_guest_team": [2, 3], "match_host_team": [2, 3], "ticket": [2, 8, 10], "match_id": [2, 3], "seat_numb": [2, 3, 5], "section_id": [2, 3], "set_as_sold": [2, 3], "invoiceseri": [2, 4, 5, 16], "matchseri": [2, 4, 5, 16], "paymentseri": [2, 4, 5, 16], "sectionseri": [2, 4, 5, 16], "stadiumseri": [2, 4, 5, 16], "teamseri": [2, 4, 5, 16], "ticketseri": [2, 4, 5, 16], "invoiceviewset": [2, 5, 16], "matchviewset": [2, 5, 16], "seat": [2, 3, 5], "paymentviewset": [2, 5, 16], "sectionviewset": [2, 5, 16], "stadiumviewset": [2, 5, 16], "teamviewset": [2, 5, 16], "ticketviewset": [2, 5, 16], "invoiceadmin": [2, 10, 16], "modeladmin": 2, "has_add_permiss": [2, 10, 16], "given": 2, "ha": 2, "overridden": 2, "has_change_permiss": [2, 10, 16], "doesn": 2, "t": 2, "examin": 2, "In": 2, "case": 2, "ani": 2, "has_delete_permiss": [2, 10, 16], "matchadmin": [2, 10, 16], "paymentadmin": [2, 10, 16], "sectioninlineadmin": [2, 10, 16], "extra": [2, 10, 16], "min_num": [2, 10, 16], "stadiumadmin": [2, 10, 16], "teamadmin": [2, 10, 16], "ticketadmin": [2, 10, 16], "bookingconfig": [2, 10, 16], "\u0631\u0632\u0631\u0648": 2, "booking_invoic": 3, "\u0648\u0636\u0639\u06cc\u062a": 3, "invoice_us": [3, 11, 12], "\u0641\u0627\u06a9\u062a\u0648\u0631": 3, "decim": 3, "booking_match": 3, "\u062a\u0627\u0631\u06cc\u062e": 3, "\u0622\u063a\u0627\u0632": 3, "\u0627\u0633\u062a\u0627\u062f\u06cc\u0648\u0645": 3, "\u062a\u06cc\u0645": 3, "\u0645\u06cc\u0632\u0628\u0627\u0646": 3, "\u0645\u0647\u0645\u0627\u0646": 3, "\u0645\u0633\u0627\u0628\u0642\u0647": 3, "booking_pay": 3, "decimalfield": 3, "\u0645\u0628\u0644\u063a": 3, "booking_sect": 3, "\u0645\u0648\u0642\u0639\u06cc\u062a": 3, "\u0638\u0631\u0641\u06cc\u062a": 3, "\u0642\u06cc\u0645\u062a": 3, "\u062c\u0627\u06cc\u06af\u0627\u0647": 3, "3": [3, 8], "booking_stadium": 3, "\u0627\u0633\u062a\u0627\u0646": 3, "\u0634\u0647\u0631": 3, "textfield": 3, "\u0622\u062f\u0631\u0633": 3, "urlfield": 3, "\u0646\u0642\u0634\u0647": 3, "booking_team": 3, "host": 3, "guest": 3, "booking_ticket": 3, "\u0634\u0645\u0627\u0631\u0647": 3, "\u0635\u0646\u062f\u0644\u06cc": 3, "make": 4, "error": 4, "user__usernam": 5, "stadium__nam": 5, "host_team__nam": 5, "guest_team__nam": 5, "listmodelmixin": 5, "retrievemodelmixin": 5, "createmodelmixin": 5, "genericviewset": 5, "invoice__user__usernam": 5, "invoicestatuschoic": [8, 10, 16], "paid": [8, 10, 16], "unpaid": [8, 10, 16], "locationchoic": [8, 10, 16], "b": [8, 10, 16], "c": [8, 10, 16], "vip": [8, 10, 16], "paymentstatuschoic": [8, 10, 16], "ticketstatuschoic": [8, 10, 16], "reserv": [8, 10, 16], "sold": [8, 10, 16], "book": [10, 16], "total": 3, "number": 3}, "objects": {"": [[0, 0, 0, "-", "api"], [2, 0, 0, "-", "booking"], [6, 0, 0, "-", "commands"], [7, 0, 0, "-", "core"], [8, 0, 0, "-", "extensions"], [9, 0, 0, "-", "manage"], [11, 0, 0, "-", "user_management"]], "api": [[0, 0, 0, "-", "apps"], [1, 0, 0, "-", "tests"], [0, 0, 0, "-", "urls"]], "api.apps": [[0, 1, 1, "", "ApiConfig"]], "api.apps.ApiConfig": [[0, 2, 1, "", "default_auto_field"], [0, 2, 1, "", "name"]], "api.tests": [[1, 0, 0, "-", "test_group"], [1, 0, 0, "-", "test_permission"], [1, 0, 0, "-", "test_user"]], "api.tests.test_group": [[1, 1, 1, "", "PrivateGroupsAPITests"], [1, 1, 1, "", "PublicGroupsAPITests"]], "api.tests.test_group.PrivateGroupsAPITests": [[1, 3, 1, "", "setUp"], [1, 3, 1, "", "test_create_group_successful"], [1, 3, 1, "", "test_create_group_with_duplicate_name_unsuccessful"], [1, 3, 1, "", "test_create_group_with_invalid_name_unsuccessful"], [1, 3, 1, "", "test_create_group_with_invalid_permission_unsuccessful"], [1, 3, 1, "", "test_create_group_with_permissions_successful"], [1, 3, 1, "", "test_delete_group_successful"], [1, 3, 1, "", "test_delete_group_unsuccessful"], [1, 3, 1, "", "test_retrieve_groups_successful"], [1, 3, 1, "", "test_retrieve_single_group"], [1, 3, 1, "", "test_update_group_successful"], [1, 3, 1, "", "test_update_group_unsuccessful"]], "api.tests.test_group.PublicGroupsAPITests": [[1, 3, 1, "", "setUp"], [1, 3, 1, "", "test_login_required"]], "api.tests.test_permission": [[1, 1, 1, "", "PrivatePermissionsAPITests"], [1, 1, 1, "", "PublicPermissionsAPITests"]], "api.tests.test_permission.PrivatePermissionsAPITests": [[1, 3, 1, "", "setUp"], [1, 3, 1, "", "test_create_permission_not_allowed"], [1, 3, 1, "", "test_delete_permission_not_allowed"], [1, 3, 1, "", "test_retrieve_permissions"], [1, 3, 1, "", "test_retrieve_single_permission"], [1, 3, 1, "", "test_update_permission_not_allowed"]], "api.tests.test_permission.PublicPermissionsAPITests": [[1, 3, 1, "", "setUp"], [1, 3, 1, "", "test_login_required"]], "api.tests.test_user": [[1, 1, 1, "", "PrivateUsersAPITests"], [1, 1, 1, "", "PublicUsersAPITests"]], "api.tests.test_user.PrivateUsersAPITests": [[1, 3, 1, "", "setUp"], [1, 3, 1, "", "test_create_superuser_successful"], [1, 3, 1, "", "test_create_user_successful"], [1, 3, 1, "", "test_create_user_with_duplicate_username_unsuccessful"], [1, 3, 1, "", "test_create_user_with_group_successful"], [1, 3, 1, "", "test_create_user_with_group_with_permission_to_do_something_successful"], [1, 3, 1, "", "test_create_user_with_group_without_permission_to_do_something_unsuccessful"], [1, 3, 1, "", "test_create_user_with_invalid_password_unsuccessful"], [1, 3, 1, "", "test_create_user_with_invalid_username_unsuccessful"], [1, 3, 1, "", "test_create_user_with_permission_successful"], [1, 3, 1, "", "test_delete_user_successful"], [1, 3, 1, "", "test_delete_user_unsuccessful"], [1, 3, 1, "", "test_retrieve_single_user_successful"], [1, 3, 1, "", "test_retrieve_users_successful"], [1, 3, 1, "", "test_update_user_successful"], [1, 3, 1, "", "test_update_user_unsuccessful"]], "api.tests.test_user.PublicUsersAPITests": [[1, 3, 1, "", "setUp"], [1, 3, 1, "", "test_login_required"]], "booking": [[2, 0, 0, "-", "admin"], [2, 0, 0, "-", "apps"], [3, 0, 0, "-", "models"], [4, 0, 0, "-", "serializers"], [2, 0, 0, "-", "urls"], [5, 0, 0, "-", "views"]], "booking.admin": [[2, 1, 1, "", "InvoiceAdmin"], [2, 1, 1, "", "MatchAdmin"], [2, 1, 1, "", "PaymentAdmin"], [2, 1, 1, "", "SectionInlineAdmin"], [2, 1, 1, "", "StadiumAdmin"], [2, 1, 1, "", "TeamAdmin"], [2, 1, 1, "", "TicketAdmin"]], "booking.admin.InvoiceAdmin": [[2, 2, 1, "", "actions"], [2, 3, 1, "", "get_queryset"], [2, 3, 1, "", "has_add_permission"], [2, 3, 1, "", "has_change_permission"], [2, 3, 1, "", "has_delete_permission"], [2, 2, 1, "", "list_display"], [2, 2, 1, "", "list_filter"], [2, 4, 1, "", "media"], [2, 2, 1, "", "search_fields"]], "booking.admin.MatchAdmin": [[2, 2, 1, "", "actions"], [2, 3, 1, "", "get_queryset"], [2, 2, 1, "", "list_display"], [2, 2, 1, "", "list_filter"], [2, 4, 1, "", "media"], [2, 2, 1, "", "search_fields"]], "booking.admin.PaymentAdmin": [[2, 2, 1, "", "actions"], [2, 3, 1, "", "get_queryset"], [2, 3, 1, "", "has_add_permission"], [2, 3, 1, "", "has_change_permission"], [2, 3, 1, "", "has_delete_permission"], [2, 2, 1, "", "list_display"], [2, 2, 1, "", "list_filter"], [2, 4, 1, "", "media"], [2, 2, 1, "", "search_fields"]], "booking.admin.SectionInlineAdmin": [[2, 2, 1, "", "extra"], [2, 4, 1, "", "media"], [2, 2, 1, "", "min_num"], [2, 2, 1, "", "model"]], "booking.admin.StadiumAdmin": [[2, 2, 1, "", "actions"], [2, 3, 1, "", "get_queryset"], [2, 2, 1, "", "inlines"], [2, 2, 1, "", "list_display"], [2, 2, 1, "", "list_filter"], [2, 4, 1, "", "media"], [2, 2, 1, "", "search_fields"]], "booking.admin.TeamAdmin": [[2, 2, 1, "", "actions"], [2, 2, 1, "", "list_display"], [2, 2, 1, "", "list_filter"], [2, 4, 1, "", "media"], [2, 2, 1, "", "search_fields"]], "booking.admin.TicketAdmin": [[2, 2, 1, "", "actions"], [2, 3, 1, "", "get_queryset"], [2, 3, 1, "", "has_add_permission"], [2, 3, 1, "", "has_change_permission"], [2, 3, 1, "", "has_delete_permission"], [2, 2, 1, "", "list_display"], [2, 2, 1, "", "list_filter"], [2, 4, 1, "", "media"], [2, 2, 1, "", "search_fields"]], "booking.apps": [[2, 1, 1, "", "BookingConfig"]], "booking.apps.BookingConfig": [[2, 2, 1, "", "default_auto_field"], [2, 2, 1, "", "name"], [2, 2, 1, "", "verbose_name"]], "booking.models": [[3, 1, 1, "", "Invoice"], [3, 1, 1, "", "Match"], [3, 1, 1, "", "Payment"], [3, 1, 1, "", "Section"], [3, 1, 1, "", "Stadium"], [3, 1, 1, "", "Team"], [3, 1, 1, "", "Ticket"], [3, 0, 0, "-", "invoice"], [3, 0, 0, "-", "match"], [3, 0, 0, "-", "payment"], [3, 0, 0, "-", "section"], [3, 0, 0, "-", "stadium"], [3, 0, 0, "-", "team"], [3, 0, 0, "-", "ticket"]], "booking.models.Invoice": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 3, 1, "", "get_status_display"], [3, 4, 1, "", "get_total_amount"], [3, 2, 1, "", "id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "payment_invoices"], [3, 3, 1, "", "set_as_paid"], [3, 2, 1, "", "status"], [3, 2, 1, "", "ticket_invoices"], [3, 2, 1, "", "updated_at"], [3, 2, 1, "", "user"], [3, 2, 1, "", "user_id"]], "booking.models.Match": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_start_time"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_start_time"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 2, 1, "", "guest_team"], [3, 2, 1, "", "guest_team_id"], [3, 2, 1, "", "host_team"], [3, 2, 1, "", "host_team_id"], [3, 2, 1, "", "id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "stadium"], [3, 2, 1, "", "stadium_id"], [3, 2, 1, "", "start_time"], [3, 2, 1, "", "ticket_matches"], [3, 2, 1, "", "updated_at"]], "booking.models.Payment": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "amount"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_status_display"], [3, 2, 1, "", "id"], [3, 2, 1, "", "invoice"], [3, 2, 1, "", "invoice_id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "status"]], "booking.models.Section": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "capacity"], [3, 2, 1, "", "created_at"], [3, 4, 1, "", "get_list_of_seat_numbers"], [3, 3, 1, "", "get_location_display"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 2, 1, "", "id"], [3, 2, 1, "", "location"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "price"], [3, 2, 1, "", "stadium"], [3, 2, 1, "", "stadium_id"], [3, 2, 1, "", "ticket_sections"], [3, 2, 1, "", "updated_at"]], "booking.models.Stadium": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "address"], [3, 2, 1, "", "city"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 4, 1, "", "get_section_ids"], [3, 2, 1, "", "id"], [3, 2, 1, "", "map_url"], [3, 2, 1, "", "match_stadiums"], [3, 2, 1, "", "name"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "province"], [3, 2, 1, "", "section_stadiums"], [3, 2, 1, "", "updated_at"]], "booking.models.Team": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 2, 1, "", "id"], [3, 2, 1, "", "match_guest_teams"], [3, 2, 1, "", "match_host_teams"], [3, 2, 1, "", "name"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "updated_at"]], "booking.models.Ticket": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 3, 1, "", "get_status_display"], [3, 2, 1, "", "id"], [3, 2, 1, "", "invoice"], [3, 2, 1, "", "invoice_id"], [3, 2, 1, "", "match"], [3, 2, 1, "", "match_id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "seat_number"], [3, 2, 1, "", "section"], [3, 2, 1, "", "section_id"], [3, 3, 1, "", "set_as_sold"], [3, 2, 1, "", "status"], [3, 2, 1, "", "updated_at"]], "booking.models.invoice": [[3, 1, 1, "", "Invoice"]], "booking.models.invoice.Invoice": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 3, 1, "", "get_status_display"], [3, 4, 1, "", "get_total_amount"], [3, 2, 1, "", "id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "payment_invoices"], [3, 3, 1, "", "set_as_paid"], [3, 2, 1, "", "status"], [3, 2, 1, "", "ticket_invoices"], [3, 2, 1, "", "updated_at"], [3, 2, 1, "", "user"], [3, 2, 1, "", "user_id"]], "booking.models.match": [[3, 1, 1, "", "Match"]], "booking.models.match.Match": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_start_time"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_start_time"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 2, 1, "", "guest_team"], [3, 2, 1, "", "guest_team_id"], [3, 2, 1, "", "host_team"], [3, 2, 1, "", "host_team_id"], [3, 2, 1, "", "id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "stadium"], [3, 2, 1, "", "stadium_id"], [3, 2, 1, "", "start_time"], [3, 2, 1, "", "ticket_matches"], [3, 2, 1, "", "updated_at"]], "booking.models.payment": [[3, 1, 1, "", "Payment"]], "booking.models.payment.Payment": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "amount"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_status_display"], [3, 2, 1, "", "id"], [3, 2, 1, "", "invoice"], [3, 2, 1, "", "invoice_id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "status"]], "booking.models.section": [[3, 1, 1, "", "Section"]], "booking.models.section.Section": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "capacity"], [3, 2, 1, "", "created_at"], [3, 4, 1, "", "get_list_of_seat_numbers"], [3, 3, 1, "", "get_location_display"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 2, 1, "", "id"], [3, 2, 1, "", "location"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "price"], [3, 2, 1, "", "stadium"], [3, 2, 1, "", "stadium_id"], [3, 2, 1, "", "ticket_sections"], [3, 2, 1, "", "updated_at"]], "booking.models.stadium": [[3, 1, 1, "", "Stadium"]], "booking.models.stadium.Stadium": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "address"], [3, 2, 1, "", "city"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 4, 1, "", "get_section_ids"], [3, 2, 1, "", "id"], [3, 2, 1, "", "map_url"], [3, 2, 1, "", "match_stadiums"], [3, 2, 1, "", "name"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "province"], [3, 2, 1, "", "section_stadiums"], [3, 2, 1, "", "updated_at"]], "booking.models.team": [[3, 1, 1, "", "Team"]], "booking.models.team.Team": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 2, 1, "", "id"], [3, 2, 1, "", "match_guest_teams"], [3, 2, 1, "", "match_host_teams"], [3, 2, 1, "", "name"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "updated_at"]], "booking.models.ticket": [[3, 1, 1, "", "Ticket"]], "booking.models.ticket.Ticket": [[3, 5, 1, "", "DoesNotExist"], [3, 5, 1, "", "MultipleObjectsReturned"], [3, 2, 1, "", "created_at"], [3, 3, 1, "", "get_next_by_created_at"], [3, 3, 1, "", "get_next_by_updated_at"], [3, 3, 1, "", "get_previous_by_created_at"], [3, 3, 1, "", "get_previous_by_updated_at"], [3, 3, 1, "", "get_status_display"], [3, 2, 1, "", "id"], [3, 2, 1, "", "invoice"], [3, 2, 1, "", "invoice_id"], [3, 2, 1, "", "match"], [3, 2, 1, "", "match_id"], [3, 2, 1, "", "objects"], [3, 2, 1, "", "seat_number"], [3, 2, 1, "", "section"], [3, 2, 1, "", "section_id"], [3, 3, 1, "", "set_as_sold"], [3, 2, 1, "", "status"], [3, 2, 1, "", "updated_at"]], "booking.serializers": [[4, 1, 1, "", "InvoiceSerializer"], [4, 1, 1, "", "MatchSerializer"], [4, 1, 1, "", "PaymentSerializer"], [4, 1, 1, "", "SectionSerializer"], [4, 1, 1, "", "StadiumSerializer"], [4, 1, 1, "", "TeamSerializer"], [4, 1, 1, "", "TicketSerializer"], [4, 0, 0, "-", "invoice"], [4, 0, 0, "-", "match"], [4, 0, 0, "-", "payment"], [4, 0, 0, "-", "section"], [4, 0, 0, "-", "stadium"], [4, 0, 0, "-", "team"], [4, 0, 0, "-", "ticket"]], "booking.serializers.InvoiceSerializer": [[4, 3, 1, "", "to_representation"]], "booking.serializers.PaymentSerializer": [[4, 3, 1, "", "create"], [4, 3, 1, "", "to_representation"], [4, 3, 1, "", "validate"]], "booking.serializers.SectionSerializer": [[4, 3, 1, "", "to_representation"]], "booking.serializers.TicketSerializer": [[4, 3, 1, "", "create"], [4, 3, 1, "", "to_representation"], [4, 3, 1, "", "validate"]], "booking.serializers.invoice": [[4, 1, 1, "", "InvoiceSerializer"]], "booking.serializers.invoice.InvoiceSerializer": [[4, 3, 1, "", "to_representation"]], "booking.serializers.match": [[4, 1, 1, "", "MatchSerializer"]], "booking.serializers.payment": [[4, 1, 1, "", "PaymentSerializer"]], "booking.serializers.payment.PaymentSerializer": [[4, 3, 1, "", "create"], [4, 3, 1, "", "to_representation"], [4, 3, 1, "", "validate"]], "booking.serializers.section": [[4, 1, 1, "", "SectionSerializer"]], "booking.serializers.section.SectionSerializer": [[4, 3, 1, "", "to_representation"]], "booking.serializers.stadium": [[4, 1, 1, "", "StadiumSerializer"]], "booking.serializers.team": [[4, 1, 1, "", "TeamSerializer"]], "booking.serializers.ticket": [[4, 1, 1, "", "TicketSerializer"]], "booking.serializers.ticket.TicketSerializer": [[4, 3, 1, "", "create"], [4, 3, 1, "", "to_representation"], [4, 3, 1, "", "validate"]], "booking.views": [[5, 1, 1, "", "InvoiceViewSet"], [5, 1, 1, "", "MatchViewSet"], [5, 1, 1, "", "PaymentViewSet"], [5, 1, 1, "", "SectionViewSet"], [5, 1, 1, "", "StadiumViewSet"], [5, 1, 1, "", "TeamViewSet"], [5, 1, 1, "", "TicketViewSet"], [5, 0, 0, "-", "invoice"], [5, 0, 0, "-", "match"], [5, 0, 0, "-", "payment"], [5, 0, 0, "-", "section"], [5, 0, 0, "-", "stadium"], [5, 0, 0, "-", "team"], [5, 0, 0, "-", "ticket"]], "booking.views.InvoiceViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.MatchViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 3, 1, "", "seats"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.PaymentViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.SectionViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.StadiumViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.TeamViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.TicketViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.invoice": [[5, 1, 1, "", "InvoiceViewSet"]], "booking.views.invoice.InvoiceViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.match": [[5, 1, 1, "", "MatchViewSet"]], "booking.views.match.MatchViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 3, 1, "", "seats"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.payment": [[5, 1, 1, "", "PaymentViewSet"]], "booking.views.payment.PaymentViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.section": [[5, 1, 1, "", "SectionViewSet"]], "booking.views.section.SectionViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.stadium": [[5, 1, 1, "", "StadiumViewSet"]], "booking.views.stadium.StadiumViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.team": [[5, 1, 1, "", "TeamViewSet"]], "booking.views.team.TeamViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "booking.views.ticket": [[5, 1, 1, "", "TicketViewSet"]], "booking.views.ticket.TicketViewSet": [[5, 2, 1, "", "basename"], [5, 2, 1, "", "description"], [5, 2, 1, "", "detail"], [5, 2, 1, "", "filter_backends"], [5, 2, 1, "", "filterset_fields"], [5, 2, 1, "", "name"], [5, 2, 1, "", "permission_classes"], [5, 2, 1, "", "queryset"], [5, 2, 1, "", "search_fields"], [5, 2, 1, "", "serializer_class"], [5, 2, 1, "", "suffix"]], "commands": [[6, 0, 0, "-", "apps"]], "commands.apps": [[6, 1, 1, "", "CommandsConfig"]], "commands.apps.CommandsConfig": [[6, 2, 1, "", "default_auto_field"], [6, 2, 1, "", "name"], [6, 2, 1, "", "verbose_name"]], "core": [[7, 0, 0, "-", "asgi"], [7, 0, 0, "-", "settings"], [7, 0, 0, "-", "urls"], [7, 0, 0, "-", "wsgi"]], "extensions": [[8, 0, 0, "-", "abstract_models"], [8, 0, 0, "-", "choices"], [8, 0, 0, "-", "custom_exception_handlers"], [8, 0, 0, "-", "custom_permissions"]], "extensions.abstract_models": [[8, 1, 1, "", "AbstractCreatAtUpdateAt"]], "extensions.abstract_models.AbstractCreatAtUpdateAt": [[8, 2, 1, "", "created_at"], [8, 3, 1, "", "get_next_by_created_at"], [8, 3, 1, "", "get_next_by_updated_at"], [8, 3, 1, "", "get_previous_by_created_at"], [8, 3, 1, "", "get_previous_by_updated_at"], [8, 2, 1, "", "updated_at"]], "extensions.choices": [[8, 1, 1, "", "GenderChoices"], [8, 1, 1, "", "InvoiceStatusChoices"], [8, 1, 1, "", "LocationChoices"], [8, 1, 1, "", "PaymentStatusChoices"], [8, 1, 1, "", "TicketStatusChoices"]], "extensions.choices.GenderChoices": [[8, 2, 1, "", "FEMALE"], [8, 2, 1, "", "MALE"]], "extensions.choices.InvoiceStatusChoices": [[8, 2, 1, "", "PAID"], [8, 2, 1, "", "UNPAID"]], "extensions.choices.LocationChoices": [[8, 2, 1, "", "A"], [8, 2, 1, "", "B"], [8, 2, 1, "", "C"], [8, 2, 1, "", "VIP"]], "extensions.choices.PaymentStatusChoices": [[8, 2, 1, "", "SUCCESSFUL"], [8, 2, 1, "", "UNSUCCESSFUL"]], "extensions.choices.TicketStatusChoices": [[8, 2, 1, "", "RESERVED"], [8, 2, 1, "", "SOLD"]], "extensions.custom_exception_handlers": [[8, 6, 1, "", "exception_handler"]], "extensions.custom_permissions": [[8, 1, 1, "", "CustomDjangoModelPermission"], [8, 1, 1, "", "OwnUserPermission"], [8, 1, 1, "", "UnauthenticatedPost"]], "extensions.custom_permissions.CustomDjangoModelPermission": [[8, 3, 1, "", "__init__"]], "extensions.custom_permissions.OwnUserPermission": [[8, 3, 1, "", "has_object_permission"]], "extensions.custom_permissions.UnauthenticatedPost": [[8, 3, 1, "", "has_permission"]], "manage": [[9, 6, 1, "", "main"]], "user_management": [[11, 0, 0, "-", "admin"], [11, 0, 0, "-", "apps"], [11, 0, 0, "-", "managers"], [12, 0, 0, "-", "models"], [13, 0, 0, "-", "serializers"], [11, 0, 0, "-", "signals"], [14, 0, 0, "-", "tests"], [11, 0, 0, "-", "urls"], [15, 0, 0, "-", "views"]], "user_management.admin": [[11, 1, 1, "", "ProfileInlineAdmin"], [11, 1, 1, "", "UserAdmin"]], "user_management.admin.ProfileInlineAdmin": [[11, 4, 1, "", "media"], [11, 2, 1, "", "model"]], "user_management.admin.UserAdmin": [[11, 2, 1, "", "actions"], [11, 2, 1, "", "add_fieldsets"], [11, 3, 1, "", "get_full_name"], [11, 3, 1, "", "get_queryset"], [11, 2, 1, "", "inlines"], [11, 2, 1, "", "list_display"], [11, 2, 1, "", "list_filter"], [11, 4, 1, "", "media"], [11, 2, 1, "", "readonly_fields"], [11, 2, 1, "", "search_fields"]], "user_management.apps": [[11, 1, 1, "", "UserManagementConfig"]], "user_management.apps.UserManagementConfig": [[11, 2, 1, "", "default_auto_field"], [11, 2, 1, "", "name"], [11, 3, 1, "", "ready"], [11, 2, 1, "", "verbose_name"]], "user_management.managers": [[11, 1, 1, "", "UserManager"]], "user_management.managers.UserManager": [[11, 3, 1, "", "create_staff"], [11, 3, 1, "", "create_superuser"], [11, 3, 1, "", "create_user"], [11, 2, 1, "", "use_in_migrations"]], "user_management.models": [[12, 1, 1, "", "Profile"], [12, 1, 1, "", "User"], [12, 0, 0, "-", "profile"], [12, 0, 0, "-", "user"]], "user_management.models.Profile": [[12, 5, 1, "", "DoesNotExist"], [12, 5, 1, "", "MultipleObjectsReturned"], [12, 2, 1, "", "created_at"], [12, 2, 1, "", "first_name"], [12, 2, 1, "", "gender"], [12, 4, 1, "", "get_full_name"], [12, 3, 1, "", "get_gender_display"], [12, 3, 1, "", "get_next_by_created_at"], [12, 3, 1, "", "get_next_by_updated_at"], [12, 3, 1, "", "get_previous_by_created_at"], [12, 3, 1, "", "get_previous_by_updated_at"], [12, 2, 1, "", "last_name"], [12, 2, 1, "", "objects"], [12, 2, 1, "", "updated_at"], [12, 2, 1, "", "user"], [12, 2, 1, "", "user_id"]], "user_management.models.User": [[12, 5, 1, "", "DoesNotExist"], [12, 2, 1, "", "EMAIL_FIELD"], [12, 5, 1, "", "MultipleObjectsReturned"], [12, 2, 1, "", "REQUIRED_FIELDS"], [12, 2, 1, "", "USERNAME_FIELD"], [12, 2, 1, "", "created_at"], [12, 3, 1, "", "get_next_by_created_at"], [12, 3, 1, "", "get_next_by_updated_at"], [12, 3, 1, "", "get_previous_by_created_at"], [12, 3, 1, "", "get_previous_by_updated_at"], [12, 2, 1, "", "groups"], [12, 2, 1, "", "id"], [12, 2, 1, "", "invoice_users"], [12, 2, 1, "", "is_active"], [12, 2, 1, "", "is_staff"], [12, 2, 1, "", "is_superuser"], [12, 2, 1, "", "last_login"], [12, 2, 1, "", "logentry_set"], [12, 2, 1, "", "objects"], [12, 2, 1, "", "password"], [12, 2, 1, "", "profile_user"], [12, 3, 1, "", "save"], [12, 2, 1, "", "updated_at"], [12, 2, 1, "", "user_permissions"], [12, 2, 1, "", "username"]], "user_management.models.profile": [[12, 1, 1, "", "Profile"]], "user_management.models.profile.Profile": [[12, 5, 1, "", "DoesNotExist"], [12, 5, 1, "", "MultipleObjectsReturned"], [12, 2, 1, "", "created_at"], [12, 2, 1, "", "first_name"], [12, 2, 1, "", "gender"], [12, 4, 1, "", "get_full_name"], [12, 3, 1, "", "get_gender_display"], [12, 3, 1, "", "get_next_by_created_at"], [12, 3, 1, "", "get_next_by_updated_at"], [12, 3, 1, "", "get_previous_by_created_at"], [12, 3, 1, "", "get_previous_by_updated_at"], [12, 2, 1, "", "last_name"], [12, 2, 1, "", "objects"], [12, 2, 1, "", "updated_at"], [12, 2, 1, "", "user"], [12, 2, 1, "", "user_id"]], "user_management.models.user": [[12, 1, 1, "", "User"]], "user_management.models.user.User": [[12, 5, 1, "", "DoesNotExist"], [12, 2, 1, "", "EMAIL_FIELD"], [12, 5, 1, "", "MultipleObjectsReturned"], [12, 2, 1, "", "REQUIRED_FIELDS"], [12, 2, 1, "", "USERNAME_FIELD"], [12, 2, 1, "", "created_at"], [12, 3, 1, "", "get_next_by_created_at"], [12, 3, 1, "", "get_next_by_updated_at"], [12, 3, 1, "", "get_previous_by_created_at"], [12, 3, 1, "", "get_previous_by_updated_at"], [12, 2, 1, "", "groups"], [12, 2, 1, "", "id"], [12, 2, 1, "", "invoice_users"], [12, 2, 1, "", "is_active"], [12, 2, 1, "", "is_staff"], [12, 2, 1, "", "is_superuser"], [12, 2, 1, "", "last_login"], [12, 2, 1, "", "logentry_set"], [12, 2, 1, "", "objects"], [12, 2, 1, "", "password"], [12, 2, 1, "", "profile_user"], [12, 3, 1, "", "save"], [12, 2, 1, "", "updated_at"], [12, 2, 1, "", "user_permissions"], [12, 2, 1, "", "username"]], "user_management.serializers": [[13, 1, 1, "", "CreateGroupSerializer"], [13, 1, 1, "", "GroupSerializer"], [13, 1, 1, "", "PermissionSerializer"], [13, 1, 1, "", "ProfileSerializer"], [13, 1, 1, "", "UserPasswordChangeSerializer"], [13, 1, 1, "", "UserSerializer"], [13, 0, 0, "-", "group"], [13, 0, 0, "-", "permission"], [13, 0, 0, "-", "profile"], [13, 0, 0, "-", "user"]], "user_management.serializers.CreateGroupSerializer": [[13, 3, 1, "", "create"], [13, 3, 1, "", "update"]], "user_management.serializers.ProfileSerializer": [[13, 3, 1, "", "to_representation"]], "user_management.serializers.UserPasswordChangeSerializer": [[13, 3, 1, "", "save"], [13, 3, 1, "", "to_representation"], [13, 3, 1, "", "validate"], [13, 3, 1, "", "validate_new_password1"], [13, 3, 1, "", "validate_new_password2"], [13, 3, 1, "", "validate_old_password"]], "user_management.serializers.UserSerializer": [[13, 3, 1, "", "to_representation"], [13, 3, 1, "", "validate_password"]], "user_management.serializers.group": [[13, 1, 1, "", "CreateGroupSerializer"], [13, 1, 1, "", "GroupSerializer"]], "user_management.serializers.group.CreateGroupSerializer": [[13, 3, 1, "", "create"], [13, 3, 1, "", "update"]], "user_management.serializers.permission": [[13, 1, 1, "", "PermissionSerializer"]], "user_management.serializers.profile": [[13, 1, 1, "", "ProfileSerializer"]], "user_management.serializers.profile.ProfileSerializer": [[13, 3, 1, "", "to_representation"]], "user_management.serializers.user": [[13, 1, 1, "", "UserPasswordChangeSerializer"], [13, 1, 1, "", "UserSerializer"]], "user_management.serializers.user.UserPasswordChangeSerializer": [[13, 3, 1, "", "save"], [13, 3, 1, "", "to_representation"], [13, 3, 1, "", "validate"], [13, 3, 1, "", "validate_new_password1"], [13, 3, 1, "", "validate_new_password2"], [13, 3, 1, "", "validate_old_password"]], "user_management.serializers.user.UserSerializer": [[13, 3, 1, "", "to_representation"], [13, 3, 1, "", "validate_password"]], "user_management.signals": [[11, 6, 1, "", "creating_profile"]], "user_management.tests": [[14, 0, 0, "-", "common_functions"], [14, 0, 0, "-", "test_group_model"], [14, 0, 0, "-", "test_user_model"]], "user_management.tests.common_functions": [[14, 6, 1, "", "sample_group"], [14, 6, 1, "", "sample_superuser"], [14, 6, 1, "", "sample_user"]], "user_management.tests.test_group_model": [[14, 1, 1, "", "GroupModelTest"]], "user_management.tests.test_group_model.GroupModelTest": [[14, 3, 1, "", "test_create_group_successful"], [14, 3, 1, "", "test_create_group_with_permissions_successful"], [14, 3, 1, "", "test_create_new_group_with_duplicate_name_unsuccessful"], [14, 3, 1, "", "test_creating_group_with_none_name"]], "user_management.tests.test_user_model": [[14, 1, 1, "", "UserModelTest"]], "user_management.tests.test_user_model.UserModelTest": [[14, 3, 1, "", "test_create_new_superuser_with_duplicate_username_unsuccessful"], [14, 3, 1, "", "test_create_new_user_with_duplicate_username_unsuccessful"], [14, 3, 1, "", "test_create_user_with_username_successful"], [14, 3, 1, "", "test_creating_new_superuser"], [14, 3, 1, "", "test_creating_user_with_invalid_username"], [14, 3, 1, "", "test_creating_user_with_none_username"]], "user_management.views": [[15, 1, 1, "", "GroupViewSet"], [15, 1, 1, "", "PermissionViewSet"], [15, 1, 1, "", "UserViewSet"], [15, 0, 0, "-", "group"], [15, 0, 0, "-", "permission"], [15, 0, 0, "-", "user"]], "user_management.views.GroupViewSet": [[15, 2, 1, "", "basename"], [15, 2, 1, "", "description"], [15, 3, 1, "", "destroy"], [15, 2, 1, "", "detail"], [15, 2, 1, "", "filter_backends"], [15, 2, 1, "", "filterset_fields"], [15, 3, 1, "", "get_serializer_class"], [15, 2, 1, "", "name"], [15, 2, 1, "", "permission_classes"], [15, 2, 1, "", "queryset"], [15, 2, 1, "", "search_fields"], [15, 2, 1, "", "serializer_class"], [15, 2, 1, "", "suffix"]], "user_management.views.PermissionViewSet": [[15, 2, 1, "", "basename"], [15, 2, 1, "", "description"], [15, 2, 1, "", "detail"], [15, 2, 1, "", "filter_backends"], [15, 2, 1, "", "filterset_fields"], [15, 2, 1, "", "name"], [15, 2, 1, "", "permission_classes"], [15, 2, 1, "", "queryset"], [15, 2, 1, "", "search_fields"], [15, 2, 1, "", "serializer_class"], [15, 2, 1, "", "suffix"]], "user_management.views.UserViewSet": [[15, 2, 1, "", "basename"], [15, 3, 1, "", "change_password"], [15, 2, 1, "", "description"], [15, 2, 1, "", "detail"], [15, 2, 1, "", "filter_backends"], [15, 2, 1, "", "filterset_fields"], [15, 2, 1, "", "name"], [15, 2, 1, "", "permission_classes"], [15, 2, 1, "", "queryset"], [15, 2, 1, "", "search_fields"], [15, 2, 1, "", "serializer_class"], [15, 2, 1, "", "suffix"], [15, 3, 1, "", "update_user_profile"], [15, 3, 1, "", "user_profile"]], "user_management.views.group": [[15, 1, 1, "", "GroupViewSet"]], "user_management.views.group.GroupViewSet": [[15, 2, 1, "", "basename"], [15, 2, 1, "", "description"], [15, 3, 1, "", "destroy"], [15, 2, 1, "", "detail"], [15, 2, 1, "", "filter_backends"], [15, 2, 1, "", "filterset_fields"], [15, 3, 1, "", "get_serializer_class"], [15, 2, 1, "", "name"], [15, 2, 1, "", "permission_classes"], [15, 2, 1, "", "queryset"], [15, 2, 1, "", "search_fields"], [15, 2, 1, "", "serializer_class"], [15, 2, 1, "", "suffix"]], "user_management.views.permission": [[15, 1, 1, "", "PermissionViewSet"]], "user_management.views.permission.PermissionViewSet": [[15, 2, 1, "", "basename"], [15, 2, 1, "", "description"], [15, 2, 1, "", "detail"], [15, 2, 1, "", "filter_backends"], [15, 2, 1, "", "filterset_fields"], [15, 2, 1, "", "name"], [15, 2, 1, "", "permission_classes"], [15, 2, 1, "", "queryset"], [15, 2, 1, "", "search_fields"], [15, 2, 1, "", "serializer_class"], [15, 2, 1, "", "suffix"]], "user_management.views.user": [[15, 1, 1, "", "UserViewSet"]], "user_management.views.user.UserViewSet": [[15, 2, 1, "", "basename"], [15, 3, 1, "", "change_password"], [15, 2, 1, "", "description"], [15, 2, 1, "", "detail"], [15, 2, 1, "", "filter_backends"], [15, 2, 1, "", "filterset_fields"], [15, 2, 1, "", "name"], [15, 2, 1, "", "permission_classes"], [15, 2, 1, "", "queryset"], [15, 2, 1, "", "search_fields"], [15, 2, 1, "", "serializer_class"], [15, 2, 1, "", "suffix"], [15, 3, 1, "", "update_user_profile"], [15, 3, 1, "", "user_profile"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method", "4": "py:property", "5": "py:exception", "6": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"], "4": ["py", "property", "Python property"], "5": ["py", "exception", "Python exception"], "6": ["py", "function", "Python function"]}, "titleterms": {"core": 7, "packag": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15], "submodul": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15], "asgi": 7, "modul": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "set": 7, "url": [0, 2, 7, 11], "wsgi": 7, "content": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16], "manag": [9, 11], "src": 10, "welcom": 16, "ticket": [3, 4, 5, 16], "system": 16, "": 16, "document": 16, "indic": 16, "tabl": 16, "api": [0, 1], "subpackag": [0, 2, 11], "app": [0, 2, 6, 11], "test": [1, 14], "test_group": 1, "test_permiss": 1, "test_us": 1, "command": 6, "extens": 8, "abstract_model": 8, "choic": 8, "custom_exception_handl": 8, "custom_permiss": 8, "user_manag": [11, 12, 13, 14, 15], "admin": [2, 11], "signal": 11, "model": [3, 12], "profil": [12, 13], "user": [12, 13, 15], "serial": [4, 13], "group": [13, 15], "permiss": [13, 15], "common_funct": 14, "test_group_model": 14, "test_user_model": 14, "view": [5, 15], "book": [2, 3, 4, 5], "invoic": [3, 4, 5], "match": [3, 4, 5], "payment": [3, 4, 5], "section": [3, 4, 5], "stadium": [3, 4, 5], "team": [3, 4, 5]}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1, "sphinx.ext.intersphinx": 1, "sphinx": 57}, "alltitles": {"Submodules": [[1, "submodules"], [6, "submodules"], [13, "submodules"], [14, "submodules"], [15, "submodules"], [0, "submodules"], [7, "submodules"], [8, "submodules"], [12, "submodules"], [2, "submodules"], [3, "submodules"], [4, "submodules"], [5, "submodules"], [11, "submodules"]], "Module contents": [[1, "module-api.tests"], [6, "module-commands"], [13, "module-user_management.serializers"], [14, "module-user_management.tests"], [15, "module-user_management.views"], [0, "module-api"], [7, "module-core"], [8, "module-extensions"], [12, "module-user_management.models"], [2, "module-booking"], [3, "module-booking.models"], [4, "module-booking.serializers"], [5, "module-booking.views"], [11, "module-user_management"]], "api.tests package": [[1, "api-tests-package"]], "api.tests.test_group module": [[1, "module-api.tests.test_group"]], "api.tests.test_permission module": [[1, "module-api.tests.test_permission"]], "api.tests.test_user module": [[1, "module-api.tests.test_user"]], "commands package": [[6, "commands-package"]], "commands.apps module": [[6, "module-commands.apps"]], "manage module": [[9, "module-manage"]], "user_management.serializers package": [[13, "user-management-serializers-package"]], "user_management.serializers.group module": [[13, "module-user_management.serializers.group"]], "user_management.serializers.permission module": [[13, "module-user_management.serializers.permission"]], "user_management.serializers.profile module": [[13, "module-user_management.serializers.profile"]], "user_management.serializers.user module": [[13, "module-user_management.serializers.user"]], "user_management.tests package": [[14, "user-management-tests-package"]], "user_management.tests.common_functions module": [[14, "module-user_management.tests.common_functions"]], "user_management.tests.test_group_model module": [[14, "module-user_management.tests.test_group_model"]], "user_management.tests.test_user_model module": [[14, "module-user_management.tests.test_user_model"]], "Welcome to Ticketing system\u2019s documentation!": [[16, "welcome-to-ticketing-system-s-documentation"]], "Contents:": [[16, null]], "Indices and tables": [[16, "indices-and-tables"]], "user_management.views package": [[15, "user-management-views-package"]], "user_management.views.group module": [[15, "module-user_management.views.group"]], "user_management.views.permission module": [[15, "module-user_management.views.permission"]], "user_management.views.user module": [[15, "module-user_management.views.user"]], "api package": [[0, "api-package"]], "Subpackages": [[0, "subpackages"], [2, "subpackages"], [11, "subpackages"]], "api.apps module": [[0, "module-api.apps"]], "api.urls module": [[0, "module-api.urls"]], "core package": [[7, "core-package"]], "core.asgi module": [[7, "module-core.asgi"]], "core.settings module": [[7, "module-core.settings"]], "core.urls module": [[7, "module-core.urls"]], "core.wsgi module": [[7, "module-core.wsgi"]], "extensions package": [[8, "extensions-package"]], "extensions.abstract_models module": [[8, "module-extensions.abstract_models"]], "extensions.choices module": [[8, "module-extensions.choices"]], "extensions.custom_exception_handlers module": [[8, "module-extensions.custom_exception_handlers"]], "extensions.custom_permissions module": [[8, "module-extensions.custom_permissions"]], "src": [[10, "src"]], "user_management.models package": [[12, "user-management-models-package"]], "user_management.models.profile module": [[12, "module-user_management.models.profile"]], "user_management.models.user module": [[12, "module-user_management.models.user"]], "booking package": [[2, "booking-package"]], "booking.admin module": [[2, "module-booking.admin"]], "booking.apps module": [[2, "module-booking.apps"]], "booking.urls module": [[2, "module-booking.urls"]], "booking.models package": [[3, "booking-models-package"]], "booking.models.invoice module": [[3, "module-booking.models.invoice"]], "booking.models.match module": [[3, "module-booking.models.match"]], "booking.models.payment module": [[3, "module-booking.models.payment"]], "booking.models.section module": [[3, "module-booking.models.section"]], "booking.models.stadium module": [[3, "module-booking.models.stadium"]], "booking.models.team module": [[3, "module-booking.models.team"]], "booking.models.ticket module": [[3, "module-booking.models.ticket"]], "booking.serializers package": [[4, "booking-serializers-package"]], "booking.serializers.invoice module": [[4, "module-booking.serializers.invoice"]], "booking.serializers.match module": [[4, "module-booking.serializers.match"]], "booking.serializers.payment module": [[4, "module-booking.serializers.payment"]], "booking.serializers.section module": [[4, "module-booking.serializers.section"]], "booking.serializers.stadium module": [[4, "module-booking.serializers.stadium"]], "booking.serializers.team module": [[4, "module-booking.serializers.team"]], "booking.serializers.ticket module": [[4, "module-booking.serializers.ticket"]], "booking.views package": [[5, "booking-views-package"]], "booking.views.invoice module": [[5, "module-booking.views.invoice"]], "booking.views.match module": [[5, "module-booking.views.match"]], "booking.views.payment module": [[5, "module-booking.views.payment"]], "booking.views.section module": [[5, "module-booking.views.section"]], "booking.views.stadium module": [[5, "module-booking.views.stadium"]], "booking.views.team module": [[5, "module-booking.views.team"]], "booking.views.ticket module": [[5, "module-booking.views.ticket"]], "user_management package": [[11, "user-management-package"]], "user_management.admin module": [[11, "module-user_management.admin"]], "user_management.apps module": [[11, "module-user_management.apps"]], "user_management.managers module": [[11, "module-user_management.managers"]], "user_management.signals module": [[11, "module-user_management.signals"]], "user_management.urls module": [[11, "module-user_management.urls"]]}, "indexentries": {"bookingconfig (class in booking.apps)": [[2, "booking.apps.BookingConfig"]], "invoiceadmin (class in booking.admin)": [[2, "booking.admin.InvoiceAdmin"]], "matchadmin (class in booking.admin)": [[2, "booking.admin.MatchAdmin"]], "paymentadmin (class in booking.admin)": [[2, "booking.admin.PaymentAdmin"]], "sectioninlineadmin (class in booking.admin)": [[2, "booking.admin.SectionInlineAdmin"]], "stadiumadmin (class in booking.admin)": [[2, "booking.admin.StadiumAdmin"]], "teamadmin (class in booking.admin)": [[2, "booking.admin.TeamAdmin"]], "ticketadmin (class in booking.admin)": [[2, "booking.admin.TicketAdmin"]], "actions (booking.admin.invoiceadmin attribute)": [[2, "booking.admin.InvoiceAdmin.actions"]], "actions (booking.admin.matchadmin attribute)": [[2, "booking.admin.MatchAdmin.actions"]], "actions (booking.admin.paymentadmin attribute)": [[2, "booking.admin.PaymentAdmin.actions"]], "actions (booking.admin.stadiumadmin attribute)": [[2, "booking.admin.StadiumAdmin.actions"]], "actions (booking.admin.teamadmin attribute)": [[2, "booking.admin.TeamAdmin.actions"]], "actions (booking.admin.ticketadmin attribute)": [[2, "booking.admin.TicketAdmin.actions"]], "booking": [[2, "module-booking"]], "booking.admin": [[2, "module-booking.admin"]], "booking.apps": [[2, "module-booking.apps"]], "booking.urls": [[2, "module-booking.urls"]], "default_auto_field (booking.apps.bookingconfig attribute)": [[2, "booking.apps.BookingConfig.default_auto_field"]], "extra (booking.admin.sectioninlineadmin attribute)": [[2, "booking.admin.SectionInlineAdmin.extra"]], "get_queryset() (booking.admin.invoiceadmin method)": [[2, "booking.admin.InvoiceAdmin.get_queryset"]], "get_queryset() (booking.admin.matchadmin method)": [[2, "booking.admin.MatchAdmin.get_queryset"]], "get_queryset() (booking.admin.paymentadmin method)": [[2, "booking.admin.PaymentAdmin.get_queryset"]], "get_queryset() (booking.admin.stadiumadmin method)": [[2, "booking.admin.StadiumAdmin.get_queryset"]], "get_queryset() (booking.admin.ticketadmin method)": [[2, "booking.admin.TicketAdmin.get_queryset"]], "has_add_permission() (booking.admin.invoiceadmin method)": [[2, "booking.admin.InvoiceAdmin.has_add_permission"]], "has_add_permission() (booking.admin.paymentadmin method)": [[2, "booking.admin.PaymentAdmin.has_add_permission"]], "has_add_permission() (booking.admin.ticketadmin method)": [[2, "booking.admin.TicketAdmin.has_add_permission"]], "has_change_permission() (booking.admin.invoiceadmin method)": [[2, "booking.admin.InvoiceAdmin.has_change_permission"]], "has_change_permission() (booking.admin.paymentadmin method)": [[2, "booking.admin.PaymentAdmin.has_change_permission"]], "has_change_permission() (booking.admin.ticketadmin method)": [[2, "booking.admin.TicketAdmin.has_change_permission"]], "has_delete_permission() (booking.admin.invoiceadmin method)": [[2, "booking.admin.InvoiceAdmin.has_delete_permission"]], "has_delete_permission() (booking.admin.paymentadmin method)": [[2, "booking.admin.PaymentAdmin.has_delete_permission"]], "has_delete_permission() (booking.admin.ticketadmin method)": [[2, "booking.admin.TicketAdmin.has_delete_permission"]], "inlines (booking.admin.stadiumadmin attribute)": [[2, "booking.admin.StadiumAdmin.inlines"]], "list_display (booking.admin.invoiceadmin attribute)": [[2, "booking.admin.InvoiceAdmin.list_display"]], "list_display (booking.admin.matchadmin attribute)": [[2, "booking.admin.MatchAdmin.list_display"]], "list_display (booking.admin.paymentadmin attribute)": [[2, "booking.admin.PaymentAdmin.list_display"]], "list_display (booking.admin.stadiumadmin attribute)": [[2, "booking.admin.StadiumAdmin.list_display"]], "list_display (booking.admin.teamadmin attribute)": [[2, "booking.admin.TeamAdmin.list_display"]], "list_display (booking.admin.ticketadmin attribute)": [[2, "booking.admin.TicketAdmin.list_display"]], "list_filter (booking.admin.invoiceadmin attribute)": [[2, "booking.admin.InvoiceAdmin.list_filter"]], "list_filter (booking.admin.matchadmin attribute)": [[2, "booking.admin.MatchAdmin.list_filter"]], "list_filter (booking.admin.paymentadmin attribute)": [[2, "booking.admin.PaymentAdmin.list_filter"]], "list_filter (booking.admin.stadiumadmin attribute)": [[2, "booking.admin.StadiumAdmin.list_filter"]], "list_filter (booking.admin.teamadmin attribute)": [[2, "booking.admin.TeamAdmin.list_filter"]], "list_filter (booking.admin.ticketadmin attribute)": [[2, "booking.admin.TicketAdmin.list_filter"]], "media (booking.admin.invoiceadmin property)": [[2, "booking.admin.InvoiceAdmin.media"]], "media (booking.admin.matchadmin property)": [[2, "booking.admin.MatchAdmin.media"]], "media (booking.admin.paymentadmin property)": [[2, "booking.admin.PaymentAdmin.media"]], "media (booking.admin.sectioninlineadmin property)": [[2, "booking.admin.SectionInlineAdmin.media"]], "media (booking.admin.stadiumadmin property)": [[2, "booking.admin.StadiumAdmin.media"]], "media (booking.admin.teamadmin property)": [[2, "booking.admin.TeamAdmin.media"]], "media (booking.admin.ticketadmin property)": [[2, "booking.admin.TicketAdmin.media"]], "min_num (booking.admin.sectioninlineadmin attribute)": [[2, "booking.admin.SectionInlineAdmin.min_num"]], "model (booking.admin.sectioninlineadmin attribute)": [[2, "booking.admin.SectionInlineAdmin.model"]], "module": [[2, "module-booking"], [2, "module-booking.admin"], [2, "module-booking.apps"], [2, "module-booking.urls"], [3, "module-booking.models"], [3, "module-booking.models.invoice"], [3, "module-booking.models.match"], [3, "module-booking.models.payment"], [3, "module-booking.models.section"], [3, "module-booking.models.stadium"], [3, "module-booking.models.team"], [3, "module-booking.models.ticket"], [4, "module-booking.serializers"], [4, "module-booking.serializers.invoice"], [4, "module-booking.serializers.match"], [4, "module-booking.serializers.payment"], [4, "module-booking.serializers.section"], [4, "module-booking.serializers.stadium"], [4, "module-booking.serializers.team"], [4, "module-booking.serializers.ticket"], [5, "module-booking.views"], [5, "module-booking.views.invoice"], [5, "module-booking.views.match"], [5, "module-booking.views.payment"], [5, "module-booking.views.section"], [5, "module-booking.views.stadium"], [5, "module-booking.views.team"], [5, "module-booking.views.ticket"], [11, "module-user_management"], [11, "module-user_management.admin"], [11, "module-user_management.apps"], [11, "module-user_management.managers"], [11, "module-user_management.signals"], [11, "module-user_management.urls"]], "name (booking.apps.bookingconfig attribute)": [[2, "booking.apps.BookingConfig.name"]], "search_fields (booking.admin.invoiceadmin attribute)": [[2, "booking.admin.InvoiceAdmin.search_fields"]], "search_fields (booking.admin.matchadmin attribute)": [[2, "booking.admin.MatchAdmin.search_fields"]], "search_fields (booking.admin.paymentadmin attribute)": [[2, "booking.admin.PaymentAdmin.search_fields"]], "search_fields (booking.admin.stadiumadmin attribute)": [[2, "booking.admin.StadiumAdmin.search_fields"]], "search_fields (booking.admin.teamadmin attribute)": [[2, "booking.admin.TeamAdmin.search_fields"]], "search_fields (booking.admin.ticketadmin attribute)": [[2, "booking.admin.TicketAdmin.search_fields"]], "verbose_name (booking.apps.bookingconfig attribute)": [[2, "booking.apps.BookingConfig.verbose_name"]], "invoice (class in booking.models)": [[3, "booking.models.Invoice"]], "invoice (class in booking.models.invoice)": [[3, "booking.models.invoice.Invoice"]], "invoice.doesnotexist": [[3, "booking.models.Invoice.DoesNotExist"], [3, "booking.models.invoice.Invoice.DoesNotExist"]], "invoice.multipleobjectsreturned": [[3, "booking.models.Invoice.MultipleObjectsReturned"], [3, "booking.models.invoice.Invoice.MultipleObjectsReturned"]], "match (class in booking.models)": [[3, "booking.models.Match"]], "match (class in booking.models.match)": [[3, "booking.models.match.Match"]], "match.doesnotexist": [[3, "booking.models.Match.DoesNotExist"], [3, "booking.models.match.Match.DoesNotExist"]], "match.multipleobjectsreturned": [[3, "booking.models.Match.MultipleObjectsReturned"], [3, "booking.models.match.Match.MultipleObjectsReturned"]], "payment (class in booking.models)": [[3, "booking.models.Payment"]], "payment (class in booking.models.payment)": [[3, "booking.models.payment.Payment"]], "payment.doesnotexist": [[3, "booking.models.Payment.DoesNotExist"], [3, "booking.models.payment.Payment.DoesNotExist"]], "payment.multipleobjectsreturned": [[3, "booking.models.Payment.MultipleObjectsReturned"], [3, "booking.models.payment.Payment.MultipleObjectsReturned"]], "section (class in booking.models)": [[3, "booking.models.Section"]], "section (class in booking.models.section)": [[3, "booking.models.section.Section"]], "section.doesnotexist": [[3, "booking.models.Section.DoesNotExist"], [3, "booking.models.section.Section.DoesNotExist"]], "section.multipleobjectsreturned": [[3, "booking.models.Section.MultipleObjectsReturned"], [3, "booking.models.section.Section.MultipleObjectsReturned"]], "stadium (class in booking.models)": [[3, "booking.models.Stadium"]], "stadium (class in booking.models.stadium)": [[3, "booking.models.stadium.Stadium"]], "stadium.doesnotexist": [[3, "booking.models.Stadium.DoesNotExist"], [3, "booking.models.stadium.Stadium.DoesNotExist"]], "stadium.multipleobjectsreturned": [[3, "booking.models.Stadium.MultipleObjectsReturned"], [3, "booking.models.stadium.Stadium.MultipleObjectsReturned"]], "team (class in booking.models)": [[3, "booking.models.Team"]], "team (class in booking.models.team)": [[3, "booking.models.team.Team"]], "team.doesnotexist": [[3, "booking.models.Team.DoesNotExist"], [3, "booking.models.team.Team.DoesNotExist"]], "team.multipleobjectsreturned": [[3, "booking.models.Team.MultipleObjectsReturned"], [3, "booking.models.team.Team.MultipleObjectsReturned"]], "ticket (class in booking.models)": [[3, "booking.models.Ticket"]], "ticket (class in booking.models.ticket)": [[3, "booking.models.ticket.Ticket"]], "ticket.doesnotexist": [[3, "booking.models.Ticket.DoesNotExist"], [3, "booking.models.ticket.Ticket.DoesNotExist"]], "ticket.multipleobjectsreturned": [[3, "booking.models.Ticket.MultipleObjectsReturned"], [3, "booking.models.ticket.Ticket.MultipleObjectsReturned"]], "address (booking.models.stadium attribute)": [[3, "booking.models.Stadium.address"]], "address (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.address"]], "amount (booking.models.payment attribute)": [[3, "booking.models.Payment.amount"]], "amount (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.amount"]], "booking.models": [[3, "module-booking.models"]], "booking.models.invoice": [[3, "module-booking.models.invoice"]], "booking.models.match": [[3, "module-booking.models.match"]], "booking.models.payment": [[3, "module-booking.models.payment"]], "booking.models.section": [[3, "module-booking.models.section"]], "booking.models.stadium": [[3, "module-booking.models.stadium"]], "booking.models.team": [[3, "module-booking.models.team"]], "booking.models.ticket": [[3, "module-booking.models.ticket"]], "capacity (booking.models.section attribute)": [[3, "booking.models.Section.capacity"]], "capacity (booking.models.section.section attribute)": [[3, "booking.models.section.Section.capacity"]], "city (booking.models.stadium attribute)": [[3, "booking.models.Stadium.city"]], "city (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.city"]], "created_at (booking.models.invoice attribute)": [[3, "booking.models.Invoice.created_at"]], "created_at (booking.models.match attribute)": [[3, "booking.models.Match.created_at"]], "created_at (booking.models.payment attribute)": [[3, "booking.models.Payment.created_at"]], "created_at (booking.models.section attribute)": [[3, "booking.models.Section.created_at"]], "created_at (booking.models.stadium attribute)": [[3, "booking.models.Stadium.created_at"]], "created_at (booking.models.team attribute)": [[3, "booking.models.Team.created_at"]], "created_at (booking.models.ticket attribute)": [[3, "booking.models.Ticket.created_at"]], "created_at (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.created_at"]], "created_at (booking.models.match.match attribute)": [[3, "booking.models.match.Match.created_at"]], "created_at (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.created_at"]], "created_at (booking.models.section.section attribute)": [[3, "booking.models.section.Section.created_at"]], "created_at (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.created_at"]], "created_at (booking.models.team.team attribute)": [[3, "booking.models.team.Team.created_at"]], "created_at (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.created_at"]], "get_list_of_seat_numbers (booking.models.section property)": [[3, "booking.models.Section.get_list_of_seat_numbers"]], "get_list_of_seat_numbers (booking.models.section.section property)": [[3, "booking.models.section.Section.get_list_of_seat_numbers"]], "get_location_display() (booking.models.section method)": [[3, "booking.models.Section.get_location_display"]], "get_location_display() (booking.models.section.section method)": [[3, "booking.models.section.Section.get_location_display"]], "get_next_by_created_at() (booking.models.invoice method)": [[3, "booking.models.Invoice.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.match method)": [[3, "booking.models.Match.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.payment method)": [[3, "booking.models.Payment.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.section method)": [[3, "booking.models.Section.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.stadium method)": [[3, "booking.models.Stadium.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.team method)": [[3, "booking.models.Team.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.ticket method)": [[3, "booking.models.Ticket.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.invoice.invoice method)": [[3, "booking.models.invoice.Invoice.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.match.match method)": [[3, "booking.models.match.Match.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.payment.payment method)": [[3, "booking.models.payment.Payment.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.section.section method)": [[3, "booking.models.section.Section.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.stadium.stadium method)": [[3, "booking.models.stadium.Stadium.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.team.team method)": [[3, "booking.models.team.Team.get_next_by_created_at"]], "get_next_by_created_at() (booking.models.ticket.ticket method)": [[3, "booking.models.ticket.Ticket.get_next_by_created_at"]], "get_next_by_start_time() (booking.models.match method)": [[3, "booking.models.Match.get_next_by_start_time"]], "get_next_by_start_time() (booking.models.match.match method)": [[3, "booking.models.match.Match.get_next_by_start_time"]], "get_next_by_updated_at() (booking.models.invoice method)": [[3, "booking.models.Invoice.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.match method)": [[3, "booking.models.Match.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.section method)": [[3, "booking.models.Section.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.stadium method)": [[3, "booking.models.Stadium.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.team method)": [[3, "booking.models.Team.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.ticket method)": [[3, "booking.models.Ticket.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.invoice.invoice method)": [[3, "booking.models.invoice.Invoice.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.match.match method)": [[3, "booking.models.match.Match.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.section.section method)": [[3, "booking.models.section.Section.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.stadium.stadium method)": [[3, "booking.models.stadium.Stadium.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.team.team method)": [[3, "booking.models.team.Team.get_next_by_updated_at"]], "get_next_by_updated_at() (booking.models.ticket.ticket method)": [[3, "booking.models.ticket.Ticket.get_next_by_updated_at"]], "get_previous_by_created_at() (booking.models.invoice method)": [[3, "booking.models.Invoice.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.match method)": [[3, "booking.models.Match.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.payment method)": [[3, "booking.models.Payment.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.section method)": [[3, "booking.models.Section.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.stadium method)": [[3, "booking.models.Stadium.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.team method)": [[3, "booking.models.Team.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.ticket method)": [[3, "booking.models.Ticket.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.invoice.invoice method)": [[3, "booking.models.invoice.Invoice.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.match.match method)": [[3, "booking.models.match.Match.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.payment.payment method)": [[3, "booking.models.payment.Payment.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.section.section method)": [[3, "booking.models.section.Section.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.stadium.stadium method)": [[3, "booking.models.stadium.Stadium.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.team.team method)": [[3, "booking.models.team.Team.get_previous_by_created_at"]], "get_previous_by_created_at() (booking.models.ticket.ticket method)": [[3, "booking.models.ticket.Ticket.get_previous_by_created_at"]], "get_previous_by_start_time() (booking.models.match method)": [[3, "booking.models.Match.get_previous_by_start_time"]], "get_previous_by_start_time() (booking.models.match.match method)": [[3, "booking.models.match.Match.get_previous_by_start_time"]], "get_previous_by_updated_at() (booking.models.invoice method)": [[3, "booking.models.Invoice.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.match method)": [[3, "booking.models.Match.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.section method)": [[3, "booking.models.Section.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.stadium method)": [[3, "booking.models.Stadium.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.team method)": [[3, "booking.models.Team.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.ticket method)": [[3, "booking.models.Ticket.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.invoice.invoice method)": [[3, "booking.models.invoice.Invoice.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.match.match method)": [[3, "booking.models.match.Match.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.section.section method)": [[3, "booking.models.section.Section.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.stadium.stadium method)": [[3, "booking.models.stadium.Stadium.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.team.team method)": [[3, "booking.models.team.Team.get_previous_by_updated_at"]], "get_previous_by_updated_at() (booking.models.ticket.ticket method)": [[3, "booking.models.ticket.Ticket.get_previous_by_updated_at"]], "get_section_ids (booking.models.stadium property)": [[3, "booking.models.Stadium.get_section_ids"]], "get_section_ids (booking.models.stadium.stadium property)": [[3, "booking.models.stadium.Stadium.get_section_ids"]], "get_status_display() (booking.models.invoice method)": [[3, "booking.models.Invoice.get_status_display"]], "get_status_display() (booking.models.payment method)": [[3, "booking.models.Payment.get_status_display"]], "get_status_display() (booking.models.ticket method)": [[3, "booking.models.Ticket.get_status_display"]], "get_status_display() (booking.models.invoice.invoice method)": [[3, "booking.models.invoice.Invoice.get_status_display"]], "get_status_display() (booking.models.payment.payment method)": [[3, "booking.models.payment.Payment.get_status_display"]], "get_status_display() (booking.models.ticket.ticket method)": [[3, "booking.models.ticket.Ticket.get_status_display"]], "get_total_amount (booking.models.invoice property)": [[3, "booking.models.Invoice.get_total_amount"]], "get_total_amount (booking.models.invoice.invoice property)": [[3, "booking.models.invoice.Invoice.get_total_amount"]], "guest_team (booking.models.match attribute)": [[3, "booking.models.Match.guest_team"]], "guest_team (booking.models.match.match attribute)": [[3, "booking.models.match.Match.guest_team"]], "guest_team_id (booking.models.match attribute)": [[3, "booking.models.Match.guest_team_id"]], "guest_team_id (booking.models.match.match attribute)": [[3, "booking.models.match.Match.guest_team_id"]], "host_team (booking.models.match attribute)": [[3, "booking.models.Match.host_team"]], "host_team (booking.models.match.match attribute)": [[3, "booking.models.match.Match.host_team"]], "host_team_id (booking.models.match attribute)": [[3, "booking.models.Match.host_team_id"]], "host_team_id (booking.models.match.match attribute)": [[3, "booking.models.match.Match.host_team_id"]], "id (booking.models.invoice attribute)": [[3, "booking.models.Invoice.id"]], "id (booking.models.match attribute)": [[3, "booking.models.Match.id"]], "id (booking.models.payment attribute)": [[3, "booking.models.Payment.id"]], "id (booking.models.section attribute)": [[3, "booking.models.Section.id"]], "id (booking.models.stadium attribute)": [[3, "booking.models.Stadium.id"]], "id (booking.models.team attribute)": [[3, "booking.models.Team.id"]], "id (booking.models.ticket attribute)": [[3, "booking.models.Ticket.id"]], "id (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.id"]], "id (booking.models.match.match attribute)": [[3, "booking.models.match.Match.id"]], "id (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.id"]], "id (booking.models.section.section attribute)": [[3, "booking.models.section.Section.id"]], "id (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.id"]], "id (booking.models.team.team attribute)": [[3, "booking.models.team.Team.id"]], "id (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.id"]], "invoice (booking.models.payment attribute)": [[3, "booking.models.Payment.invoice"]], "invoice (booking.models.ticket attribute)": [[3, "booking.models.Ticket.invoice"]], "invoice (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.invoice"]], "invoice (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.invoice"]], "invoice_id (booking.models.payment attribute)": [[3, "booking.models.Payment.invoice_id"]], "invoice_id (booking.models.ticket attribute)": [[3, "booking.models.Ticket.invoice_id"]], "invoice_id (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.invoice_id"]], "invoice_id (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.invoice_id"]], "location (booking.models.section attribute)": [[3, "booking.models.Section.location"]], "location (booking.models.section.section attribute)": [[3, "booking.models.section.Section.location"]], "map_url (booking.models.stadium attribute)": [[3, "booking.models.Stadium.map_url"]], "map_url (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.map_url"]], "match (booking.models.ticket attribute)": [[3, "booking.models.Ticket.match"]], "match (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.match"]], "match_guest_teams (booking.models.team attribute)": [[3, "booking.models.Team.match_guest_teams"]], "match_guest_teams (booking.models.team.team attribute)": [[3, "booking.models.team.Team.match_guest_teams"]], "match_host_teams (booking.models.team attribute)": [[3, "booking.models.Team.match_host_teams"]], "match_host_teams (booking.models.team.team attribute)": [[3, "booking.models.team.Team.match_host_teams"]], "match_id (booking.models.ticket attribute)": [[3, "booking.models.Ticket.match_id"]], "match_id (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.match_id"]], "match_stadiums (booking.models.stadium attribute)": [[3, "booking.models.Stadium.match_stadiums"]], "match_stadiums (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.match_stadiums"]], "name (booking.models.stadium attribute)": [[3, "booking.models.Stadium.name"]], "name (booking.models.team attribute)": [[3, "booking.models.Team.name"]], "name (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.name"]], "name (booking.models.team.team attribute)": [[3, "booking.models.team.Team.name"]], "objects (booking.models.invoice attribute)": [[3, "booking.models.Invoice.objects"]], "objects (booking.models.match attribute)": [[3, "booking.models.Match.objects"]], "objects (booking.models.payment attribute)": [[3, "booking.models.Payment.objects"]], "objects (booking.models.section attribute)": [[3, "booking.models.Section.objects"]], "objects (booking.models.stadium attribute)": [[3, "booking.models.Stadium.objects"]], "objects (booking.models.team attribute)": [[3, "booking.models.Team.objects"]], "objects (booking.models.ticket attribute)": [[3, "booking.models.Ticket.objects"]], "objects (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.objects"]], "objects (booking.models.match.match attribute)": [[3, "booking.models.match.Match.objects"]], "objects (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.objects"]], "objects (booking.models.section.section attribute)": [[3, "booking.models.section.Section.objects"]], "objects (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.objects"]], "objects (booking.models.team.team attribute)": [[3, "booking.models.team.Team.objects"]], "objects (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.objects"]], "payment_invoices (booking.models.invoice attribute)": [[3, "booking.models.Invoice.payment_invoices"]], "payment_invoices (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.payment_invoices"]], "price (booking.models.section attribute)": [[3, "booking.models.Section.price"]], "price (booking.models.section.section attribute)": [[3, "booking.models.section.Section.price"]], "province (booking.models.stadium attribute)": [[3, "booking.models.Stadium.province"]], "province (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.province"]], "seat_number (booking.models.ticket attribute)": [[3, "booking.models.Ticket.seat_number"]], "seat_number (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.seat_number"]], "section (booking.models.ticket attribute)": [[3, "booking.models.Ticket.section"]], "section (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.section"]], "section_id (booking.models.ticket attribute)": [[3, "booking.models.Ticket.section_id"]], "section_id (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.section_id"]], "section_stadiums (booking.models.stadium attribute)": [[3, "booking.models.Stadium.section_stadiums"]], "section_stadiums (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.section_stadiums"]], "set_as_paid() (booking.models.invoice method)": [[3, "booking.models.Invoice.set_as_paid"]], "set_as_paid() (booking.models.invoice.invoice method)": [[3, "booking.models.invoice.Invoice.set_as_paid"]], "set_as_sold() (booking.models.ticket method)": [[3, "booking.models.Ticket.set_as_sold"]], "set_as_sold() (booking.models.ticket.ticket method)": [[3, "booking.models.ticket.Ticket.set_as_sold"]], "stadium (booking.models.match attribute)": [[3, "booking.models.Match.stadium"]], "stadium (booking.models.section attribute)": [[3, "booking.models.Section.stadium"]], "stadium (booking.models.match.match attribute)": [[3, "booking.models.match.Match.stadium"]], "stadium (booking.models.section.section attribute)": [[3, "booking.models.section.Section.stadium"]], "stadium_id (booking.models.match attribute)": [[3, "booking.models.Match.stadium_id"]], "stadium_id (booking.models.section attribute)": [[3, "booking.models.Section.stadium_id"]], "stadium_id (booking.models.match.match attribute)": [[3, "booking.models.match.Match.stadium_id"]], "stadium_id (booking.models.section.section attribute)": [[3, "booking.models.section.Section.stadium_id"]], "start_time (booking.models.match attribute)": [[3, "booking.models.Match.start_time"]], "start_time (booking.models.match.match attribute)": [[3, "booking.models.match.Match.start_time"]], "status (booking.models.invoice attribute)": [[3, "booking.models.Invoice.status"]], "status (booking.models.payment attribute)": [[3, "booking.models.Payment.status"]], "status (booking.models.ticket attribute)": [[3, "booking.models.Ticket.status"]], "status (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.status"]], "status (booking.models.payment.payment attribute)": [[3, "booking.models.payment.Payment.status"]], "status (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.status"]], "ticket_invoices (booking.models.invoice attribute)": [[3, "booking.models.Invoice.ticket_invoices"]], "ticket_invoices (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.ticket_invoices"]], "ticket_matches (booking.models.match attribute)": [[3, "booking.models.Match.ticket_matches"]], "ticket_matches (booking.models.match.match attribute)": [[3, "booking.models.match.Match.ticket_matches"]], "ticket_sections (booking.models.section attribute)": [[3, "booking.models.Section.ticket_sections"]], "ticket_sections (booking.models.section.section attribute)": [[3, "booking.models.section.Section.ticket_sections"]], "updated_at (booking.models.invoice attribute)": [[3, "booking.models.Invoice.updated_at"]], "updated_at (booking.models.match attribute)": [[3, "booking.models.Match.updated_at"]], "updated_at (booking.models.section attribute)": [[3, "booking.models.Section.updated_at"]], "updated_at (booking.models.stadium attribute)": [[3, "booking.models.Stadium.updated_at"]], "updated_at (booking.models.team attribute)": [[3, "booking.models.Team.updated_at"]], "updated_at (booking.models.ticket attribute)": [[3, "booking.models.Ticket.updated_at"]], "updated_at (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.updated_at"]], "updated_at (booking.models.match.match attribute)": [[3, "booking.models.match.Match.updated_at"]], "updated_at (booking.models.section.section attribute)": [[3, "booking.models.section.Section.updated_at"]], "updated_at (booking.models.stadium.stadium attribute)": [[3, "booking.models.stadium.Stadium.updated_at"]], "updated_at (booking.models.team.team attribute)": [[3, "booking.models.team.Team.updated_at"]], "updated_at (booking.models.ticket.ticket attribute)": [[3, "booking.models.ticket.Ticket.updated_at"]], "user (booking.models.invoice attribute)": [[3, "booking.models.Invoice.user"]], "user (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.user"]], "user_id (booking.models.invoice attribute)": [[3, "booking.models.Invoice.user_id"]], "user_id (booking.models.invoice.invoice attribute)": [[3, "booking.models.invoice.Invoice.user_id"]], "invoiceserializer (class in booking.serializers)": [[4, "booking.serializers.InvoiceSerializer"]], "invoiceserializer (class in booking.serializers.invoice)": [[4, "booking.serializers.invoice.InvoiceSerializer"]], "matchserializer (class in booking.serializers)": [[4, "booking.serializers.MatchSerializer"]], "matchserializer (class in booking.serializers.match)": [[4, "booking.serializers.match.MatchSerializer"]], "paymentserializer (class in booking.serializers)": [[4, "booking.serializers.PaymentSerializer"]], "paymentserializer (class in booking.serializers.payment)": [[4, "booking.serializers.payment.PaymentSerializer"]], "sectionserializer (class in booking.serializers)": [[4, "booking.serializers.SectionSerializer"]], "sectionserializer (class in booking.serializers.section)": [[4, "booking.serializers.section.SectionSerializer"]], "stadiumserializer (class in booking.serializers)": [[4, "booking.serializers.StadiumSerializer"]], "stadiumserializer (class in booking.serializers.stadium)": [[4, "booking.serializers.stadium.StadiumSerializer"]], "teamserializer (class in booking.serializers)": [[4, "booking.serializers.TeamSerializer"]], "teamserializer (class in booking.serializers.team)": [[4, "booking.serializers.team.TeamSerializer"]], "ticketserializer (class in booking.serializers)": [[4, "booking.serializers.TicketSerializer"]], "ticketserializer (class in booking.serializers.ticket)": [[4, "booking.serializers.ticket.TicketSerializer"]], "booking.serializers": [[4, "module-booking.serializers"]], "booking.serializers.invoice": [[4, "module-booking.serializers.invoice"]], "booking.serializers.match": [[4, "module-booking.serializers.match"]], "booking.serializers.payment": [[4, "module-booking.serializers.payment"]], "booking.serializers.section": [[4, "module-booking.serializers.section"]], "booking.serializers.stadium": [[4, "module-booking.serializers.stadium"]], "booking.serializers.team": [[4, "module-booking.serializers.team"]], "booking.serializers.ticket": [[4, "module-booking.serializers.ticket"]], "create() (booking.serializers.paymentserializer method)": [[4, "booking.serializers.PaymentSerializer.create"]], "create() (booking.serializers.ticketserializer method)": [[4, "booking.serializers.TicketSerializer.create"]], "create() (booking.serializers.payment.paymentserializer method)": [[4, "booking.serializers.payment.PaymentSerializer.create"]], "create() (booking.serializers.ticket.ticketserializer method)": [[4, "booking.serializers.ticket.TicketSerializer.create"]], "to_representation() (booking.serializers.invoiceserializer method)": [[4, "booking.serializers.InvoiceSerializer.to_representation"]], "to_representation() (booking.serializers.paymentserializer method)": [[4, "booking.serializers.PaymentSerializer.to_representation"]], "to_representation() (booking.serializers.sectionserializer method)": [[4, "booking.serializers.SectionSerializer.to_representation"]], "to_representation() (booking.serializers.ticketserializer method)": [[4, "booking.serializers.TicketSerializer.to_representation"]], "to_representation() (booking.serializers.invoice.invoiceserializer method)": [[4, "booking.serializers.invoice.InvoiceSerializer.to_representation"]], "to_representation() (booking.serializers.payment.paymentserializer method)": [[4, "booking.serializers.payment.PaymentSerializer.to_representation"]], "to_representation() (booking.serializers.section.sectionserializer method)": [[4, "booking.serializers.section.SectionSerializer.to_representation"]], "to_representation() (booking.serializers.ticket.ticketserializer method)": [[4, "booking.serializers.ticket.TicketSerializer.to_representation"]], "validate() (booking.serializers.paymentserializer method)": [[4, "booking.serializers.PaymentSerializer.validate"]], "validate() (booking.serializers.ticketserializer method)": [[4, "booking.serializers.TicketSerializer.validate"]], "validate() (booking.serializers.payment.paymentserializer method)": [[4, "booking.serializers.payment.PaymentSerializer.validate"]], "validate() (booking.serializers.ticket.ticketserializer method)": [[4, "booking.serializers.ticket.TicketSerializer.validate"]], "invoiceviewset (class in booking.views)": [[5, "booking.views.InvoiceViewSet"]], "invoiceviewset (class in booking.views.invoice)": [[5, "booking.views.invoice.InvoiceViewSet"]], "matchviewset (class in booking.views)": [[5, "booking.views.MatchViewSet"]], "matchviewset (class in booking.views.match)": [[5, "booking.views.match.MatchViewSet"]], "paymentviewset (class in booking.views)": [[5, "booking.views.PaymentViewSet"]], "paymentviewset (class in booking.views.payment)": [[5, "booking.views.payment.PaymentViewSet"]], "sectionviewset (class in booking.views)": [[5, "booking.views.SectionViewSet"]], "sectionviewset (class in booking.views.section)": [[5, "booking.views.section.SectionViewSet"]], "stadiumviewset (class in booking.views)": [[5, "booking.views.StadiumViewSet"]], "stadiumviewset (class in booking.views.stadium)": [[5, "booking.views.stadium.StadiumViewSet"]], "teamviewset (class in booking.views)": [[5, "booking.views.TeamViewSet"]], "teamviewset (class in booking.views.team)": [[5, "booking.views.team.TeamViewSet"]], "ticketviewset (class in booking.views)": [[5, "booking.views.TicketViewSet"]], "ticketviewset (class in booking.views.ticket)": [[5, "booking.views.ticket.TicketViewSet"]], "basename (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.basename"]], "basename (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.basename"]], "basename (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.basename"]], "basename (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.basename"]], "basename (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.basename"]], "basename (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.basename"]], "basename (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.basename"]], "basename (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.basename"]], "basename (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.basename"]], "basename (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.basename"]], "basename (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.basename"]], "basename (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.basename"]], "basename (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.basename"]], "basename (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.basename"]], "booking.views": [[5, "module-booking.views"]], "booking.views.invoice": [[5, "module-booking.views.invoice"]], "booking.views.match": [[5, "module-booking.views.match"]], "booking.views.payment": [[5, "module-booking.views.payment"]], "booking.views.section": [[5, "module-booking.views.section"]], "booking.views.stadium": [[5, "module-booking.views.stadium"]], "booking.views.team": [[5, "module-booking.views.team"]], "booking.views.ticket": [[5, "module-booking.views.ticket"]], "description (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.description"]], "description (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.description"]], "description (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.description"]], "description (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.description"]], "description (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.description"]], "description (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.description"]], "description (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.description"]], "description (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.description"]], "description (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.description"]], "description (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.description"]], "description (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.description"]], "description (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.description"]], "description (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.description"]], "description (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.description"]], "detail (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.detail"]], "detail (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.detail"]], "detail (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.detail"]], "detail (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.detail"]], "detail (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.detail"]], "detail (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.detail"]], "detail (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.detail"]], "detail (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.detail"]], "detail (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.detail"]], "detail (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.detail"]], "detail (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.detail"]], "detail (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.detail"]], "detail (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.detail"]], "detail (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.detail"]], "filter_backends (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.filter_backends"]], "filter_backends (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.filter_backends"]], "filter_backends (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.filter_backends"]], "filter_backends (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.filter_backends"]], "filter_backends (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.filter_backends"]], "filter_backends (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.filter_backends"]], "filter_backends (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.filter_backends"]], "filter_backends (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.filter_backends"]], "filter_backends (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.filter_backends"]], "filter_backends (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.filter_backends"]], "filter_backends (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.filter_backends"]], "filter_backends (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.filter_backends"]], "filter_backends (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.filter_backends"]], "filter_backends (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.filter_backends"]], "filterset_fields (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.filterset_fields"]], "filterset_fields (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.filterset_fields"]], "filterset_fields (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.filterset_fields"]], "filterset_fields (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.filterset_fields"]], "filterset_fields (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.filterset_fields"]], "filterset_fields (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.filterset_fields"]], "filterset_fields (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.filterset_fields"]], "filterset_fields (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.filterset_fields"]], "filterset_fields (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.filterset_fields"]], "filterset_fields (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.filterset_fields"]], "filterset_fields (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.filterset_fields"]], "filterset_fields (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.filterset_fields"]], "filterset_fields (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.filterset_fields"]], "filterset_fields (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.filterset_fields"]], "name (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.name"]], "name (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.name"]], "name (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.name"]], "name (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.name"]], "name (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.name"]], "name (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.name"]], "name (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.name"]], "name (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.name"]], "name (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.name"]], "name (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.name"]], "name (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.name"]], "name (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.name"]], "name (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.name"]], "name (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.name"]], "permission_classes (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.permission_classes"]], "permission_classes (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.permission_classes"]], "permission_classes (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.permission_classes"]], "permission_classes (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.permission_classes"]], "permission_classes (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.permission_classes"]], "permission_classes (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.permission_classes"]], "permission_classes (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.permission_classes"]], "permission_classes (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.permission_classes"]], "permission_classes (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.permission_classes"]], "permission_classes (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.permission_classes"]], "permission_classes (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.permission_classes"]], "permission_classes (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.permission_classes"]], "permission_classes (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.permission_classes"]], "permission_classes (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.permission_classes"]], "queryset (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.queryset"]], "queryset (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.queryset"]], "queryset (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.queryset"]], "queryset (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.queryset"]], "queryset (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.queryset"]], "queryset (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.queryset"]], "queryset (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.queryset"]], "queryset (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.queryset"]], "queryset (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.queryset"]], "queryset (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.queryset"]], "queryset (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.queryset"]], "queryset (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.queryset"]], "queryset (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.queryset"]], "queryset (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.queryset"]], "search_fields (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.search_fields"]], "search_fields (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.search_fields"]], "search_fields (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.search_fields"]], "search_fields (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.search_fields"]], "search_fields (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.search_fields"]], "search_fields (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.search_fields"]], "search_fields (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.search_fields"]], "search_fields (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.search_fields"]], "search_fields (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.search_fields"]], "search_fields (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.search_fields"]], "search_fields (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.search_fields"]], "search_fields (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.search_fields"]], "search_fields (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.search_fields"]], "search_fields (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.search_fields"]], "seats() (booking.views.matchviewset method)": [[5, "booking.views.MatchViewSet.seats"]], "seats() (booking.views.match.matchviewset method)": [[5, "booking.views.match.MatchViewSet.seats"]], "serializer_class (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.serializer_class"]], "serializer_class (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.serializer_class"]], "serializer_class (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.serializer_class"]], "serializer_class (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.serializer_class"]], "serializer_class (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.serializer_class"]], "serializer_class (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.serializer_class"]], "serializer_class (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.serializer_class"]], "serializer_class (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.serializer_class"]], "serializer_class (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.serializer_class"]], "serializer_class (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.serializer_class"]], "serializer_class (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.serializer_class"]], "serializer_class (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.serializer_class"]], "serializer_class (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.serializer_class"]], "serializer_class (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.serializer_class"]], "suffix (booking.views.invoiceviewset attribute)": [[5, "booking.views.InvoiceViewSet.suffix"]], "suffix (booking.views.matchviewset attribute)": [[5, "booking.views.MatchViewSet.suffix"]], "suffix (booking.views.paymentviewset attribute)": [[5, "booking.views.PaymentViewSet.suffix"]], "suffix (booking.views.sectionviewset attribute)": [[5, "booking.views.SectionViewSet.suffix"]], "suffix (booking.views.stadiumviewset attribute)": [[5, "booking.views.StadiumViewSet.suffix"]], "suffix (booking.views.teamviewset attribute)": [[5, "booking.views.TeamViewSet.suffix"]], "suffix (booking.views.ticketviewset attribute)": [[5, "booking.views.TicketViewSet.suffix"]], "suffix (booking.views.invoice.invoiceviewset attribute)": [[5, "booking.views.invoice.InvoiceViewSet.suffix"]], "suffix (booking.views.match.matchviewset attribute)": [[5, "booking.views.match.MatchViewSet.suffix"]], "suffix (booking.views.payment.paymentviewset attribute)": [[5, "booking.views.payment.PaymentViewSet.suffix"]], "suffix (booking.views.section.sectionviewset attribute)": [[5, "booking.views.section.SectionViewSet.suffix"]], "suffix (booking.views.stadium.stadiumviewset attribute)": [[5, "booking.views.stadium.StadiumViewSet.suffix"]], "suffix (booking.views.team.teamviewset attribute)": [[5, "booking.views.team.TeamViewSet.suffix"]], "suffix (booking.views.ticket.ticketviewset attribute)": [[5, "booking.views.ticket.TicketViewSet.suffix"]], "profileinlineadmin (class in user_management.admin)": [[11, "user_management.admin.ProfileInlineAdmin"]], "useradmin (class in user_management.admin)": [[11, "user_management.admin.UserAdmin"]], "usermanagementconfig (class in user_management.apps)": [[11, "user_management.apps.UserManagementConfig"]], "usermanager (class in user_management.managers)": [[11, "user_management.managers.UserManager"]], "actions (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.actions"]], "add_fieldsets (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.add_fieldsets"]], "create_staff() (user_management.managers.usermanager method)": [[11, "user_management.managers.UserManager.create_staff"]], "create_superuser() (user_management.managers.usermanager method)": [[11, "user_management.managers.UserManager.create_superuser"]], "create_user() (user_management.managers.usermanager method)": [[11, "user_management.managers.UserManager.create_user"]], "creating_profile() (in module user_management.signals)": [[11, "user_management.signals.creating_profile"]], "default_auto_field (user_management.apps.usermanagementconfig attribute)": [[11, "user_management.apps.UserManagementConfig.default_auto_field"]], "get_full_name() (user_management.admin.useradmin method)": [[11, "user_management.admin.UserAdmin.get_full_name"]], "get_queryset() (user_management.admin.useradmin method)": [[11, "user_management.admin.UserAdmin.get_queryset"]], "inlines (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.inlines"]], "list_display (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.list_display"]], "list_filter (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.list_filter"]], "media (user_management.admin.profileinlineadmin property)": [[11, "user_management.admin.ProfileInlineAdmin.media"]], "media (user_management.admin.useradmin property)": [[11, "user_management.admin.UserAdmin.media"]], "model (user_management.admin.profileinlineadmin attribute)": [[11, "user_management.admin.ProfileInlineAdmin.model"]], "name (user_management.apps.usermanagementconfig attribute)": [[11, "user_management.apps.UserManagementConfig.name"]], "readonly_fields (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.readonly_fields"]], "ready() (user_management.apps.usermanagementconfig method)": [[11, "user_management.apps.UserManagementConfig.ready"]], "search_fields (user_management.admin.useradmin attribute)": [[11, "user_management.admin.UserAdmin.search_fields"]], "use_in_migrations (user_management.managers.usermanager attribute)": [[11, "user_management.managers.UserManager.use_in_migrations"]], "user_management": [[11, "module-user_management"]], "user_management.admin": [[11, "module-user_management.admin"]], "user_management.apps": [[11, "module-user_management.apps"]], "user_management.managers": [[11, "module-user_management.managers"]], "user_management.signals": [[11, "module-user_management.signals"]], "user_management.urls": [[11, "module-user_management.urls"]], "verbose_name (user_management.apps.usermanagementconfig attribute)": [[11, "user_management.apps.UserManagementConfig.verbose_name"]]}}) \ No newline at end of file diff --git a/docs/_rst/api.rst b/docs/_rst/api.rst new file mode 100644 index 00000000..3ac8f213 --- /dev/null +++ b/docs/_rst/api.rst @@ -0,0 +1,37 @@ +api package +=========== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + api.tests + +Submodules +---------- + +api.apps module +--------------- + +.. automodule:: api.apps + :members: + :undoc-members: + :show-inheritance: + +api.urls module +--------------- + +.. automodule:: api.urls + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/api.tests.rst b/docs/_rst/api.tests.rst new file mode 100644 index 00000000..1f3f72d5 --- /dev/null +++ b/docs/_rst/api.tests.rst @@ -0,0 +1,37 @@ +api.tests package +================= + +Submodules +---------- + +api.tests.test\_group module +---------------------------- + +.. automodule:: api.tests.test_group + :members: + :undoc-members: + :show-inheritance: + +api.tests.test\_permission module +--------------------------------- + +.. automodule:: api.tests.test_permission + :members: + :undoc-members: + :show-inheritance: + +api.tests.test\_user module +--------------------------- + +.. automodule:: api.tests.test_user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/booking.migrations.rst b/docs/_rst/booking.migrations.rst new file mode 100644 index 00000000..6178a157 --- /dev/null +++ b/docs/_rst/booking.migrations.rst @@ -0,0 +1,69 @@ +booking.migrations package +========================== + +Submodules +---------- + +booking.migrations.0001\_stadium module +--------------------------------------- + +.. automodule:: booking.migrations.0001_stadium + :members: + :undoc-members: + :show-inheritance: + +booking.migrations.0002\_section module +--------------------------------------- + +.. automodule:: booking.migrations.0002_section + :members: + :undoc-members: + :show-inheritance: + +booking.migrations.0003\_team module +------------------------------------ + +.. automodule:: booking.migrations.0003_team + :members: + :undoc-members: + :show-inheritance: + +booking.migrations.0004\_match module +------------------------------------- + +.. automodule:: booking.migrations.0004_match + :members: + :undoc-members: + :show-inheritance: + +booking.migrations.0005\_invoice module +--------------------------------------- + +.. automodule:: booking.migrations.0005_invoice + :members: + :undoc-members: + :show-inheritance: + +booking.migrations.0006\_ticket module +-------------------------------------- + +.. automodule:: booking.migrations.0006_ticket + :members: + :undoc-members: + :show-inheritance: + +booking.migrations.0007\_payment module +--------------------------------------- + +.. automodule:: booking.migrations.0007_payment + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.migrations + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/booking.models.rst b/docs/_rst/booking.models.rst new file mode 100644 index 00000000..a0196bca --- /dev/null +++ b/docs/_rst/booking.models.rst @@ -0,0 +1,69 @@ +booking.models package +====================== + +Submodules +---------- + +booking.models.invoice module +----------------------------- + +.. automodule:: booking.models.invoice + :members: + :undoc-members: + :show-inheritance: + +booking.models.match module +--------------------------- + +.. automodule:: booking.models.match + :members: + :undoc-members: + :show-inheritance: + +booking.models.payment module +----------------------------- + +.. automodule:: booking.models.payment + :members: + :undoc-members: + :show-inheritance: + +booking.models.section module +----------------------------- + +.. automodule:: booking.models.section + :members: + :undoc-members: + :show-inheritance: + +booking.models.stadium module +----------------------------- + +.. automodule:: booking.models.stadium + :members: + :undoc-members: + :show-inheritance: + +booking.models.team module +-------------------------- + +.. automodule:: booking.models.team + :members: + :undoc-members: + :show-inheritance: + +booking.models.ticket module +---------------------------- + +.. automodule:: booking.models.ticket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.models + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/booking.rst b/docs/_rst/booking.rst new file mode 100644 index 00000000..44e9afec --- /dev/null +++ b/docs/_rst/booking.rst @@ -0,0 +1,48 @@ +booking package +=============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + booking.migrations + booking.models + booking.serializers + booking.views + +Submodules +---------- + +booking.admin module +-------------------- + +.. automodule:: booking.admin + :members: + :undoc-members: + :show-inheritance: + +booking.apps module +------------------- + +.. automodule:: booking.apps + :members: + :undoc-members: + :show-inheritance: + +booking.urls module +------------------- + +.. automodule:: booking.urls + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/booking.serializers.rst b/docs/_rst/booking.serializers.rst new file mode 100644 index 00000000..2379d0cf --- /dev/null +++ b/docs/_rst/booking.serializers.rst @@ -0,0 +1,69 @@ +booking.serializers package +=========================== + +Submodules +---------- + +booking.serializers.invoice module +---------------------------------- + +.. automodule:: booking.serializers.invoice + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.match module +-------------------------------- + +.. automodule:: booking.serializers.match + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.payment module +---------------------------------- + +.. automodule:: booking.serializers.payment + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.section module +---------------------------------- + +.. automodule:: booking.serializers.section + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.stadium module +---------------------------------- + +.. automodule:: booking.serializers.stadium + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.team module +------------------------------- + +.. automodule:: booking.serializers.team + :members: + :undoc-members: + :show-inheritance: + +booking.serializers.ticket module +--------------------------------- + +.. automodule:: booking.serializers.ticket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.serializers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/booking.views.rst b/docs/_rst/booking.views.rst new file mode 100644 index 00000000..2fc6a16f --- /dev/null +++ b/docs/_rst/booking.views.rst @@ -0,0 +1,69 @@ +booking.views package +===================== + +Submodules +---------- + +booking.views.invoice module +---------------------------- + +.. automodule:: booking.views.invoice + :members: + :undoc-members: + :show-inheritance: + +booking.views.match module +-------------------------- + +.. automodule:: booking.views.match + :members: + :undoc-members: + :show-inheritance: + +booking.views.payment module +---------------------------- + +.. automodule:: booking.views.payment + :members: + :undoc-members: + :show-inheritance: + +booking.views.section module +---------------------------- + +.. automodule:: booking.views.section + :members: + :undoc-members: + :show-inheritance: + +booking.views.stadium module +---------------------------- + +.. automodule:: booking.views.stadium + :members: + :undoc-members: + :show-inheritance: + +booking.views.team module +------------------------- + +.. automodule:: booking.views.team + :members: + :undoc-members: + :show-inheritance: + +booking.views.ticket module +--------------------------- + +.. automodule:: booking.views.ticket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: booking.views + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/commands.rst b/docs/_rst/commands.rst new file mode 100644 index 00000000..dfd7af1d --- /dev/null +++ b/docs/_rst/commands.rst @@ -0,0 +1,21 @@ +commands package +================ + +Submodules +---------- + +commands.apps module +-------------------- + +.. automodule:: commands.apps + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: commands + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/core.rst b/docs/_rst/core.rst new file mode 100644 index 00000000..5dc8ee22 --- /dev/null +++ b/docs/_rst/core.rst @@ -0,0 +1,45 @@ +core package +============ + +Submodules +---------- + +core.asgi module +---------------- + +.. automodule:: core.asgi + :members: + :undoc-members: + :show-inheritance: + +core.settings module +-------------------- + +.. automodule:: core.settings + :members: + :undoc-members: + :show-inheritance: + +core.urls module +---------------- + +.. automodule:: core.urls + :members: + :undoc-members: + :show-inheritance: + +core.wsgi module +---------------- + +.. automodule:: core.wsgi + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/extensions.rst b/docs/_rst/extensions.rst new file mode 100644 index 00000000..7bd4a1b4 --- /dev/null +++ b/docs/_rst/extensions.rst @@ -0,0 +1,45 @@ +extensions package +================== + +Submodules +---------- + +extensions.abstract\_models module +---------------------------------- + +.. automodule:: extensions.abstract_models + :members: + :undoc-members: + :show-inheritance: + +extensions.choices module +------------------------- + +.. automodule:: extensions.choices + :members: + :undoc-members: + :show-inheritance: + +extensions.custom\_exception\_handlers module +--------------------------------------------- + +.. automodule:: extensions.custom_exception_handlers + :members: + :undoc-members: + :show-inheritance: + +extensions.custom\_permissions module +------------------------------------- + +.. automodule:: extensions.custom_permissions + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: extensions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/manage.rst b/docs/_rst/manage.rst new file mode 100644 index 00000000..776b9e34 --- /dev/null +++ b/docs/_rst/manage.rst @@ -0,0 +1,7 @@ +manage module +============= + +.. automodule:: manage + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/modules.rst b/docs/_rst/modules.rst new file mode 100644 index 00000000..1668a9bd --- /dev/null +++ b/docs/_rst/modules.rst @@ -0,0 +1,13 @@ +src +=== + +.. toctree:: + :maxdepth: 4 + + api + booking + commands + core + extensions + manage + user_management diff --git a/docs/_rst/user_management.migrations.rst b/docs/_rst/user_management.migrations.rst new file mode 100644 index 00000000..998cec8b --- /dev/null +++ b/docs/_rst/user_management.migrations.rst @@ -0,0 +1,29 @@ +user\_management.migrations package +=================================== + +Submodules +---------- + +user\_management.migrations.0001\_user module +--------------------------------------------- + +.. automodule:: user_management.migrations.0001_user + :members: + :undoc-members: + :show-inheritance: + +user\_management.migrations.0002\_profile module +------------------------------------------------ + +.. automodule:: user_management.migrations.0002_profile + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.migrations + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/user_management.models.rst b/docs/_rst/user_management.models.rst new file mode 100644 index 00000000..901f8724 --- /dev/null +++ b/docs/_rst/user_management.models.rst @@ -0,0 +1,29 @@ +user\_management.models package +=============================== + +Submodules +---------- + +user\_management.models.profile module +-------------------------------------- + +.. automodule:: user_management.models.profile + :members: + :undoc-members: + :show-inheritance: + +user\_management.models.user module +----------------------------------- + +.. automodule:: user_management.models.user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.models + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/user_management.rst b/docs/_rst/user_management.rst new file mode 100644 index 00000000..50a29c26 --- /dev/null +++ b/docs/_rst/user_management.rst @@ -0,0 +1,65 @@ +user\_management package +======================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + user_management.migrations + user_management.models + user_management.serializers + user_management.tests + user_management.views + +Submodules +---------- + +user\_management.admin module +----------------------------- + +.. automodule:: user_management.admin + :members: + :undoc-members: + :show-inheritance: + +user\_management.apps module +---------------------------- + +.. automodule:: user_management.apps + :members: + :undoc-members: + :show-inheritance: + +user\_management.managers module +-------------------------------- + +.. automodule:: user_management.managers + :members: + :undoc-members: + :show-inheritance: + +user\_management.signals module +------------------------------- + +.. automodule:: user_management.signals + :members: + :undoc-members: + :show-inheritance: + +user\_management.urls module +---------------------------- + +.. automodule:: user_management.urls + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/user_management.serializers.rst b/docs/_rst/user_management.serializers.rst new file mode 100644 index 00000000..43a222bc --- /dev/null +++ b/docs/_rst/user_management.serializers.rst @@ -0,0 +1,45 @@ +user\_management.serializers package +==================================== + +Submodules +---------- + +user\_management.serializers.group module +----------------------------------------- + +.. automodule:: user_management.serializers.group + :members: + :undoc-members: + :show-inheritance: + +user\_management.serializers.permission module +---------------------------------------------- + +.. automodule:: user_management.serializers.permission + :members: + :undoc-members: + :show-inheritance: + +user\_management.serializers.profile module +------------------------------------------- + +.. automodule:: user_management.serializers.profile + :members: + :undoc-members: + :show-inheritance: + +user\_management.serializers.user module +---------------------------------------- + +.. automodule:: user_management.serializers.user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.serializers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/user_management.tests.rst b/docs/_rst/user_management.tests.rst new file mode 100644 index 00000000..2e4ce934 --- /dev/null +++ b/docs/_rst/user_management.tests.rst @@ -0,0 +1,37 @@ +user\_management.tests package +============================== + +Submodules +---------- + +user\_management.tests.common\_functions module +----------------------------------------------- + +.. automodule:: user_management.tests.common_functions + :members: + :undoc-members: + :show-inheritance: + +user\_management.tests.test\_group\_model module +------------------------------------------------ + +.. automodule:: user_management.tests.test_group_model + :members: + :undoc-members: + :show-inheritance: + +user\_management.tests.test\_user\_model module +----------------------------------------------- + +.. automodule:: user_management.tests.test_user_model + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_rst/user_management.views.rst b/docs/_rst/user_management.views.rst new file mode 100644 index 00000000..f60d4f19 --- /dev/null +++ b/docs/_rst/user_management.views.rst @@ -0,0 +1,37 @@ +user\_management.views package +============================== + +Submodules +---------- + +user\_management.views.group module +----------------------------------- + +.. automodule:: user_management.views.group + :members: + :undoc-members: + :show-inheritance: + +user\_management.views.permission module +---------------------------------------- + +.. automodule:: user_management.views.permission + :members: + :undoc-members: + :show-inheritance: + +user\_management.views.user module +---------------------------------- + +.. automodule:: user_management.views.user + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: user_management.views + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..4d90eff4 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import django + +sys.path.insert(0, os.path.abspath('../src')) + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") + +django.setup() + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'Ticketing system' +copyright = '2023, Mahdi Namaki' +author = 'Mahdi Namaki' +release = '0.0.1' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', + 'sphinx.ext.autodoc', + 'sphinxcontrib_django2' +] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**/*.migrations.rst'] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] + +# Include the database table names of Django models +django_show_db_tables = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..3cd1d8ff --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +.. Ticketing system documentation master file, created by + sphinx-quickstart on Wed Oct 25 21:55:00 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Ticketing system's documentation! +============================================ + +.. toctree:: + :maxdepth: 6 + :caption: Contents: + + _rst/modules + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..954237b9 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/postman_collection.json b/postman_collection.json new file mode 100644 index 00000000..df8c4f36 --- /dev/null +++ b/postman_collection.json @@ -0,0 +1,2372 @@ +{ + "info": { + "_postman_id": "1c12d306-e1e6-4c2e-a1c1-396a9e065dce", + "name": "Ticketing System", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "28955453", + "_collection_link": "https://grey-robot-946514.postman.co/workspace/New-Team-Workspace~6a989cae-37cc-459b-b1e1-164cd99b7ee4/collection/28955453-1c12d306-e1e6-4c2e-a1c1-396a9e065dce?action=share&source=collection_link&creator=28955453" + }, + "item": [ + { + "name": "JWT Authentication", + "item": [ + { + "name": "Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"access_token\", jsonData.access);", + "pm.globals.set(\"refresh_token\", jsonData.refresh);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"admin@ticketing.sample\",\n \"password\": \"admin@control*987\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/token/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "token", + "" + ] + }, + "description": "To log in and get the `access_token`" + }, + "response": [] + }, + { + "name": "Refresh", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"access_token\", jsonData.access);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"refresh\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY5ODM5MDk1NiwiaWF0IjoxNjk4MzA0NTU2LCJqdGkiOiI5MzY3MDQyMTA5ZTE0MmQ5OGYxZGFkZjk3NTEwNzE3ZCIsInVzZXJfaWQiOjF9.OuuJSPurX2vaotnyfn7sKgXA5RLwxG5hjg4vmm7RgE0\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/token/refresh/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "token", + "refresh", + "" + ] + }, + "description": "To log in and get the `access_token`" + }, + "response": [] + }, + { + "name": "Verify", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"access_token\", jsonData.access);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjk4MzA2MzczLCJpYXQiOjE2OTgzMDQ1NTYsImp0aSI6IjY5NzJmYWE4MWZkOTQ5M2Y5YTUzZjM5YTBhNTdlZmFiIiwidXNlcl9pZCI6MX0.dtTaZNptlR55WZWyg6xAIeJh1RjW0hURhAPQtGKACUc\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/token/verify/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "token", + "verify", + "" + ] + }, + "description": "To log in and get the `access_token`" + }, + "response": [] + } + ] + }, + { + "name": "User Management", + "item": [ + { + "name": "Permissions", + "item": [ + { + "name": "Permissions - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/permissions/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "permissions", + "" + ] + } + }, + "response": [] + }, + { + "name": "Permissions - Filter", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/permissions/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "permissions", + "" + ], + "query": [ + { + "key": "name", + "value": "Can add group", + "disabled": true + }, + { + "key": "codename", + "value": "add_group", + "disabled": true + }, + { + "key": "content_type", + "value": "2", + "disabled": true + }, + { + "key": "search", + "value": "group", + "description": "By 'name' field", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Permission - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/permissions/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "permissions", + "1", + "" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Groups", + "item": [ + { + "name": "Groups - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/groups/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "groups", + "" + ] + }, + "description": "To fetch the partner objects" + }, + "response": [] + }, + { + "name": "Groups - Filter", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{service_url}}/api/v1/groups/?search=test", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "groups", + "" + ], + "query": [ + { + "key": "name", + "value": "test", + "disabled": true + }, + { + "key": "search", + "value": "test", + "description": "By 'name' field" + } + ] + }, + "description": "To filter the partner objects by all model fields" + }, + "response": [] + }, + { + "name": "Group - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"test\",\n \"permission_ids\": [1, 2, 3]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/groups/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "groups", + "" + ] + }, + "description": "To create a new partner object" + }, + "response": [] + }, + { + "name": "Group - Update", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"permission_ids\": [1, 2, 3, 5, 6]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/groups/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "groups", + "1", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "Group - Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/groups/3/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "groups", + "3", + "" + ] + }, + "description": "To delete the partner object by id field" + }, + "response": [] + }, + { + "name": "Group - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/groups/2/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "groups", + "2", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Users - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/users/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "" + ] + }, + "description": "To fetch the partner objects" + }, + "response": [] + }, + { + "name": "Users - Filter", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{service_url}}/api/v1/users/?is_staff=true", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "" + ], + "query": [ + { + "key": "username", + "value": "admin@ticketing.sample", + "disabled": true + }, + { + "key": "is_active", + "value": "true", + "disabled": true + }, + { + "key": "is_superuser", + "value": "true", + "disabled": true + }, + { + "key": "is_staff", + "value": "true" + }, + { + "key": "search", + "value": "", + "description": "By 'username' field", + "disabled": true + } + ] + }, + "description": "To filter the partner objects by all model fields" + }, + "response": [] + }, + { + "name": "User - Create", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"@strong#password\",\n \"is_superuser\": false,\n \"is_staff\": false,\n \"username\": \"user@ticketing.sample\",\n \"is_active\": true,\n \"groups\": [],\n \"user_permissions\": []\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/users/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "" + ] + }, + "description": "To create a new partner object" + }, + "response": [] + }, + { + "name": "User - Update", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"is_active\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/users/2/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "2", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "User - Changing The Current User's Password", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "old_password", + "value": "admin@control*123", + "type": "text" + }, + { + "key": "new_password1", + "value": "admin@control*222", + "type": "text" + }, + { + "key": "new_password2", + "value": "admin@control*222", + "type": "text" + } + ] + }, + "url": { + "raw": "{{service_url}}/api/v1/users/2/change_password/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "2", + "change_password", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "User - Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/users/2/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "2", + "" + ] + }, + "description": "To delete the partner object by id field" + }, + "response": [] + }, + { + "name": "User - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/users/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "1", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + }, + { + "name": "User - Profile", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/users/1/user_profile/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "1", + "user_profile", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + }, + { + "name": "User - Changing The Current User's Profile", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "first_name", + "value": "", + "type": "text", + "disabled": true + }, + { + "key": "last_name", + "value": "", + "type": "text", + "disabled": true + }, + { + "key": "gender", + "value": "0", + "description": "0 => Male, 1 => Female", + "type": "text" + } + ] + }, + "url": { + "raw": "{{service_url}}/api/v1/users/1/update_user_profile/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "users", + "1", + "update_user_profile", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Booking", + "item": [ + { + "name": "Stadiums", + "item": [ + { + "name": "Stadiums - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/stadiums/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "stadiums", + "" + ] + }, + "description": "To fetch the partner objects" + }, + "response": [] + }, + { + "name": "Stadiums - Filter", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{service_url}}/api/v1/stadiums/?search=test", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "stadiums", + "" + ], + "query": [ + { + "key": "name", + "value": "test", + "disabled": true + }, + { + "key": "province", + "value": "test", + "disabled": true + }, + { + "key": "city", + "value": "test", + "disabled": true + }, + { + "key": "search", + "value": "test", + "description": "By 'name', 'province', 'city', 'address' fields\n" + } + ] + }, + "description": "To filter the partner objects by all model fields" + }, + "response": [] + }, + { + "name": "Stadium - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Azadi\",\n \"province\": \"Tehran\",\n \"city\": \"Tehran\",\n \"address\": \"Sample address\",\n \"map_url\": \"https://maps.app.goo.gl/2t5nqYLTf8TEzFBx5\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/stadiums/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "stadiums", + "" + ] + }, + "description": "To create a new partner object" + }, + "response": [] + }, + { + "name": "Stadium - Update", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n // \"name\": \"Azadi\",\n // \"province\": \"Tehran\",\n // \"city\": \"Tehran\",\n \"address\": \"District 22, Azadi Stadium Blvd\"\n // \"map_url\": \"https://maps.app.goo.gl/2t5nqYLTf8TEzFBx5\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/stadiums/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "stadiums", + "1", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "Stadium - Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/stadiums/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "stadiums", + "1", + "" + ] + }, + "description": "To delete the partner object by id field" + }, + "response": [] + }, + { + "name": "Stadium - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/stadiums/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "stadiums", + "1", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + } + ] + }, + { + "name": "Sections", + "item": [ + { + "name": "Sections - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/sections/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "sections", + "" + ] + }, + "description": "To fetch the partner objects" + }, + "response": [] + }, + { + "name": "Sections - Filter", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{service_url}}/api/v1/sections/?search=azadi", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "sections", + "" + ], + "query": [ + { + "key": "stadium", + "value": "test", + "disabled": true + }, + { + "key": "location", + "value": "test", + "disabled": true + }, + { + "key": "capacity", + "value": "test", + "disabled": true + }, + { + "key": "price", + "value": "test", + "disabled": true + }, + { + "key": "search", + "value": "azadi", + "description": "By 'stadium__name', 'location', 'capacity', 'price' fields" + } + ] + }, + "description": "To filter the partner objects by all model fields" + }, + "response": [] + }, + { + "name": "Section - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"stadium\": 1,\n // Location Enums (0 => A, 1 => B, 2 => C, 3 => VIP)\n \"location\": 0,\n \"capacity\": 50,\n \"price\": 5000\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/sections/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "sections", + "" + ] + }, + "description": "To create a new partner object" + }, + "response": [] + }, + { + "name": "Section - Update", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n // \"stadium\": 1,\n // Location Enums (0 => A, 1 => B, 2 => C, 3 => VIP)\n // \"location\": 0,\n \"capacity\": 70\n // \"price\": 5000\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/sections/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "sections", + "1", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "Section - Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/sections/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "sections", + "1", + "" + ] + }, + "description": "To delete the partner object by id field" + }, + "response": [] + }, + { + "name": "Section - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/sections/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "sections", + "1", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + } + ] + }, + { + "name": "Teams", + "item": [ + { + "name": "Teams - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/teams/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "teams", + "" + ] + }, + "description": "To fetch the partner objects" + }, + "response": [] + }, + { + "name": "Teams - Filter", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{service_url}}/api/v1/teams/?search=", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "teams", + "" + ], + "query": [ + { + "key": "name", + "value": "test", + "disabled": true + }, + { + "key": "search", + "value": "", + "description": "By 'name' field" + } + ] + }, + "description": "To filter the partner objects by all model fields" + }, + "response": [] + }, + { + "name": "Team - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Perspolis\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/teams/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "teams", + "" + ] + }, + "description": "To create a new partner object" + }, + "response": [] + }, + { + "name": "Team - Update", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Perspolis FC\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/teams/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "teams", + "1", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "Team - Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/teams/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "teams", + "1", + "" + ] + }, + "description": "To delete the partner object by id field" + }, + "response": [] + }, + { + "name": "Team - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/teams/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "teams", + "1", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + } + ] + }, + { + "name": "Matches", + "item": [ + { + "name": "Matches - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/matches/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "" + ] + }, + "description": "To fetch the partner objects" + }, + "response": [] + }, + { + "name": "Matches - Filter", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{service_url}}/api/v1/matches/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "" + ], + "query": [ + { + "key": "stadium", + "value": "", + "disabled": true + }, + { + "key": "host_team", + "value": "", + "disabled": true + }, + { + "key": "guest_team", + "value": "", + "disabled": true + }, + { + "key": "start_time", + "value": "", + "disabled": true + }, + { + "key": "search", + "value": "", + "description": "By 'stadium__name', 'host_team__name', 'guest_team__name', 'start_time' fields", + "disabled": true + } + ] + }, + "description": "To filter the partner objects by all model fields" + }, + "response": [] + }, + { + "name": "Match - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"stadium\": 1,\n \"host_team\": 1,\n \"guest_team\": 2,\n \"start_time\": \"2023-10-30T16:00:00\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/matches/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "" + ] + }, + "description": "To create a new partner object" + }, + "response": [] + }, + { + "name": "Match - Update", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"stadium\": 1,\n \"host_team\": 1,\n \"guest_team\": 2,\n \"start_time\": \"2023-10-30T16:00:00\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{service_url}}/api/v1/matches/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "1", + "" + ] + }, + "description": "To update the partner object by id field" + }, + "response": [] + }, + { + "name": "Match - Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/matches/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "1", + "" + ] + }, + "description": "To delete the partner object by id field" + }, + "response": [] + }, + { + "name": "Match - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/matches/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "1", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + }, + { + "name": "Match - Seats", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/matches/1/seats/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "matches", + "1", + "seats", + "" + ] + }, + "description": "To fetch a single partner object besd id field" + }, + "response": [] + } + ] + }, + { + "name": "Invoices", + "item": [ + { + "name": "Invoices - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/invoices/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "invoices", + "" + ] + } + }, + "response": [] + }, + { + "name": "Invoices - Filter", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/invoices/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "invoices", + "" + ], + "query": [ + { + "key": "user", + "value": "", + "disabled": true + }, + { + "key": "status", + "value": "", + "description": "0 => Paid, 1 => Unpaid", + "disabled": true + }, + { + "key": "search", + "value": "admin@ticketing.sample", + "description": "By 'user__username' field", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Invoice - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/invoices/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "invoices", + "1", + "" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Tickets", + "item": [ + { + "name": "Tickets - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/tickets/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "tickets", + "" + ] + } + }, + "response": [] + }, + { + "name": "Tickets - Filter", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/tickets/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "tickets", + "" + ], + "query": [ + { + "key": "invoice", + "value": "", + "disabled": true + }, + { + "key": "match", + "value": "", + "disabled": true + }, + { + "key": "section", + "value": "", + "disabled": true + }, + { + "key": "seat_number", + "value": "", + "disabled": true + }, + { + "key": "status", + "value": "", + "description": "0 => Sold, 1 => Unsold, 2 => Reserved", + "disabled": true + }, + { + "key": "search", + "value": "admin@ticketing.sample", + "description": "By 'invoice', 'invoice__user__username', 'match', 'section', 'seat_number' fields", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Ticket - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "match", + "value": "1", + "description": "ID of the 'Match' object", + "type": "text" + }, + { + "key": "section", + "value": "1", + "description": "ID of the 'Section' object", + "type": "text" + }, + { + "key": "seat_number", + "value": "1", + "type": "text" + } + ] + }, + "url": { + "raw": "{{service_url}}/api/v1/tickets/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "tickets", + "" + ] + } + }, + "response": [] + }, + { + "name": "Ticket - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/tickets/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "tickets", + "1", + "" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Payments", + "item": [ + { + "name": "Payments - List", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/payments/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "payments", + "" + ] + } + }, + "response": [] + }, + { + "name": "Payments - Filter", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/payments/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "payments", + "" + ], + "query": [ + { + "key": "invoice", + "value": "", + "disabled": true + }, + { + "key": "amount", + "value": "", + "disabled": true + }, + { + "key": "status", + "value": "", + "description": "0 => Successful, 1 => Unsuccessful", + "disabled": true + }, + { + "key": "created_at", + "value": "", + "disabled": true + }, + { + "key": "search", + "value": "admin@ticketing.sample", + "description": "By 'invoice', 'invoice__user__username', 'amount', 'created_at' fields", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Payment - Create", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "invoice", + "value": "1", + "description": "ID of the 'Invoice' object", + "type": "text" + }, + { + "key": "amount", + "value": "15000", + "type": "text" + } + ] + }, + "url": { + "raw": "{{service_url}}/api/v1/payments/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "payments", + "" + ] + } + }, + "response": [] + }, + { + "name": "Payment - Detail", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{service_url}}/api/v1/payments/1/", + "host": [ + "{{service_url}}" + ], + "path": [ + "api", + "v1", + "payments", + "1", + "" + ] + } + }, + "response": [] + } + ] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "pm.globals.set(\"service_url\", \"http://127.0.0.1:8000\");" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/requirements_base.txt b/requirements_base.txt new file mode 100644 index 00000000..b5e852d0 --- /dev/null +++ b/requirements_base.txt @@ -0,0 +1,6 @@ +django>=4.2.6,<4.3.0 +psycopg2-binary>=2.9.9,<2.10.0 +djangorestframework>=3.14.0,<3.15.0 +djangorestframework-simplejwt>=5.3.0,<5.4.0 +drf-yasg>=1.21.7,<1.22.0 +django-filter>=23.2,<24.0 \ No newline at end of file diff --git a/requirements_development.txt b/requirements_development.txt new file mode 100644 index 00000000..379d6942 --- /dev/null +++ b/requirements_development.txt @@ -0,0 +1,4 @@ +-r requirements_base.txt +Sphinx>=6.2.1,<6.3.0 +sphinx-rtd-theme>=1.2.2,<1.3.0 +sphinxcontrib-django2>=1.9,<2.0 \ No newline at end of file diff --git a/requirements_production.txt b/requirements_production.txt new file mode 100644 index 00000000..bbf7ba6d --- /dev/null +++ b/requirements_production.txt @@ -0,0 +1,2 @@ +-r requirements_base.txt +gunicorn>=21.2.0,<21.3.0 \ No newline at end of file diff --git a/src/api/__init__.py b/src/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/api/apps.py b/src/api/apps.py new file mode 100644 index 00000000..66656fd2 --- /dev/null +++ b/src/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/src/api/tests/__init__.py b/src/api/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/api/tests/test_group.py b/src/api/tests/test_group.py new file mode 100644 index 00000000..21247a41 --- /dev/null +++ b/src/api/tests/test_group.py @@ -0,0 +1,137 @@ +from django.contrib.auth.models import Group, Permission +from django.test import TestCase +from django.urls import reverse + +from rest_framework import status +from rest_framework.test import APIClient + +from user_management.tests.common_functions import sample_superuser, sample_group + +from user_management.serializers import GroupSerializer, PermissionSerializer + +GROUPS_LIST_URL = reverse('api:user_management:groups-list') + + +class PublicGroupsAPITests(TestCase): + """Test the public available groups API""" + def setUp(self) -> None: + self.client = APIClient() + + def test_login_required(self): + """Test that login is required for retrieving permissions""" + result = self.client.get(GROUPS_LIST_URL) + self.assertEqual(result.status_code, status.HTTP_401_UNAUTHORIZED) + + +class PrivateGroupsAPITests(TestCase): + """Test the authorized user groups API""" + def setUp(self) -> None: + self.super_user = sample_superuser() + self.client = APIClient() + self.client.force_authenticate(self.super_user) + + def test_create_group_successful(self): + """Test creating a new group is successful""" + payload = { + 'name': 'Sample Group', + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['name'], payload['name']) + + def test_create_group_with_permissions_successful(self): + """Test creating a new group with permissions is successful""" + permissions = Permission.objects.all() + payload = { + 'name': 'Sample Group', + 'permission_ids': sorted([permission.pk for permission in permissions]) + } + result = self.client.post(GROUPS_LIST_URL, payload) + permission_serializer = PermissionSerializer(permissions, many=True) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['name'], payload['name']) + self.assertEqual(result.data['permissions'], permission_serializer.data) + + def test_create_group_with_invalid_name_unsuccessful(self): + """Test creating a new group with invalid name is unsuccessful""" + payload = { + 'name': '', + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_group_with_duplicate_name_unsuccessful(self): + """Test creating a new group with duplicate name is unsuccessful""" + instance = sample_group() + payload = { + 'name': instance.name, + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_group_with_invalid_permission_unsuccessful(self): + """Test creating a new group with invalid permission is unsuccessful""" + payload = { + 'name': 'Sample Group', + 'permission_ids': [123456] + } + result = self.client.post(GROUPS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_retrieve_groups_successful(self): + """Test retrieving groups is successful""" + sample_group(name='Sample Group One') + sample_group(name='Sample Group Two') + result = self.client.get(GROUPS_LIST_URL) + groups = Group.objects.all() + serializer = GroupSerializer(groups, many=True) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['results'], serializer.data) + + def test_retrieve_single_group(self): + """Test retrieving a single group is successful""" + instance = sample_group() + result = self.client.get(reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['name'], instance.name) + + def test_update_group_successful(self): + """Test updating the group is successful""" + instance = sample_group() + payload = { + 'name': 'Update Group' + } + result = self.client.patch( + reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['name'], payload['name']) + self.assertNotEqual(result.data['name'], instance.name) + + def test_update_group_unsuccessful(self): + """Test updating the group is unsuccessful""" + instance = sample_group() + payload = { + 'name': '', + } + result = self.client.patch( + reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete_group_successful(self): + """Test deleting the group is successful""" + instance = sample_group() + result = self.client.delete( + reverse('api:user_management:groups-detail', kwargs={'pk': instance.pk}), + ) + self.assertEqual(result.status_code, status.HTTP_204_NO_CONTENT) + + def test_delete_group_unsuccessful(self): + """Test deleting the group is unsuccessful""" + result = self.client.delete( + reverse('api:user_management:groups-detail', kwargs={'pk': 123}), + ) + self.assertEqual(result.status_code, status.HTTP_404_NOT_FOUND) diff --git a/src/api/tests/test_permission.py b/src/api/tests/test_permission.py new file mode 100644 index 00000000..ef73188c --- /dev/null +++ b/src/api/tests/test_permission.py @@ -0,0 +1,74 @@ +from django.contrib.auth.models import Permission +from django.test import TestCase +from django.urls import reverse + +from rest_framework import status +from rest_framework.test import APIClient + +from user_management.tests.common_functions import sample_superuser + +from user_management.serializers import PermissionSerializer + +PERMISSIONS_LIST_URL = reverse('api:user_management:permissions-list') + + +class PublicPermissionsAPITests(TestCase): + """Test the public available permissions API""" + def setUp(self) -> None: + self.client = APIClient() + + def test_login_required(self): + """Test that login is required for retrieving permissions""" + result = self.client.get(PERMISSIONS_LIST_URL) + self.assertEqual(result.status_code, status.HTTP_401_UNAUTHORIZED) + + +class PrivatePermissionsAPITests(TestCase): + """Test the authorized user permissions API""" + def setUp(self) -> None: + self.super_user = sample_superuser() + self.client = APIClient() + self.client.force_authenticate(self.super_user) + + def test_retrieve_permissions(self): + """Test retrieving the permissions""" + result = self.client.get(PERMISSIONS_LIST_URL) + permissions = Permission.objects.all().order_by('id')[:20] + serializer = PermissionSerializer(permissions, many=True) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['results'], serializer.data) + + def test_retrieve_single_permission(self): + """Test retrieving a single permission""" + instance = Permission.objects.get(pk=1) + result = self.client.get(reverse('api:user_management:permissions-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['name'], instance.name) + self.assertEqual(result.data['codename'], instance.codename) + + def test_create_permission_not_allowed(self): + """Test creating a new permission is not allowed""" + payload = { + 'name': 'Sample Permission', + 'content_type_id': 1, + 'codename': 'sample_permission', + } + result = self.client.post(PERMISSIONS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) + + def test_update_permission_not_allowed(self): + """Test updating the permission is not allowed""" + instance = Permission.objects.get(pk=1) + result = self.client.patch( + reverse('api:user_management:permissions-detail', kwargs={'pk': instance.pk}), + data={ + 'name': 'Other Name', + } + ) + self.assertEqual(result.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) + + def test_delete_permission_not_allowed(self): + """Test deleting the permission is not allowed""" + instance = Permission.objects.get(pk=1) + result = self.client.delete(reverse('api:user_management:permissions-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) diff --git a/src/api/tests/test_user.py b/src/api/tests/test_user.py new file mode 100644 index 00000000..888d352e --- /dev/null +++ b/src/api/tests/test_user.py @@ -0,0 +1,269 @@ +from django.contrib.auth.models import Permission +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse + +from rest_framework import status +from rest_framework.test import APIClient + +from user_management.tests.common_functions import sample_superuser, sample_group, sample_user + +from user_management.serializers import UserSerializer, PermissionSerializer, GroupSerializer + +USERS_LIST_URL = reverse('api:user_management:users-list') + + +class PublicUsersAPITests(TestCase): + """Test the public available users API""" + def setUp(self) -> None: + self.client = APIClient() + + def test_login_required(self): + """Test that login is required for retrieving permissions""" + result = self.client.get(USERS_LIST_URL) + self.assertEqual(result.status_code, status.HTTP_401_UNAUTHORIZED) + + +class PrivateUsersAPITests(TestCase): + """Test the authorized user users API""" + def setUp(self) -> None: + self.super_user = sample_superuser() + self.client = APIClient() + self.client.force_authenticate(self.super_user) + + def test_create_user_successful(self): + """Test creating a new user is successful""" + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertFalse(result.data['is_superuser']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login']) + + def test_create_user_with_group_successful(self): + """Test creating a new user with group is successful""" + group = sample_group() + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [group.id], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertEqual(result.data['groups'], GroupSerializer(many=True, instance=[group]).data) + self.assertFalse(result.data['is_superuser']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login']) + + def test_create_user_with_permission_successful(self): + """Test creating a new user with permission is successful""" + permission = Permission.objects.get(pk=1) + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [permission.id] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertEqual(result.data['user_permissions'], PermissionSerializer(many=True, instance=[permission]).data) + self.assertFalse(result.data['is_superuser']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login']) + + def test_create_user_with_group_with_permission_to_do_something_successful(self): + """Test creating a new user with group with the permission to do something is successful""" + group = sample_group() + permission = Permission.objects.get(codename='add_group') + group.permissions.set([permission.id]) + + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [group.id], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertEqual(result.data['groups'], GroupSerializer(many=True, instance=[group]).data) + + user = get_user_model().objects.get(pk=result.data['id']) + self.assertTrue(user.has_perm("auth.add_group")) + + self.client.logout() + self.client.force_authenticate(user=user) + create_group_payload = { + 'name': 'Sample Group Two', + } + create_group_result = self.client.post(reverse('api:user_management:groups-list'), create_group_payload) + self.assertEqual(create_group_result.status_code, status.HTTP_201_CREATED) + self.assertEqual(create_group_result.data['name'], create_group_payload['name']) + + def test_create_user_with_group_without_permission_to_do_something_unsuccessful(self): + """Test creating a new user with group without the permission to do something is unsuccessful""" + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + + user = get_user_model().objects.get(pk=result.data['id']) + self.assertFalse(user.has_perm("auth.add_group")) + + self.client.logout() + self.client.force_authenticate(user=user) + create_group_payload = { + 'name': 'Sample Group Two', + } + create_group_result = self.client.post(reverse('api:user_management:groups-list'), create_group_payload) + self.assertEqual(create_group_result.status_code, status.HTTP_403_FORBIDDEN) + + def test_create_user_with_invalid_username_unsuccessful(self): + """Test creating a new user with invalid username is unsuccessful""" + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": "Sample User", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_user_with_invalid_password_unsuccessful(self): + """Test creating a new user with invalid password is unsuccessful""" + payload = { + "password": "123", + "is_superuser": False, + "is_staff": False, + "username": "sample_user@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_user_with_duplicate_username_unsuccessful(self): + """Test creating a new user with duplicate username is unsuccessful""" + instance = sample_user() + payload = { + "password": "@strong#password", + "is_superuser": False, + "is_staff": False, + "username": instance.username, + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_superuser_successful(self): + """Test creating a new superuser is successful""" + payload = { + "password": "@strong#password", + "is_superuser": True, + "is_staff": True, + "username": "sample_superuser@ticketing.sample", + "is_active": True, + "groups": [], + "user_permissions": [] + } + result = self.client.post(USERS_LIST_URL, payload) + self.assertEqual(result.status_code, status.HTTP_201_CREATED) + self.assertEqual(result.data['username'], payload['username']) + self.assertTrue(result.data['is_superuser']) + self.assertTrue(result.data['is_staff']) + self.assertTrue(result.data['is_active']) + self.assertIsNone(result.data['last_login']) + + def test_retrieve_users_successful(self): + """Test retrieving users is successful""" + sample_user() + sample_user(username='user2@ticketing.sample') + result = self.client.get(USERS_LIST_URL) + users = get_user_model().objects.all() + serializer = UserSerializer(users, many=True) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['results'], serializer.data) + + def test_retrieve_single_user_successful(self): + """Test retrieving a single user is successful""" + instance = sample_user() + result = self.client.get(reverse('api:user_management:users-detail', kwargs={'pk': instance.pk})) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['username'], instance.username) + + def test_update_user_successful(self): + """Test updating the user is successful""" + instance = sample_user() + payload = { + 'is_active': False + } + result = self.client.patch( + reverse('api:user_management:users-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_200_OK) + self.assertEqual(result.data['is_active'], payload['is_active']) + self.assertNotEqual(result.data['is_active'], instance.is_active) + self.assertFalse(result.data['is_active']) + + def test_update_user_unsuccessful(self): + """Test updating the user is unsuccessful""" + instance = sample_user() + payload = { + 'username': '', + } + result = self.client.patch( + reverse('api:user_management:users-detail', kwargs={'pk': instance.pk}), + data=payload + ) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete_user_successful(self): + """Test deleting the user is successful""" + instance = sample_user() + result = self.client.delete( + reverse('api:user_management:users-detail', kwargs={'pk': instance.pk}), + ) + self.assertEqual(result.status_code, status.HTTP_204_NO_CONTENT) + + def test_delete_user_unsuccessful(self): + """Test deleting the user is unsuccessful""" + result = self.client.delete( + reverse('api:user_management:users-detail', kwargs={'pk': 10000}), + ) + self.assertEqual(result.status_code, status.HTTP_404_NOT_FOUND) diff --git a/src/api/urls.py b/src/api/urls.py new file mode 100644 index 00000000..0d8d6d89 --- /dev/null +++ b/src/api/urls.py @@ -0,0 +1,34 @@ +from django.urls import path, include + +from rest_framework import permissions + +from drf_yasg.views import get_schema_view +from drf_yasg import openapi + +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView +from rest_framework_simplejwt.authentication import JWTAuthentication + +from extensions.custom_permissions import CustomDjangoModelPermission + +schema_view = get_schema_view( + openapi.Info( + title="Ticketing System API", + default_version='v1', + terms_of_service="https://www.google.com/policies/terms/", + contact=openapi.Contact(email="mavenium@gmail.com"), + license=openapi.License(name="MIT License"), + ), + public=True, + permission_classes=(permissions.DjangoModelPermissions, CustomDjangoModelPermission), + authentication_classes=(JWTAuthentication,) +) + +urlpatterns = [ + path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('token/verify/', TokenVerifyView.as_view(), name='token_verify'), + path('', include(('user_management.urls', 'user_management'), namespace='user_management')), + path('', include(('booking.urls', 'booking'), namespace='booking')), + path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema_swagger_ui'), + path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema_redoc'), +] diff --git a/src/booking/__init__.py b/src/booking/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/booking/admin.py b/src/booking/admin.py new file mode 100644 index 00000000..ea2a08ca --- /dev/null +++ b/src/booking/admin.py @@ -0,0 +1,216 @@ +from django.contrib import admin + +from booking.models import Stadium, Section, Team, Match, Invoice, Ticket, Payment + + +class SectionInlineAdmin(admin.TabularInline): + model = Section + min_num = 1 + extra = 0 + + +class StadiumAdmin(admin.ModelAdmin): + list_display = [ + 'name', + 'province', + 'city', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'province', + 'city', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'name', + 'province', + 'city', + ] + + actions = [ + 'delete_selected', + ] + + inlines = ( + SectionInlineAdmin, + ) + + def get_queryset(self, request): + return self.model.objects.prefetch_related('section_stadiums') + + +class TeamAdmin(admin.ModelAdmin): + list_display = [ + 'name', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'name', + ] + + actions = [ + 'delete_selected', + ] + + +class MatchAdmin(admin.ModelAdmin): + list_display = [ + 'stadium', + 'host_team', + 'guest_team', + 'start_time', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'stadium', + 'host_team', + 'guest_team', + 'start_time', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'stadium', + 'host_team', + 'guest_team', + ] + + actions = [ + 'delete_selected', + ] + + def get_queryset(self, request): + return self.model.objects.select_related('stadium', 'host_team', 'guest_team') + + +class InvoiceAdmin(admin.ModelAdmin): + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + list_display = [ + 'user', + 'status', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'status', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'user', + ] + + actions = [ + 'delete_selected', + ] + + def get_queryset(self, request): + return self.model.objects.select_related('user') + + +class TicketAdmin(admin.ModelAdmin): + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + list_display = [ + 'match', + 'section', + 'seat_number', + 'status', + 'created_at', + 'updated_at', + ] + + list_filter = [ + 'match', + 'section', + 'status', + 'created_at', + 'updated_at', + ] + + search_fields = [ + 'match', + 'section', + 'seat_number', + ] + + actions = [ + 'delete_selected', + ] + + def get_queryset(self, request): + return self.model.objects.select_related('invoice', 'match', 'section') + + +class PaymentAdmin(admin.ModelAdmin): + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + list_display = [ + 'invoice', + 'amount', + 'status', + 'created_at', + ] + + list_filter = [ + 'status', + 'created_at', + ] + + search_fields = [ + 'invoice', + 'amount', + ] + + actions = [ + 'delete_selected', + ] + + def get_queryset(self, request): + return self.model.objects.select_related('invoice') + + +admin.site.register(Stadium, StadiumAdmin) +admin.site.register(Team, TeamAdmin) +admin.site.register(Match, MatchAdmin) +admin.site.register(Invoice, InvoiceAdmin) +admin.site.register(Ticket, TicketAdmin) +admin.site.register(Payment, PaymentAdmin) diff --git a/src/booking/apps.py b/src/booking/apps.py new file mode 100644 index 00000000..ba85d1df --- /dev/null +++ b/src/booking/apps.py @@ -0,0 +1,8 @@ +from django.utils.translation import gettext_lazy as _ +from django.apps import AppConfig + + +class BookingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'booking' + verbose_name = _('Booking') diff --git a/src/booking/migrations/0001_stadium.py b/src/booking/migrations/0001_stadium.py new file mode 100644 index 00000000..12e21ad2 --- /dev/null +++ b/src/booking/migrations/0001_stadium.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.6 on 2023-10-26 20:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Stadium', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('name', models.CharField(db_index=True, max_length=200, verbose_name='Name')), + ('province', models.CharField(db_index=True, max_length=200, verbose_name='Province')), + ('city', models.CharField(db_index=True, max_length=200, verbose_name='City')), + ('address', models.TextField(blank=True, verbose_name='Address')), + ('map_url', models.URLField(blank=True, verbose_name='Map URL')), + ], + options={ + 'verbose_name': 'Stadium', + 'verbose_name_plural': 'Stadiums', + 'db_table': 'booking_stadiums', + 'ordering': ['id'], + 'unique_together': {('name', 'province', 'city')}, + }, + ), + ] diff --git a/src/booking/migrations/0002_section.py b/src/booking/migrations/0002_section.py new file mode 100644 index 00000000..132add57 --- /dev/null +++ b/src/booking/migrations/0002_section.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.6 on 2023-10-26 21:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0001_stadium'), + ] + + operations = [ + migrations.CreateModel( + name='Section', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('location', models.PositiveSmallIntegerField(choices=[(0, 'A'), (1, 'B'), (2, 'C'), (3, 'VIP')], verbose_name='Location')), + ('capacity', models.PositiveSmallIntegerField(verbose_name='Capacity')), + ('stadium', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='section_stadiums', to='booking.stadium', verbose_name='Stadium')), + ('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')), + ], + options={ + 'verbose_name': 'Section', + 'verbose_name_plural': 'Sections', + 'db_table': 'booking_sections', + 'ordering': ['id'], + 'unique_together': {('stadium', 'location')}, + }, + ), + ] diff --git a/src/booking/migrations/0003_team.py b/src/booking/migrations/0003_team.py new file mode 100644 index 00000000..6c829723 --- /dev/null +++ b/src/booking/migrations/0003_team.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.6 on 2023-10-26 21:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0002_section'), + ] + + operations = [ + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('name', models.CharField(db_index=True, max_length=200, unique=True, verbose_name='Name')), + ], + options={ + 'verbose_name': 'Team', + 'verbose_name_plural': 'Teams', + 'db_table': 'booking_teams', + 'ordering': ['id'], + }, + ), + ] diff --git a/src/booking/migrations/0004_match.py b/src/booking/migrations/0004_match.py new file mode 100644 index 00000000..8f0f628c --- /dev/null +++ b/src/booking/migrations/0004_match.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.6 on 2023-10-26 21:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0003_team'), + ] + + operations = [ + migrations.CreateModel( + name='Match', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('start_time', models.DateTimeField(verbose_name='Start Time')), + ('guest_team', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='match_guest_teams', to='booking.team', verbose_name='Guest Team')), + ('host_team', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='match_host_teams', to='booking.team', verbose_name='Host Team')), + ('stadium', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='match_stadiums', to='booking.stadium', verbose_name='Stadium')), + ], + options={ + 'verbose_name': 'Match', + 'verbose_name_plural': 'Matches', + 'db_table': 'booking_matches', + 'ordering': ['id'], + 'unique_together': {('stadium', 'host_team', 'guest_team', 'start_time')}, + }, + ), + ] diff --git a/src/booking/migrations/0005_invoice.py b/src/booking/migrations/0005_invoice.py new file mode 100644 index 00000000..35c10610 --- /dev/null +++ b/src/booking/migrations/0005_invoice.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.6 on 2023-10-26 22:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('booking', '0004_match'), + ] + + operations = [ + migrations.CreateModel( + name='Invoice', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('status', models.PositiveSmallIntegerField(choices=[(0, 'Paid'), (1, 'Unpaid')], default=1, verbose_name='Status')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invoice_users', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Invoice', + 'verbose_name_plural': 'Invoices', + 'db_table': 'booking_invoices', + 'ordering': ['id'], + }, + ), + ] diff --git a/src/booking/migrations/0006_ticket.py b/src/booking/migrations/0006_ticket.py new file mode 100644 index 00000000..370efaf7 --- /dev/null +++ b/src/booking/migrations/0006_ticket.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.6 on 2023-10-26 22:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0005_invoice'), + ] + + operations = [ + migrations.CreateModel( + name='Ticket', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('seat_number', models.PositiveSmallIntegerField(verbose_name='Seat Number')), + ('status', models.PositiveSmallIntegerField(choices=[(0, 'Sold'), (1, 'Reserved')], default=1, verbose_name='Status')), + ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ticket_invoices', to='booking.invoice', verbose_name='Invoice')), + ('match', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ticket_matches', to='booking.match', verbose_name='Match')), + ('section', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ticket_sections', to='booking.section', verbose_name='Section')), + ], + options={ + 'verbose_name': 'Ticket', + 'verbose_name_plural': 'Tickets', + 'db_table': 'booking_tickets', + 'ordering': ['id'], + }, + ), + ] diff --git a/src/booking/migrations/0007_payment.py b/src/booking/migrations/0007_payment.py new file mode 100644 index 00000000..6c3b3881 --- /dev/null +++ b/src/booking/migrations/0007_payment.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.6 on 2023-10-27 08:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0006_ticket'), + ] + + operations = [ + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Amount')), + ('status', models.PositiveSmallIntegerField(choices=[(0, 'Successful'), (1, 'Unsuccessful')], verbose_name='Status')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payment_invoices', to='booking.invoice', verbose_name='Invoice')), + ], + options={ + 'verbose_name': 'Payment', + 'verbose_name_plural': 'Payments', + 'db_table': 'booking_payments', + 'ordering': ['id'], + }, + ), + ] diff --git a/src/booking/migrations/__init__.py b/src/booking/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/booking/models/__init__.py b/src/booking/models/__init__.py new file mode 100644 index 00000000..17b3c82c --- /dev/null +++ b/src/booking/models/__init__.py @@ -0,0 +1,17 @@ +from .stadium import Stadium +from .section import Section +from .team import Team +from .match import Match +from .invoice import Invoice +from .ticket import Ticket +from .payment import Payment + +__all__ = [ + 'Stadium', + 'Section', + 'Team', + 'Match', + 'Invoice', + 'Ticket', + 'Payment', +] diff --git a/src/booking/models/invoice.py b/src/booking/models/invoice.py new file mode 100644 index 00000000..897cf698 --- /dev/null +++ b/src/booking/models/invoice.py @@ -0,0 +1,46 @@ +from django.utils.translation import gettext_lazy as _ +from django.contrib.auth import get_user_model +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt +from extensions.choices import InvoiceStatusChoices, TicketStatusChoices + +from decimal import Decimal + + +class Invoice(AbstractCreatAtUpdateAt, models.Model): + user = models.ForeignKey( + get_user_model(), + verbose_name=_('User'), + on_delete=models.CASCADE, + related_name='invoice_users' + ) + status = models.PositiveSmallIntegerField( + verbose_name=_('Status'), + choices=InvoiceStatusChoices.choices, + default=InvoiceStatusChoices.UNPAID + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_invoices' + verbose_name = _('Invoice') + verbose_name_plural = _('Invoices') + ordering = ['id'] + + def __str__(self) -> str: + return f"{self.user} - {self.id}" + + def set_as_paid(self) -> None: + """It will change the status field and save the object and tickets""" + self.status = InvoiceStatusChoices.PAID + self.save() + + # We have to do this in order to update the 'updated_at' field instead of using the 'update' method + for ticket in self.ticket_invoices.all(): + ticket.set_as_sold() + + @property + def get_total_amount(self) -> Decimal: + """It returns total price based on price of the section price""" + return sum(list(self.ticket_invoices.values_list('section__price', flat=True))) diff --git a/src/booking/models/match.py b/src/booking/models/match.py new file mode 100644 index 00000000..05b77cf8 --- /dev/null +++ b/src/booking/models/match.py @@ -0,0 +1,39 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt + + +class Match(AbstractCreatAtUpdateAt, models.Model): + stadium = models.ForeignKey( + 'Stadium', + verbose_name=_('Stadium'), + on_delete=models.PROTECT, + related_name='match_stadiums' + ) + host_team = models.ForeignKey( + 'Team', + verbose_name=_('Host Team'), + on_delete=models.PROTECT, + related_name='match_host_teams' + ) + guest_team = models.ForeignKey( + 'Team', + verbose_name=_('Guest Team'), + on_delete=models.PROTECT, + related_name='match_guest_teams' + ) + start_time = models.DateTimeField( + verbose_name=_('Start Time') + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_matches' + verbose_name = _('Match') + verbose_name_plural = _('Matches') + ordering = ['id'] + unique_together = ['stadium', 'host_team', 'guest_team', 'start_time'] + + def __str__(self) -> str: + return f"{self.stadium} - {self.host_team} & {self.guest_team} - {self.start_time}" diff --git a/src/booking/models/payment.py b/src/booking/models/payment.py new file mode 100644 index 00000000..ef775453 --- /dev/null +++ b/src/booking/models/payment.py @@ -0,0 +1,38 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt +from extensions.choices import PaymentStatusChoices + + +class Payment(models.Model): + invoice = models.ForeignKey( + 'Invoice', + verbose_name=_('Invoice'), + on_delete=models.CASCADE, + related_name='payment_invoices' + ) + amount = models.DecimalField( + verbose_name=_('Amount'), + decimal_places=2, + max_digits=14 + ) + status = models.PositiveSmallIntegerField( + verbose_name=_('Status'), + choices=PaymentStatusChoices.choices + ) + created_at = models.DateTimeField( + verbose_name=_('Created At'), + auto_now_add=True, + editable=False + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_payments' + verbose_name = _('Payment') + verbose_name_plural = _('Payments') + ordering = ['id'] + + def __str__(self) -> str: + return f"Payment - {self.id}" diff --git a/src/booking/models/section.py b/src/booking/models/section.py new file mode 100644 index 00000000..40766334 --- /dev/null +++ b/src/booking/models/section.py @@ -0,0 +1,42 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt +from extensions.choices import LocationChoices + + +class Section(AbstractCreatAtUpdateAt, models.Model): + stadium = models.ForeignKey( + 'Stadium', + verbose_name=_('Stadium'), + on_delete=models.CASCADE, + related_name='section_stadiums' + ) + location = models.PositiveSmallIntegerField( + verbose_name=_('Location'), + choices=LocationChoices.choices, + ) + capacity = models.PositiveSmallIntegerField( + verbose_name=_('Capacity') + ) + price = models.DecimalField( + verbose_name=_('Price'), + decimal_places=2, + max_digits=14 + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_sections' + verbose_name = _('Section') + verbose_name_plural = _('Sections') + ordering = ['id'] + unique_together = ['stadium', 'location'] + + def __str__(self) -> str: + return f"{self.stadium} - {self.get_location_display()}" + + @property + def get_list_of_seat_numbers(self) -> list: + """It returns a list of seat numbers""" + return [*range(1, self.capacity + 1)] diff --git a/src/booking/models/stadium.py b/src/booking/models/stadium.py new file mode 100644 index 00000000..3179651c --- /dev/null +++ b/src/booking/models/stadium.py @@ -0,0 +1,46 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt + + +class Stadium(AbstractCreatAtUpdateAt, models.Model): + name = models.CharField( + verbose_name=_('Name'), + max_length=200, + db_index=True + ) + province = models.CharField( + verbose_name=_('Province'), + max_length=200, + db_index=True + ) + city = models.CharField( + verbose_name=_('City'), + max_length=200, + db_index=True + ) + address = models.TextField( + verbose_name=_('Address'), + blank=True + ) + map_url = models.URLField( + verbose_name=_('Map URL'), + blank=True + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_stadiums' + verbose_name = _('Stadium') + verbose_name_plural = _('Stadiums') + ordering = ['id'] + unique_together = ['name', 'province', 'city'] + + def __str__(self) -> str: + return f"{self.province} - {self.city} - {self.name}" + + @property + def get_section_ids(self) -> list: + """It returns a list of section ids""" + return list(self.section_stadiums.values_list('id', flat=True)) diff --git a/src/booking/models/team.py b/src/booking/models/team.py new file mode 100644 index 00000000..8bc409ae --- /dev/null +++ b/src/booking/models/team.py @@ -0,0 +1,23 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt + + +class Team(AbstractCreatAtUpdateAt, models.Model): + name = models.CharField( + verbose_name=_('Name'), + max_length=200, + unique=True, + db_index=True + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_teams' + verbose_name = _('Team') + verbose_name_plural = _('Teams') + ordering = ['id'] + + def __str__(self) -> str: + return self.name.__str__() diff --git a/src/booking/models/ticket.py b/src/booking/models/ticket.py new file mode 100644 index 00000000..1a38545e --- /dev/null +++ b/src/booking/models/ticket.py @@ -0,0 +1,49 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt +from extensions.choices import TicketStatusChoices + + +class Ticket(AbstractCreatAtUpdateAt, models.Model): + invoice = models.ForeignKey( + 'Invoice', + verbose_name=_('Invoice'), + on_delete=models.PROTECT, + related_name='ticket_invoices' + ) + match = models.ForeignKey( + 'Match', + verbose_name=_('Match'), + on_delete=models.PROTECT, + related_name='ticket_matches' + ) + section = models.ForeignKey( + 'Section', + verbose_name=_('Section'), + on_delete=models.PROTECT, + related_name='ticket_sections' + ) + seat_number = models.PositiveSmallIntegerField( + verbose_name=_('Seat Number') + ) + status = models.PositiveSmallIntegerField( + verbose_name=_('Status'), + choices=TicketStatusChoices.choices, + default=TicketStatusChoices.RESERVED + ) + + class Meta: + app_label = 'booking' + db_table = 'booking_tickets' + verbose_name = _('Ticket') + verbose_name_plural = _('Tickets') + ordering = ['id'] + + def __str__(self) -> str: + return f"Ticket {self.id}" + + def set_as_sold(self) -> None: + """It will change the status field and save the object""" + self.status = TicketStatusChoices.SOLD + self.save() diff --git a/src/booking/serializers/__init__.py b/src/booking/serializers/__init__.py new file mode 100644 index 00000000..b4caa69c --- /dev/null +++ b/src/booking/serializers/__init__.py @@ -0,0 +1,17 @@ +from .stadium import StadiumSerializer +from .section import SectionSerializer +from .team import TeamSerializer +from .match import MatchSerializer +from .invoice import InvoiceSerializer +from .ticket import TicketSerializer +from .payment import PaymentSerializer + +__all__ = [ + 'StadiumSerializer', + 'SectionSerializer', + 'TeamSerializer', + 'MatchSerializer', + 'InvoiceSerializer', + 'TicketSerializer', + 'PaymentSerializer', +] diff --git a/src/booking/serializers/invoice.py b/src/booking/serializers/invoice.py new file mode 100644 index 00000000..98aa71d2 --- /dev/null +++ b/src/booking/serializers/invoice.py @@ -0,0 +1,20 @@ +from rest_framework.serializers import ModelSerializer + +from booking.models import Invoice + + +class InvoiceSerializer(ModelSerializer): + """ + Serializer for the 'Invoice' model + """ + + class Meta: + model = Invoice + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') + + def to_representation(self, obj): + data = super(InvoiceSerializer, self).to_representation(obj) + data['status'] = obj.get_status_display() + data['total_amount'] = obj.get_total_amount + return data diff --git a/src/booking/serializers/match.py b/src/booking/serializers/match.py new file mode 100644 index 00000000..100cc873 --- /dev/null +++ b/src/booking/serializers/match.py @@ -0,0 +1,14 @@ +from rest_framework.serializers import ModelSerializer + +from booking.models import Match + + +class MatchSerializer(ModelSerializer): + """ + Serializer for the 'Match' model + """ + + class Meta: + model = Match + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') diff --git a/src/booking/serializers/payment.py b/src/booking/serializers/payment.py new file mode 100644 index 00000000..b5ab0cf5 --- /dev/null +++ b/src/booking/serializers/payment.py @@ -0,0 +1,78 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import transaction + +from rest_framework.serializers import ModelSerializer +from rest_framework.exceptions import ValidationError +from rest_framework.response import Response +from rest_framework import status + +from booking.models import Payment + +from extensions.choices import InvoiceStatusChoices, PaymentStatusChoices + +from decimal import Decimal + + +class PaymentSerializer(ModelSerializer): + """ + Serializer for the 'Payment' model + """ + + class Meta: + model = Payment + fields = '__all__' + read_only_fields = ('status', 'created_at') + + def validate(self, data): + """ + It will use for making the validation on data + :param data: A dict of fields + :return: A valid data or errors + """ + + invoice = data.get('invoice') + amount = data.get('amount') + + if invoice.user != self.context['request'].user: + raise ValidationError({ + "invoice": [_("This invoice does not belong to you!")] + }) + + if invoice.status == InvoiceStatusChoices.PAID: + raise ValidationError({ + "invoice": [_("This invoice has already been paid!")] + }) + + if invoice.get_total_amount != Decimal(amount): + raise ValidationError({ + "amount": [_("The entered amount is not equal to the invoice amount!")] + }) + + return data + + def create(self, validated_data): + """ + Create a new instance + :param validated_data: A dict of fields + :return: A new instance + """ + with transaction.atomic(): + try: + invoice = validated_data.get('invoice') + + payment_instance = self.Meta.model.objects.create( + invoice=invoice, + amount=invoice.get_total_amount, + status=PaymentStatusChoices.SUCCESSFUL + ) + + invoice.set_as_paid() + + return payment_instance + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + + def to_representation(self, obj): + data = super(PaymentSerializer, self).to_representation(obj) + data['status'] = obj.get_status_display() + return data diff --git a/src/booking/serializers/section.py b/src/booking/serializers/section.py new file mode 100644 index 00000000..3b6228f9 --- /dev/null +++ b/src/booking/serializers/section.py @@ -0,0 +1,20 @@ +from rest_framework.serializers import ModelSerializer + +from booking.models import Section + + +class SectionSerializer(ModelSerializer): + """ + Serializer for the 'Section' model + """ + + class Meta: + model = Section + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') + + def to_representation(self, obj): + data = super(SectionSerializer, self).to_representation(obj) + data['location'] = obj.get_location_display() + data['seat_numbers'] = obj.get_list_of_seat_numbers + return data diff --git a/src/booking/serializers/stadium.py b/src/booking/serializers/stadium.py new file mode 100644 index 00000000..851a72b6 --- /dev/null +++ b/src/booking/serializers/stadium.py @@ -0,0 +1,14 @@ +from rest_framework.serializers import ModelSerializer + +from booking.models import Stadium + + +class StadiumSerializer(ModelSerializer): + """ + Serializer for the 'Stadium' model + """ + + class Meta: + model = Stadium + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') diff --git a/src/booking/serializers/team.py b/src/booking/serializers/team.py new file mode 100644 index 00000000..b65a487a --- /dev/null +++ b/src/booking/serializers/team.py @@ -0,0 +1,14 @@ +from rest_framework.serializers import ModelSerializer + +from booking.models import Team + + +class TeamSerializer(ModelSerializer): + """ + Serializer for the 'Team' model + """ + + class Meta: + model = Team + fields = '__all__' + read_only_fields = ('created_at', 'updated_at') diff --git a/src/booking/serializers/ticket.py b/src/booking/serializers/ticket.py new file mode 100644 index 00000000..e0c8eabd --- /dev/null +++ b/src/booking/serializers/ticket.py @@ -0,0 +1,118 @@ +from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from django.db import transaction +from django.db.models import Q + +from rest_framework.serializers import ModelSerializer +from rest_framework.exceptions import ValidationError +from rest_framework.response import Response +from rest_framework import status + +from booking.models import Ticket, Invoice + +from extensions.choices import TicketStatusChoices, InvoiceStatusChoices + + +class TicketSerializer(ModelSerializer): + """ + Serializer for the 'Ticket' model + """ + + class Meta: + model = Ticket + fields = '__all__' + read_only_fields = ('invoice', 'status', 'created_at', 'updated_at') + + def validate(self, data): + """ + It will use for making the validation on data + :param data: A dict of fields + :return: A valid data or errors + """ + + match = data.get('match') + section = data.get('section') + seat_number = data.get('seat_number') + + if match.start_time < timezone.now(): + raise ValidationError({ + "match": [_("The time for this match has passed!")] + }) + + if section.stadium != match.stadium: + raise ValidationError({ + "match": [_("The stadium is not the same!")], + "section": [_("The stadium is not the same!")] + }) + + if section.id not in match.stadium.get_section_ids: + raise ValidationError({ + "section": [_("It does not belong to the stadium of this match!")] + }) + + if seat_number not in section.get_list_of_seat_numbers: + raise ValidationError({ + "seat_number": [_("There is no such seat in the selected section!")] + }) + + ticket = self.Meta.model.objects.filter( + invoice__user=self.context['request'].user, + match=match, + section=section, + seat_number=seat_number + ).first() + if ticket: + if ticket.status == TicketStatusChoices.SOLD: + raise ValidationError({ + "seat_number": [_("You have already purchased this seat number!")] + }) + if ticket.status == TicketStatusChoices.RESERVED: + raise ValidationError({ + "seat_number": [_("You have already reserved this seat number, check your invoices section!")] + }) + + unavailable_seats = list(self.Meta.model.objects.filter(match=match, section=section).filter( + Q(status=TicketStatusChoices.RESERVED) | + Q(status=TicketStatusChoices.SOLD) + ).values_list('seat_number', flat=True)) + + if seat_number in unavailable_seats: + raise ValidationError({ + "seat_number": [_("This seat number cannot be purchased!")] + }) + + return data + + def create(self, validated_data): + """ + Create a new instance + :param validated_data: A dict of fields + :return: A new instance + """ + with transaction.atomic(): + try: + match = validated_data.get('match') + section = validated_data.get('section') + seat_number = validated_data.get('seat_number') + + invoice, created = Invoice.objects.get_or_create( + user=self.context['request'].user, + status=InvoiceStatusChoices.UNPAID + ) + + reserved_ticket = self.Meta.model.objects.create( + invoice=invoice, + match=match, + section=section, + seat_number=seat_number, + status=TicketStatusChoices.RESERVED + ) + + return reserved_ticket + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + + def to_representation(self, obj): + data = super(TicketSerializer, self).to_representation(obj) + data['status'] = obj.get_status_display() + return data diff --git a/src/booking/urls.py b/src/booking/urls.py new file mode 100644 index 00000000..bc1ea577 --- /dev/null +++ b/src/booking/urls.py @@ -0,0 +1,14 @@ +from . import views + +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register('stadiums', views.StadiumViewSet, basename='stadiums') +router.register('sections', views.SectionViewSet, basename='sections') +router.register('teams', views.TeamViewSet, basename='teams') +router.register('matches', views.MatchViewSet, basename='matches') +router.register('invoices', views.InvoiceViewSet, basename='invoices') +router.register('tickets', views.TicketViewSet, basename='tickets') +router.register('payments', views.PaymentViewSet, basename='payments') + +urlpatterns = router.urls diff --git a/src/booking/views/__init__.py b/src/booking/views/__init__.py new file mode 100644 index 00000000..a77dcd8f --- /dev/null +++ b/src/booking/views/__init__.py @@ -0,0 +1,17 @@ +from .stadium import StadiumViewSet +from .section import SectionViewSet +from .team import TeamViewSet +from .match import MatchViewSet +from .invoice import InvoiceViewSet +from .ticket import TicketViewSet +from .payment import PaymentViewSet + +__all__ = [ + 'StadiumViewSet', + 'SectionViewSet', + 'TeamViewSet', + 'MatchViewSet', + 'InvoiceViewSet', + 'TicketViewSet', + 'PaymentViewSet', +] diff --git a/src/booking/views/invoice.py b/src/booking/views/invoice.py new file mode 100644 index 00000000..717e50f0 --- /dev/null +++ b/src/booking/views/invoice.py @@ -0,0 +1,21 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ReadOnlyModelViewSet + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Invoice +from booking.serializers import InvoiceSerializer + + +class InvoiceViewSet(ReadOnlyModelViewSet): + """ + ViewSet for the 'Invoice' model objects + """ + serializer_class = InvoiceSerializer + queryset = Invoice.objects.select_related('user').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['user', 'status'] + search_fields = ['user__username'] diff --git a/src/booking/views/match.py b/src/booking/views/match.py new file mode 100644 index 00000000..7264bc06 --- /dev/null +++ b/src/booking/views/match.py @@ -0,0 +1,35 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ModelViewSet +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework import status + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Match +from booking.serializers import MatchSerializer, SectionSerializer + + +class MatchViewSet(ModelViewSet): + """ + ViewSet for the 'Match' model objects + """ + serializer_class = MatchSerializer + queryset = Match.objects.select_related('stadium', 'host_team', 'guest_team').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['stadium', 'host_team', 'guest_team', 'start_time'] + search_fields = ['stadium__name', 'host_team__name', 'guest_team__name', 'start_time'] + + @action(detail=True, methods=["get"], url_path='seats', url_name='seats') + def seats(self, request, pk=None): + """ + This will use for show the current match seats + :return: The current match seats + """ + return Response( + SectionSerializer(self.get_object().stadium.section_stadiums.all(), many=True).data, + status=status.HTTP_200_OK + ) diff --git a/src/booking/views/payment.py b/src/booking/views/payment.py new file mode 100644 index 00000000..1d0587ec --- /dev/null +++ b/src/booking/views/payment.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import GenericViewSet +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Payment +from booking.serializers import PaymentSerializer + + +class PaymentViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet): + """ + ViewSet for the 'Payment' model objects + """ + serializer_class = PaymentSerializer + queryset = Payment.objects.select_related('invoice').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['invoice', 'amount', 'status', 'created_at'] + search_fields = ['invoice', 'invoice__user__username', 'amount', 'created_at'] diff --git a/src/booking/views/section.py b/src/booking/views/section.py new file mode 100644 index 00000000..fc2e3839 --- /dev/null +++ b/src/booking/views/section.py @@ -0,0 +1,21 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ModelViewSet + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Section +from booking.serializers import SectionSerializer + + +class SectionViewSet(ModelViewSet): + """ + ViewSet for the 'Section' model objects + """ + serializer_class = SectionSerializer + queryset = Section.objects.select_related('stadium').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['stadium', 'location', 'capacity', 'price'] + search_fields = ['stadium__name', 'location', 'capacity', 'price'] diff --git a/src/booking/views/stadium.py b/src/booking/views/stadium.py new file mode 100644 index 00000000..84eee176 --- /dev/null +++ b/src/booking/views/stadium.py @@ -0,0 +1,21 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ModelViewSet + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Stadium +from booking.serializers import StadiumSerializer + + +class StadiumViewSet(ModelViewSet): + """ + ViewSet for the 'Stadium' model objects + """ + serializer_class = StadiumSerializer + queryset = Stadium.objects.prefetch_related('section_stadiums').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', 'province', 'city'] + search_fields = ['name', 'province', 'city', 'address'] diff --git a/src/booking/views/team.py b/src/booking/views/team.py new file mode 100644 index 00000000..3b95c1e0 --- /dev/null +++ b/src/booking/views/team.py @@ -0,0 +1,21 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ModelViewSet + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Team +from booking.serializers import TeamSerializer + + +class TeamViewSet(ModelViewSet): + """ + ViewSet for the 'Team' model objects + """ + serializer_class = TeamSerializer + queryset = Team.objects.all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name'] + search_fields = ['name'] diff --git a/src/booking/views/ticket.py b/src/booking/views/ticket.py new file mode 100644 index 00000000..8ee83792 --- /dev/null +++ b/src/booking/views/ticket.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import GenericViewSet +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin + +from extensions.custom_permissions import CustomDjangoModelPermission + +from booking.models import Ticket +from booking.serializers import TicketSerializer + + +class TicketViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet): + """ + ViewSet for the 'Ticket' model objects + """ + serializer_class = TicketSerializer + queryset = Ticket.objects.select_related('invoice', 'match', 'section').all() + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['invoice', 'match', 'section', 'seat_number', 'status'] + search_fields = ['invoice', 'invoice__user__username', 'match', 'section', 'seat_number'] diff --git a/src/commands/__init__.py b/src/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/apps.py b/src/commands/apps.py new file mode 100644 index 00000000..e7d5b925 --- /dev/null +++ b/src/commands/apps.py @@ -0,0 +1,8 @@ +from django.utils.translation import gettext_lazy as _ +from django.apps import AppConfig + + +class CommandsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'commands' + verbose_name = _('Commands') diff --git a/src/commands/management/commands/initialize_admin.py b/src/commands/management/commands/initialize_admin.py new file mode 100644 index 00000000..258a6afe --- /dev/null +++ b/src/commands/management/commands/initialize_admin.py @@ -0,0 +1,19 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model + +import os + + +class Command(BaseCommand): + """ Create default superuser """ + + def handle(self, *args, **kwargs): + admin_username = os.environ.get("ADMIN_USERNAME", "admin@ticketing.sample") + + if not get_user_model().objects.filter(username=admin_username).exists(): + get_user_model().objects.create_superuser( + password=os.environ.get("ADMIN_PASSWORD", "admin@control*987"), + username=admin_username + ) + else: + print(f'An admin user with username = {admin_username} exists') diff --git a/src/commands/management/commands/wait_for_db.py b/src/commands/management/commands/wait_for_db.py new file mode 100644 index 00000000..5c53d0c5 --- /dev/null +++ b/src/commands/management/commands/wait_for_db.py @@ -0,0 +1,20 @@ +from django.core.management.base import BaseCommand +from django.db.utils import OperationalError +from django.db import connections + +import time + + +class Command(BaseCommand): + """ django command to pause execution until database is available """ + + def handle(self, *args, **kwargs): + self.stdout.write('Waiting for database ...') + db_connection = None + while not db_connection: + try: + db_connection = connections['default'] + except OperationalError: + self.stdout.write('Database unavailable, waiting ...') + time.sleep(1) + self.stdout.write(self.style.SUCCESS('Database available')) diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/core/asgi.py b/src/core/asgi.py new file mode 100644 index 00000000..3b35c6bc --- /dev/null +++ b/src/core/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for core project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_asgi_application() diff --git a/src/core/settings.py b/src/core/settings.py new file mode 100644 index 00000000..f37ffdc9 --- /dev/null +++ b/src/core/settings.py @@ -0,0 +1,221 @@ +""" +Django settings for core project. + +Generated by 'django-admin startproject' using Django 4.2.6. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from django.utils.translation import gettext_lazy as _ + +from pathlib import Path + +from datetime import timedelta + +import os + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get( + 'SECRET_KEY', + 'django-insecure-_kh1q=rv$1y9)wf_(pnub5dlppl5)^13rfr$i*(7z2*h=wq50^' +) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = bool(int(os.environ.get("DEBUG", 1))) + +# Allowed Hosts +ALLOWED_HOSTS = [] +ALLOWED_HOSTS_ENV = os.environ.get("ALLOWED_HOSTS") +if ALLOWED_HOSTS_ENV: + ALLOWED_HOSTS.extend(ALLOWED_HOSTS_ENV.split(",")) + +# Application definition +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'rest_framework_simplejwt', + 'drf_yasg', + 'django_filters', + 'user_management.apps.UserManagementConfig', + 'commands.apps.CommandsConfig', + 'booking.apps.BookingConfig', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'core.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'core.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.environ.get("DB_NAME", "ticket_sales_system"), + "USER": os.environ.get("DB_USER", "postgres"), + "PASSWORD": os.environ.get("DB_PASS", "123456"), + "HOST": os.environ.get("DB_HOST", "localhost"), + "PORT": int(os.environ.get("DB_PORT", "5432")), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ +LANGUAGE_CODE = 'fa' + +LANGUAGES = [ + ('fa', _('Persian')), + ('en', _('English')), +] + +LOCALE_PATHS = ( + os.path.join(BASE_DIR, 'locale'), +) + +TIME_ZONE = os.environ.get("TIME_ZONE", "Asia/Tehran") + +USE_I18N = True + +USE_L10N = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ +STATIC_URL = '/statics/' +STATIC_ROOT = os.path.join(BASE_DIR, 'statics') + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Auth Settings +AUTH_USER_MODEL = 'user_management.User' + +# Django REST Framework +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticatedOrReadOnly', + 'rest_framework.permissions.DjangoModelPermissions', + ), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), + 'DEFAULT_THROTTLE_CLASSES': [ + 'rest_framework.throttling.AnonRateThrottle', + 'rest_framework.throttling.UserRateThrottle' + ], + 'DEFAULT_THROTTLE_RATES': { + 'anon': '4/second', + 'user': '30/second' + }, + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 20, + 'EXCEPTION_HANDLER': 'extensions.custom_exception_handlers.exception_handler', +} + +# Simple JWT +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + "UPDATE_LAST_LOGIN": True, +} + +# Swagger +SWAGGER_SETTINGS = { + 'USE_SESSION_AUTH': False, + 'APIS_SORTER': 'alpha', + 'SECURITY_DEFINITIONS': { + 'Bearer': { + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header' + } + } +} + +# security configs for production +if not DEBUG: + # Https settings + SESSION_COOKIE_SECURE = True + CSRF_COOKIE_SECURE = True + SECURE_SSL_REDIRECT = True + SECURE_SSL_HOST = True + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + + # HSTS settings + SECURE_HSTS_SECONDS = 31536000 # 1 year + SECURE_HSTS_PRELOAD = True + SECURE_HSTS_INCLUDE_SUBDOMAINS = True + + # more security settings + SECURE_CONTENT_TYPE_NOSNIFF = True + SECURE_BROWSER_XSS_FILTER = True + X_FRAME_OPTIONS = "ALLOW" + SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin" + USE_X_FORWARDED_HOST = True + + CORS_ALLOW_ALL_ORIGINS = True + CORS_ALLOW_CREDENTIALS = True diff --git a/src/core/urls.py b/src/core/urls.py new file mode 100644 index 00000000..6a901bef --- /dev/null +++ b/src/core/urls.py @@ -0,0 +1,29 @@ +""" +URL configuration for core project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.utils.translation import gettext_lazy as _ +from django.contrib import admin +from django.urls import path, include + +admin.autodiscover() +admin.site.site_header = _('site_header') +admin.site.site_title = _('site_title') +admin.site.index_title = _('index_title') + +urlpatterns = [ + path('api/v1/', include(('api.urls', 'api'), namespace='api')), + path('', admin.site.urls), +] diff --git a/src/core/wsgi.py b/src/core/wsgi.py new file mode 100644 index 00000000..f44964d5 --- /dev/null +++ b/src/core/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for core project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_wsgi_application() diff --git a/src/extensions/__init__.py b/src/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/extensions/abstract_models.py b/src/extensions/abstract_models.py new file mode 100644 index 00000000..947bf54b --- /dev/null +++ b/src/extensions/abstract_models.py @@ -0,0 +1,18 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + + +class AbstractCreatAtUpdateAt(models.Model): + created_at = models.DateTimeField( + verbose_name=_('Created At'), + auto_now_add=True, + editable=False + ) + updated_at = models.DateTimeField( + verbose_name=_('Updated At'), + auto_now=True, + editable=False + ) + + class Meta: + abstract = True diff --git a/src/extensions/choices.py b/src/extensions/choices.py new file mode 100644 index 00000000..b1049e2a --- /dev/null +++ b/src/extensions/choices.py @@ -0,0 +1,36 @@ +from django.db.models import IntegerChoices +from django.utils.translation import gettext_lazy as _ + + +class GenderChoices(IntegerChoices): + """Enum class for the gender fields""" + MALE = 0, _('Male') + FEMALE = 1, _('Female') + + __empty__ = '---------' + + +class LocationChoices(IntegerChoices): + """Enum class for the location fields""" + A = 0, _('A') + B = 1, _('B') + C = 2, _('C') + VIP = 3, _('VIP') + + +class InvoiceStatusChoices(IntegerChoices): + """Enum class for the invoice status fields""" + PAID = 0, _('Paid') + UNPAID = 1, _('Unpaid') + + +class TicketStatusChoices(IntegerChoices): + """Enum class for the ticket status fields""" + SOLD = 0, _('Sold') + RESERVED = 1, _('Reserved') + + +class PaymentStatusChoices(IntegerChoices): + """Enum class for the payment status fields""" + SUCCESSFUL = 0, _('Successful') + UNSUCCESSFUL = 1, _('Unsuccessful') diff --git a/src/extensions/custom_exception_handlers.py b/src/extensions/custom_exception_handlers.py new file mode 100644 index 00000000..a8d11b39 --- /dev/null +++ b/src/extensions/custom_exception_handlers.py @@ -0,0 +1,36 @@ +from django.core.exceptions import ValidationError as DjangoValidationError +from django.db.models import ProtectedError as DjangoProtectedError +from django.utils.translation import gettext_lazy as _ + +from rest_framework.exceptions import ValidationError as DRFValidationError +from rest_framework.views import exception_handler as drf_exception_handler + +import re + + +def exception_handler(exc, context): + """Handle Django ValidationError as an accepted exception""" + + if isinstance(exc, DjangoValidationError): + exc = DRFValidationError(detail=exc.message_dict) + + if isinstance(exc, DjangoProtectedError): + matches = re.search(r"\(\"([^()]+)\"", exc.args.__str__()) + exc = DRFValidationError( + detail={ + "error": { + "type": str(exc.__class__.__name__), + "message": matches.group(1) if matches else _('It is not possible to delete this object.') + }, + "protected_elements": [ + { + "id": protected_object.pk, + "model": str(protected_object._meta.model.__name__), + "label": protected_object.__str__() + } + for protected_object in exc.protected_objects + ] + } + ) + + return drf_exception_handler(exc, context) diff --git a/src/extensions/custom_permissions.py b/src/extensions/custom_permissions.py new file mode 100644 index 00000000..db5aeacb --- /dev/null +++ b/src/extensions/custom_permissions.py @@ -0,0 +1,23 @@ +from rest_framework.permissions import DjangoModelPermissions, BasePermission + +from copy import deepcopy + + +class CustomDjangoModelPermission(DjangoModelPermissions): + """Custom permission class to handle better permission by model permissions""" + def __init__(self): + self.perms_map = deepcopy(self.perms_map) + self.perms_map['GET'] = ['%(app_label)s.view_%(model_name)s'] + + +class UnauthenticatedPost(BasePermission): + """Allow to unauthenticated user to send the 'POST' request""" + def has_permission(self, request, view): + return request.method in ['POST'] + + +class OwnUserPermission(BasePermission): + """Object-level permission to only allow updating his own user""" + def has_object_permission(self, request, view, obj): + # obj here is a User instance + return obj.id == request.user.id diff --git a/src/locale/en/LC_MESSAGES/django.mo b/src/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 00000000..71cbdf3e Binary files /dev/null and b/src/locale/en/LC_MESSAGES/django.mo differ diff --git a/src/locale/en/LC_MESSAGES/django.po b/src/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..a7ec169f --- /dev/null +++ b/src/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,328 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-27 15:17+0330\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: booking/apps.py:8 +msgid "Booking" +msgstr "" + +#: booking/models/invoice.py:14 user_management/models/profile.py:12 +#: user_management/models/user.py:39 +msgid "User" +msgstr "" + +#: booking/models/invoice.py:19 booking/models/payment.py:21 +#: booking/models/ticket.py:31 +msgid "Status" +msgstr "" + +#: booking/models/invoice.py:27 booking/models/payment.py:11 +#: booking/models/ticket.py:11 +msgid "Invoice" +msgstr "" + +#: booking/models/invoice.py:28 +msgid "Invoices" +msgstr "" + +#: booking/models/match.py:10 booking/models/section.py:11 +#: booking/models/stadium.py:35 +msgid "Stadium" +msgstr "" + +#: booking/models/match.py:16 +msgid "Host Team" +msgstr "" + +#: booking/models/match.py:22 +msgid "Guest Team" +msgstr "" + +#: booking/models/match.py:27 +msgid "Start Time" +msgstr "" + +#: booking/models/match.py:33 booking/models/ticket.py:17 +msgid "Match" +msgstr "" + +#: booking/models/match.py:34 +msgid "Matches" +msgstr "" + +#: booking/models/payment.py:16 +msgid "Amount" +msgstr "" + +#: booking/models/payment.py:25 extensions/abstract_models.py:7 +msgid "Created At" +msgstr "" + +#: booking/models/payment.py:33 +msgid "Payment" +msgstr "" + +#: booking/models/payment.py:34 +msgid "Payments" +msgstr "" + +#: booking/models/section.py:16 +msgid "Location" +msgstr "" + +#: booking/models/section.py:20 +msgid "Capacity" +msgstr "" + +#: booking/models/section.py:23 +msgid "Price" +msgstr "" + +#: booking/models/section.py:31 booking/models/ticket.py:23 +msgid "Section" +msgstr "" + +#: booking/models/section.py:32 +msgid "Sections" +msgstr "" + +#: booking/models/stadium.py:9 booking/models/team.py:9 +msgid "Name" +msgstr "" + +#: booking/models/stadium.py:14 +msgid "Province" +msgstr "" + +#: booking/models/stadium.py:19 +msgid "City" +msgstr "" + +#: booking/models/stadium.py:24 +msgid "Address" +msgstr "" + +#: booking/models/stadium.py:28 +msgid "Map URL" +msgstr "" + +#: booking/models/stadium.py:36 +msgid "Stadiums" +msgstr "" + +#: booking/models/team.py:18 +msgid "Team" +msgstr "" + +#: booking/models/team.py:19 +msgid "Teams" +msgstr "" + +#: booking/models/ticket.py:28 +msgid "Seat Number" +msgstr "" + +#: booking/models/ticket.py:39 +msgid "Ticket" +msgstr "" + +#: booking/models/ticket.py:40 +msgid "Tickets" +msgstr "" + +#: booking/serializers/payment.py:38 +msgid "This invoice does not belong to you!" +msgstr "" + +#: booking/serializers/payment.py:43 +msgid "This invoice has already been paid!" +msgstr "" + +#: booking/serializers/payment.py:48 +msgid "The entered amount is not equal to the invoice amount!" +msgstr "" + +#: booking/serializers/ticket.py:39 +msgid "The time for this match has passed!" +msgstr "" + +#: booking/serializers/ticket.py:44 booking/serializers/ticket.py:45 +msgid "The stadium is not the same!" +msgstr "" + +#: booking/serializers/ticket.py:50 +msgid "It does not belong to the stadium of this match!" +msgstr "" + +#: booking/serializers/ticket.py:55 +msgid "There is no such seat in the selected section!" +msgstr "" + +#: booking/serializers/ticket.py:67 +msgid "You have already purchased this seat number!" +msgstr "" + +#: booking/serializers/ticket.py:71 +msgid "" +"You have already reserved this seat number, check your invoices section!" +msgstr "" + +#: booking/serializers/ticket.py:81 +msgid "This seat number cannot be purchased!" +msgstr "" + +#: commands/apps.py:8 +msgid "Commands" +msgstr "" + +#: core/settings.py:129 +msgid "Persian" +msgstr "" + +#: core/settings.py:130 +msgid "English" +msgstr "" + +#: core/urls.py:22 +msgid "site_header" +msgstr "" + +#: core/urls.py:23 +msgid "site_title" +msgstr "" + +#: core/urls.py:24 +msgid "index_title" +msgstr "" + +#: extensions/abstract_models.py:12 +msgid "Updated At" +msgstr "" + +#: extensions/choices.py:7 +msgid "Male" +msgstr "" + +#: extensions/choices.py:8 +msgid "Female" +msgstr "" + +#: extensions/choices.py:15 +msgid "A" +msgstr "" + +#: extensions/choices.py:16 +msgid "B" +msgstr "" + +#: extensions/choices.py:17 +msgid "C" +msgstr "" + +#: extensions/choices.py:18 +msgid "VIP" +msgstr "" + +#: extensions/choices.py:23 +msgid "Paid" +msgstr "" + +#: extensions/choices.py:24 +msgid "Unpaid" +msgstr "" + +#: extensions/choices.py:29 +msgid "Sold" +msgstr "" + +#: extensions/choices.py:30 +msgid "Reserved" +msgstr "" + +#: extensions/choices.py:35 +msgid "Successful" +msgstr "" + +#: extensions/choices.py:36 +msgid "Unsuccessful" +msgstr "" + +#: extensions/custom_exception_handlers.py:23 +msgid "It is not possible to delete this object." +msgstr "" + +#: user_management/admin.py:43 user_management/admin.py:52 +msgid "Access Control" +msgstr "" + +#: user_management/admin.py:44 +msgid "Important Date" +msgstr "" + +#: user_management/admin.py:71 +msgid "Full Name" +msgstr "" + +#: user_management/apps.py:8 +msgid "User Management" +msgstr "" + +#: user_management/models/profile.py:18 +msgid "First Name" +msgstr "" + +#: user_management/models/profile.py:23 +msgid "Last Name" +msgstr "" + +#: user_management/models/profile.py:28 +msgid "Gender" +msgstr "" + +#: user_management/models/profile.py:37 +msgid "Profile" +msgstr "" + +#: user_management/models/profile.py:38 +msgid "Profiles" +msgstr "" + +#: user_management/models/user.py:16 +msgid "Username" +msgstr "" + +#: user_management/models/user.py:21 +msgid "Is Staff" +msgstr "" + +#: user_management/models/user.py:26 +msgid "Is Active" +msgstr "" + +#: user_management/models/user.py:40 +msgid "Users" +msgstr "" + +#: user_management/serializers/user.py:52 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "" + +#: user_management/serializers/user.py:72 +msgid "The two password fields didn't match." +msgstr "" diff --git a/src/locale/fa/LC_MESSAGES/django.mo b/src/locale/fa/LC_MESSAGES/django.mo new file mode 100644 index 00000000..cbe0a688 Binary files /dev/null and b/src/locale/fa/LC_MESSAGES/django.mo differ diff --git a/src/locale/fa/LC_MESSAGES/django.po b/src/locale/fa/LC_MESSAGES/django.po new file mode 100644 index 00000000..678962c4 --- /dev/null +++ b/src/locale/fa/LC_MESSAGES/django.po @@ -0,0 +1,336 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-27 15:17+0330\n" +"PO-Revision-Date: 2023-10-27 15:21+0330\n" +"Last-Translator: Mahdi Namaki \n" +"Language-Team: \n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 2.3\n" + +#: booking/apps.py:8 +msgid "Booking" +msgstr "رزرو" + +#: booking/models/invoice.py:14 user_management/models/profile.py:12 +#: user_management/models/user.py:39 +msgid "User" +msgstr "کاربر" + +#: booking/models/invoice.py:19 booking/models/payment.py:21 +#: booking/models/ticket.py:31 +msgid "Status" +msgstr "وضعیت" + +#: booking/models/invoice.py:27 booking/models/payment.py:11 +#: booking/models/ticket.py:11 +msgid "Invoice" +msgstr "فاکتور" + +#: booking/models/invoice.py:28 +msgid "Invoices" +msgstr "فاکتورها" + +#: booking/models/match.py:10 booking/models/section.py:11 +#: booking/models/stadium.py:35 +msgid "Stadium" +msgstr "استادیوم" + +#: booking/models/match.py:16 +msgid "Host Team" +msgstr "تیم میزبان" + +#: booking/models/match.py:22 +msgid "Guest Team" +msgstr "تیم مهمان" + +#: booking/models/match.py:27 +msgid "Start Time" +msgstr "تاریخ آغاز" + +#: booking/models/match.py:33 booking/models/ticket.py:17 +msgid "Match" +msgstr "مسابقه" + +#: booking/models/match.py:34 +msgid "Matches" +msgstr "مسابقه ها" + +#: booking/models/payment.py:16 +msgid "Amount" +msgstr "مبلغ" + +#: booking/models/payment.py:25 extensions/abstract_models.py:7 +msgid "Created At" +msgstr "ایجاد شده در" + +#: booking/models/payment.py:33 +msgid "Payment" +msgstr "پرداخت" + +#: booking/models/payment.py:34 +msgid "Payments" +msgstr "پرداخت ها" + +#: booking/models/section.py:16 +msgid "Location" +msgstr "موقعیت" + +#: booking/models/section.py:20 +msgid "Capacity" +msgstr "ظرفیت" + +#: booking/models/section.py:23 +msgid "Price" +msgstr "قیمت" + +#: booking/models/section.py:31 booking/models/ticket.py:23 +msgid "Section" +msgstr "جایگاه" + +#: booking/models/section.py:32 +msgid "Sections" +msgstr "جایگاه ها" + +#: booking/models/stadium.py:9 booking/models/team.py:9 +msgid "Name" +msgstr "نام" + +#: booking/models/stadium.py:14 +msgid "Province" +msgstr "استان" + +#: booking/models/stadium.py:19 +msgid "City" +msgstr "شهر" + +#: booking/models/stadium.py:24 +msgid "Address" +msgstr "آدرس" + +#: booking/models/stadium.py:28 +msgid "Map URL" +msgstr "آدرس نقشه" + +#: booking/models/stadium.py:36 +msgid "Stadiums" +msgstr "استادیوم ها" + +#: booking/models/team.py:18 +msgid "Team" +msgstr "تیم" + +#: booking/models/team.py:19 +msgid "Teams" +msgstr "تیم ها" + +#: booking/models/ticket.py:28 +msgid "Seat Number" +msgstr "شماره صندلی" + +#: booking/models/ticket.py:39 +msgid "Ticket" +msgstr "بلیط" + +#: booking/models/ticket.py:40 +msgid "Tickets" +msgstr "بلیط ها" + +#: booking/serializers/payment.py:38 +msgid "This invoice does not belong to you!" +msgstr "این فاکتور متعلق به شما نیست!" + +#: booking/serializers/payment.py:43 +msgid "This invoice has already been paid!" +msgstr "این فاکتور قبلا پرداخت شده است!" + +#: booking/serializers/payment.py:48 +msgid "The entered amount is not equal to the invoice amount!" +msgstr "مبلغ وارد شده با مبلغ فاکتور برابر نیست!" + +#: booking/serializers/ticket.py:39 +msgid "The time for this match has passed!" +msgstr "زمان این مسابقه گذشت!" + +#: booking/serializers/ticket.py:44 booking/serializers/ticket.py:45 +msgid "The stadium is not the same!" +msgstr "استادیوم یکسان نیست!" + +#: booking/serializers/ticket.py:50 +msgid "It does not belong to the stadium of this match!" +msgstr "به ورزشگاه این مسابقه تعلق ندارد!" + +#: booking/serializers/ticket.py:55 +msgid "There is no such seat in the selected section!" +msgstr "در بخش انتخاب شده چنین صندلی وجود ندارد!" + +#: booking/serializers/ticket.py:67 +msgid "You have already purchased this seat number!" +msgstr "شما قبلاً این شماره صندلی را خریداری کرده اید!" + +#: booking/serializers/ticket.py:71 +msgid "You have already reserved this seat number, check your invoices section!" +msgstr "شما قبلاً این شماره صندلی را رزرو کرده اید، بخش فاکتورهای خود را بررسی کنید!" + +#: booking/serializers/ticket.py:81 +msgid "This seat number cannot be purchased!" +msgstr "این شماره صندلی را نمی توان خرید!" + +#: commands/apps.py:8 +msgid "Commands" +msgstr "دستورات" + +#: core/settings.py:129 +msgid "Persian" +msgstr "فارسی" + +#: core/settings.py:130 +msgid "English" +msgstr "English" + +#: core/urls.py:22 +msgid "site_header" +msgstr "مدیریت پرتال فروش بلیط" + +#: core/urls.py:23 +msgid "site_title" +msgstr "مدیریت پرتال فروش بلیط" + +#: core/urls.py:24 +msgid "index_title" +msgstr "به مدیریت پرتال فروش بلیط خوش آمدید" + +#: extensions/abstract_models.py:12 +msgid "Updated At" +msgstr "به روز شده در" + +#: extensions/choices.py:7 +msgid "Male" +msgstr "مرد" + +#: extensions/choices.py:8 +msgid "Female" +msgstr "زن" + +#: extensions/choices.py:15 +msgid "A" +msgstr "" + +#: extensions/choices.py:16 +msgid "B" +msgstr "" + +#: extensions/choices.py:17 +msgid "C" +msgstr "" + +#: extensions/choices.py:18 +msgid "VIP" +msgstr "" + +#: extensions/choices.py:23 +msgid "Paid" +msgstr "پرداخت شده" + +#: extensions/choices.py:24 +msgid "Unpaid" +msgstr "پرداخت نشده" + +#: extensions/choices.py:29 +msgid "Sold" +msgstr "فروخته شده" + +#: extensions/choices.py:30 +msgid "Reserved" +msgstr "رزرو شده" + +#: extensions/choices.py:35 +msgid "Successful" +msgstr "موفق" + +#: extensions/choices.py:36 +msgid "Unsuccessful" +msgstr "ناموفق" + +#: extensions/custom_exception_handlers.py:23 +msgid "It is not possible to delete this object." +msgstr "امکان حذف این شی وجود ندارد." + +#: user_management/admin.py:43 user_management/admin.py:52 +msgid "Access Control" +msgstr "کنترل دسترسی" + +#: user_management/admin.py:44 +msgid "Important Date" +msgstr "تاریخ های مهم" + +#: user_management/admin.py:71 +msgid "Full Name" +msgstr "نام و نام خانوادگی" + +#: user_management/apps.py:8 +msgid "User Management" +msgstr "مدیریت کاربر" + +#: user_management/models/profile.py:18 +msgid "First Name" +msgstr "نام" + +#: user_management/models/profile.py:23 +msgid "Last Name" +msgstr "نام خانوادگی" + +#: user_management/models/profile.py:28 +msgid "Gender" +msgstr "جنسیت" + +#: user_management/models/profile.py:37 +msgid "Profile" +msgstr "پروفایل" + +#: user_management/models/profile.py:38 +msgid "Profiles" +msgstr "پروفایل ها" + +#: user_management/models/user.py:16 +msgid "Username" +msgstr "نام کاربری" + +#: user_management/models/user.py:21 +msgid "Is Staff" +msgstr "دسترسی به بخش مدیریت" + +#: user_management/models/user.py:26 +msgid "Is Active" +msgstr "فعال بودن" + +#: user_management/models/user.py:40 +msgid "Users" +msgstr "کاربران" + +#: user_management/serializers/user.py:52 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "رمز عبور قدیمی شما اشتباه وارد شده است. لطفا دوباره وارد کنید." + +#: user_management/serializers/user.py:72 +msgid "The two password fields didn't match." +msgstr "دو فیلد رمز عبور مطابقت نداشتند." + +#~ msgid "Unsold" +#~ msgstr "فروخته نشده" + +#~ msgid "It's not a valid pk!" +#~ msgstr "این یک pk معتبر نیست!" + +#~ msgid "You can't change another user's password with this method!" +#~ msgstr "با این روش نمی توانید پسورد کاربر دیگری را تغییر دهید!" diff --git a/src/manage.py b/src/manage.py new file mode 100755 index 00000000..f2a662cf --- /dev/null +++ b/src/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/src/user_management/__init__.py b/src/user_management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/user_management/admin.py b/src/user_management/admin.py new file mode 100644 index 00000000..27ec4622 --- /dev/null +++ b/src/user_management/admin.py @@ -0,0 +1,74 @@ +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.utils.translation import gettext_lazy as _ +from django.contrib import admin + +from user_management.models import User, Profile + + +class ProfileInlineAdmin(admin.TabularInline): + model = Profile + + +class UserAdmin(BaseUserAdmin): + list_display = [ + 'username', + 'get_full_name', + 'is_active', + 'is_superuser', + 'is_staff', + 'created_at', + 'updated_at', + 'last_login' + ] + + list_filter = [ + 'is_active', + 'is_superuser', + 'is_staff', + 'created_at', + 'updated_at', + 'last_login', + ] + + search_fields = [ + 'username', + ] + + actions = [ + 'delete_selected', + ] + + fieldsets = ( + (None, {'fields': ('username', 'password')}), + (_("Access Control"), {'fields': ('is_active', 'is_superuser', 'is_staff', 'groups')}), + (_("Important Date"), {'fields': ('created_at', 'updated_at', 'last_login')}), + ) + + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('username', 'password1', 'password2')} + ), + (_("Access Control"), {'fields': ('is_active', 'is_superuser', 'is_staff', 'groups')}), + ) + + readonly_fields = [ + 'created_at', + 'updated_at', + 'last_login', + ] + + inlines = ( + ProfileInlineAdmin, + ) + + def get_queryset(self, request): + return self.model.objects.select_related('profile_user') + + def get_full_name(self, obj): + return obj.profile_user.get_full_name + + get_full_name.short_description = _('Full Name') + + +admin.site.register(User, UserAdmin) diff --git a/src/user_management/apps.py b/src/user_management/apps.py new file mode 100644 index 00000000..369c31de --- /dev/null +++ b/src/user_management/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class UserManagementConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user_management' + verbose_name = _('User Management') + + def ready(self): + import user_management.signals diff --git a/src/user_management/managers.py b/src/user_management/managers.py new file mode 100644 index 00000000..6cbb48dd --- /dev/null +++ b/src/user_management/managers.py @@ -0,0 +1,32 @@ +from django.contrib.auth.base_user import BaseUserManager + + +class UserManager(BaseUserManager): + """Manager class for the 'user' model""" + use_in_migrations = True + + def create_user(self, password, **extra_fields): + """This will create a new regular user""" + user = self.model(**extra_fields) + user.is_superuser = False + user.set_password(password) + user.save(using=self._db) + return user + + def create_staff(self, password, **extra_fields): + """This will create a new staff user""" + user = self.model(**extra_fields) + user.is_staff = True + user.is_superuser = False + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, password, **extra_fields): + """This will create a new super admin user""" + user = self.model(**extra_fields) + user.is_staff = True + user.is_superuser = True + user.set_password(password) + user.save(using=self._db) + return user diff --git a/src/user_management/migrations/0001_user.py b/src/user_management/migrations/0001_user.py new file mode 100644 index 00000000..7fc9cfc2 --- /dev/null +++ b/src/user_management/migrations/0001_user.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.6 on 2023-10-26 09:06 + +from django.db import migrations, models +import user_management.managers + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('username', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Username')), + ('is_staff', models.BooleanField(default=False, verbose_name='Is Staff')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ('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')), + ('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')), + ], + options={ + 'verbose_name': 'User', + 'verbose_name_plural': 'Users', + 'db_table': 'user_management_users', + 'ordering': ['id'], + }, + managers=[ + ('objects', user_management.managers.UserManager()), + ], + ), + ] diff --git a/src/user_management/migrations/0002_profile.py b/src/user_management/migrations/0002_profile.py new file mode 100644 index 00000000..c8628de8 --- /dev/null +++ b/src/user_management/migrations/0002_profile.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.6 on 2023-10-26 09:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0001_user'), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='profile_user', serialize=False, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('first_name', models.CharField(blank=True, max_length=200, verbose_name='First Name')), + ('last_name', models.CharField(blank=True, max_length=200, verbose_name='Last Name')), + ('gender', models.PositiveSmallIntegerField(blank=True, null=True, choices=[(None, '---------'), (0, 'Male'), (1, 'Female')], verbose_name='Gender')), + ], + options={ + 'verbose_name': 'Profile', + 'verbose_name_plural': 'Profiles', + 'db_table': 'user_management_profiles', + 'ordering': ['user_id'], + }, + ), + ] diff --git a/src/user_management/migrations/__init__.py b/src/user_management/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/user_management/models/__init__.py b/src/user_management/models/__init__.py new file mode 100644 index 00000000..b5836d23 --- /dev/null +++ b/src/user_management/models/__init__.py @@ -0,0 +1,7 @@ +from .user import User +from .profile import Profile + +__all__ = [ + 'User', + 'Profile', +] diff --git a/src/user_management/models/profile.py b/src/user_management/models/profile.py new file mode 100644 index 00000000..330e6df7 --- /dev/null +++ b/src/user_management/models/profile.py @@ -0,0 +1,46 @@ +from django.utils.translation import gettext_lazy as _ +from django.contrib.auth import get_user_model +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt +from extensions.choices import GenderChoices + + +class Profile(AbstractCreatAtUpdateAt, models.Model): + user = models.OneToOneField( + get_user_model(), + verbose_name=_('User'), + on_delete=models.CASCADE, + primary_key=True, + related_name='profile_user' + ) + first_name = models.CharField( + verbose_name=_('First Name'), + max_length=200, + blank=True + ) + last_name = models.CharField( + verbose_name=_('Last Name'), + max_length=200, + blank=True + ) + gender = models.PositiveSmallIntegerField( + verbose_name=_('Gender'), + choices=GenderChoices.choices, + blank=True, + null=True + ) + + class Meta: + app_label = 'user_management' + db_table = 'user_management_profiles' + verbose_name = _('Profile') + verbose_name_plural = _('Profiles') + ordering = ['user_id'] + + def __str__(self) -> str: + return f"{self.first_name} {self.last_name}" if self.first_name and self.last_name else self.user.__str__() + + @property + def get_full_name(self) -> str: + return f"{self.first_name} {self.last_name}" if self.first_name and self.last_name else '' diff --git a/src/user_management/models/user.py b/src/user_management/models/user.py new file mode 100644 index 00000000..222d1d5e --- /dev/null +++ b/src/user_management/models/user.py @@ -0,0 +1,48 @@ +from django.contrib.auth.base_user import AbstractBaseUser +from django.contrib.auth.models import PermissionsMixin +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from extensions.abstract_models import AbstractCreatAtUpdateAt +from user_management.managers import UserManager + + +class User(AbstractBaseUser, AbstractCreatAtUpdateAt, PermissionsMixin): + """ + Custom user model to authenticate user by email address, + Verification of users by sending an activation link to their email is not implemented. + """ + username = models.EmailField( + verbose_name=_('Username'), + unique=True, + db_index=True + ) + is_staff = models.BooleanField( + verbose_name=_('Is Staff'), + default=False + ) + # This should change to true after sending the activation email, but is ignored here. + is_active = models.BooleanField( + verbose_name=_('Is Active'), + default=True + ) + + objects = UserManager() + + USERNAME_FIELD = 'username' + EMAIL_FIELD = 'username' + REQUIRED_FIELDS = [] + + class Meta: + app_label = 'user_management' + db_table = 'user_management_users' + verbose_name = _('User') + verbose_name_plural = _('Users') + ordering = ['id'] + + def save(self, *args, **kwargs): + self.full_clean() + super(User, self).save(*args, **kwargs) + + def __str__(self) -> str: + return self.username.__str__() diff --git a/src/user_management/serializers/__init__.py b/src/user_management/serializers/__init__.py new file mode 100644 index 00000000..e1fa2d59 --- /dev/null +++ b/src/user_management/serializers/__init__.py @@ -0,0 +1,13 @@ +from .permission import PermissionSerializer +from .group import GroupSerializer, CreateGroupSerializer +from .user import UserSerializer, UserPasswordChangeSerializer +from .profile import ProfileSerializer + +__all__ = [ + 'PermissionSerializer', + 'GroupSerializer', + 'CreateGroupSerializer', + 'UserSerializer', + 'UserPasswordChangeSerializer', + 'ProfileSerializer', +] diff --git a/src/user_management/serializers/group.py b/src/user_management/serializers/group.py new file mode 100644 index 00000000..a77984c0 --- /dev/null +++ b/src/user_management/serializers/group.py @@ -0,0 +1,69 @@ +from django.contrib.auth.models import Group, Permission + +from rest_framework.relations import PrimaryKeyRelatedField +from rest_framework.serializers import ModelSerializer + +from user_management.serializers import PermissionSerializer + + +class GroupSerializer(ModelSerializer): + """ + Serializer for the 'Group' model + """ + + permissions = PermissionSerializer(many=True, read_only=True) + + class Meta: + model = Group + fields = ('id', 'name', 'permissions',) + + +class CreateGroupSerializer(ModelSerializer): + """ + Serializer for creating a new 'Group' instance + """ + + permissions = PermissionSerializer(many=True, read_only=True) + permission_ids = PrimaryKeyRelatedField( + many=True, + write_only=True, + queryset=Permission.objects.all() + ) + + class Meta: + model = Group + fields = ('id', 'name', 'permissions', 'permission_ids',) + + def create(self, validated_data): + """ + Create a new instance + :param validated_data: A dict of fields + :return: A new instance + """ + + permission_ids = validated_data.pop('permission_ids') + group = Group.objects.create(**validated_data) + group.permissions.set(permission_ids) + + return group + + def update(self, instance, validated_data): + """ + Update exists instance + :param instance: Current instance + :param validated_data: A dict of fields + :return: Current instance with changes + """ + + # Get the permission ids + permission_ids = validated_data.pop('permission_ids', [item.id for item in instance.permissions.all()]) + + # Update the 'name' field + instance.name = validated_data.get('name', instance.name) + instance.save() + + # Update the permissions + instance.permissions.clear() + instance.permissions.add(*permission_ids) + + return instance diff --git a/src/user_management/serializers/permission.py b/src/user_management/serializers/permission.py new file mode 100644 index 00000000..83e861ec --- /dev/null +++ b/src/user_management/serializers/permission.py @@ -0,0 +1,13 @@ +from django.contrib.auth.models import Permission + +from rest_framework.serializers import ModelSerializer + + +class PermissionSerializer(ModelSerializer): + """ + Serializer for the 'Permission' model + """ + + class Meta: + model = Permission + fields = '__all__' diff --git a/src/user_management/serializers/profile.py b/src/user_management/serializers/profile.py new file mode 100644 index 00000000..550ef37b --- /dev/null +++ b/src/user_management/serializers/profile.py @@ -0,0 +1,19 @@ +from rest_framework.serializers import ModelSerializer + +from user_management.models import Profile + + +class ProfileSerializer(ModelSerializer): + """ + Serializer for the 'Profile' model + """ + + class Meta: + model = Profile + fields = '__all__' + read_only_fields = ('user', 'created_at', 'updated_at') + + def to_representation(self, obj): + data = super(ProfileSerializer, self).to_representation(obj) + data['gender'] = obj.get_gender_display() + return data diff --git a/src/user_management/serializers/user.py b/src/user_management/serializers/user.py new file mode 100644 index 00000000..acf5a700 --- /dev/null +++ b/src/user_management/serializers/user.py @@ -0,0 +1,85 @@ +from django.contrib.auth.password_validation import validate_password +from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.hashers import make_password +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError + +from rest_framework import serializers + +from user_management.serializers import GroupSerializer, PermissionSerializer + + +class UserSerializer(serializers.ModelSerializer): + """ + Serializer for the 'User' model + """ + + class Meta: + model = get_user_model() + fields = '__all__' + read_only_fields = ('last_login', 'created_at', 'updated_at') + extra_kwargs = { + 'password': {'write_only': True} + } + + def validate_password(self, value: str) -> str: + try: + validate_password(value, get_user_model()) + return make_password(value) + except ValidationError as e: + raise serializers.ValidationError(e.messages) + + def to_representation(self, obj): + data = super(UserSerializer, self).to_representation(obj) + data['groups'] = GroupSerializer(many=True, instance=obj.groups).data + data['user_permissions'] = PermissionSerializer(many=True, instance=obj.user_permissions).data + return data + + +class UserPasswordChangeSerializer(serializers.Serializer): + """ + Serializer for user model password change + """ + + old_password = serializers.CharField(max_length=128, write_only=True, required=True) + new_password1 = serializers.CharField(max_length=128, write_only=True, required=True) + new_password2 = serializers.CharField(max_length=128, write_only=True, required=True) + + def validate_old_password(self, value): + user = self.context['user'] + if not user.check_password(value): + raise serializers.ValidationError( + _('Your old password was entered incorrectly. Please enter it again.') + ) + return value + + def validate_new_password1(self, value: str) -> str: + try: + validate_password(value, get_user_model()) + return value + except ValidationError as e: + raise serializers.ValidationError(e.messages) + + def validate_new_password2(self, value: str) -> str: + try: + validate_password(value, get_user_model()) + return value + except ValidationError as e: + raise serializers.ValidationError(e.messages) + + def validate(self, data): + if data['new_password1'] != data['new_password2']: + raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")}) + return data + + def save(self, **kwargs): + user = self.context['user'] + user.set_password(self.validated_data['new_password2']) + user.save() + return user + + def to_representation(self, obj): + data = super(UserPasswordChangeSerializer, self).to_representation(obj) + data['groups'] = GroupSerializer(many=True, instance=obj.groups).data + data['user_permissions'] = PermissionSerializer(many=True, instance=obj.user_permissions).data + return data diff --git a/src/user_management/signals.py b/src/user_management/signals.py new file mode 100644 index 00000000..419ed761 --- /dev/null +++ b/src/user_management/signals.py @@ -0,0 +1,11 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from user_management.models import User, Profile + + +@receiver(post_save, sender=User) +def creating_profile(sender, instance, created, raw, using, **kwargs): + """It will create a new profile instance for the user""" + if created: + Profile.objects.create(user=instance) diff --git a/src/user_management/tests/__init__.py b/src/user_management/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/user_management/tests/common_functions.py b/src/user_management/tests/common_functions.py new file mode 100644 index 00000000..0ad44645 --- /dev/null +++ b/src/user_management/tests/common_functions.py @@ -0,0 +1,37 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group + + +def sample_user(**params): + """ + Create and return a sample user object + """ + defaults = { + 'username': 'user@ticketing.sample', + 'password': '1234@Pass' + } + defaults.update(params) + return get_user_model().objects.create_user(**defaults) + + +def sample_superuser(**params): + """ + Create and return a sample superuser object + """ + defaults = { + 'username': 'superuser@ticketing.sample', + 'password': '1234@Pass' + } + defaults.update(params) + return get_user_model().objects.create_superuser(**defaults) + + +def sample_group(**params): + """ + Create and return a sample group object + """ + defaults = { + 'name': 'Sample Group', + } + defaults.update(params) + return Group.objects.create(**defaults) diff --git a/src/user_management/tests/test_group_model.py b/src/user_management/tests/test_group_model.py new file mode 100644 index 00000000..2a0d1561 --- /dev/null +++ b/src/user_management/tests/test_group_model.py @@ -0,0 +1,37 @@ +from django.contrib.auth.models import Group, Permission +from django.db.utils import IntegrityError +from django.test import TestCase + +from .common_functions import sample_group + + +class GroupModelTest(TestCase): + def test_create_group_successful(self): + """Test creating a new group is successful""" + defaults = { + 'name': 'Sample Group' + } + instance = Group.objects.create(**defaults) + self.assertEqual(instance.name, defaults['name']) + + def test_create_group_with_permissions_successful(self): + """Test creating a new group with permissions is successful""" + permissions = sorted(list(Permission.objects.values_list('id', flat=True))) + defaults = { + 'name': 'Sample Group' + } + instance = Group.objects.create(**defaults) + instance.permissions.set(permissions) + self.assertEqual(instance.name, defaults['name']) + self.assertEqual(sorted([permission.id for permission in instance.permissions.all()]), permissions) + + def test_creating_group_with_none_name(self): + """Test creating a new group with a none name""" + with self.assertRaises(IntegrityError): + Group.objects.create(name=None) + + def test_create_new_group_with_duplicate_name_unsuccessful(self): + """Test creating a new group with duplicate name is unsuccessful""" + group = sample_group() + with self.assertRaises(IntegrityError): + Group.objects.create(name=group.name) diff --git a/src/user_management/tests/test_user_model.py b/src/user_management/tests/test_user_model.py new file mode 100644 index 00000000..bd8bdaf3 --- /dev/null +++ b/src/user_management/tests/test_user_model.py @@ -0,0 +1,51 @@ +from django.core.exceptions import ValidationError +from django.contrib.auth import get_user_model +from django.test import TestCase + +from .common_functions import sample_user, sample_superuser + + +class UserModelTest(TestCase): + def test_create_user_with_username_successful(self): + """Test creating a new user with a username is successful""" + username = 'username@ticketing.sample' + password = 'password@123' + user = get_user_model().objects.create_user( + username=username, + password=password + ) + self.assertEqual(user.username, username) + self.assertFalse(user.is_superuser) + self.assertTrue(user.is_active) + self.assertTrue(user.check_password(password)) + + def test_creating_user_with_none_username(self): + """Test creating a new user with a none username""" + with self.assertRaises(ValidationError): + get_user_model().objects.create_user(username=None, password='1234@passwor') + + def test_creating_user_with_invalid_username(self): + """Test creating a new user with an invalid username""" + with self.assertRaises(ValidationError): + get_user_model().objects.create_user(username='abcd 1234', password='1234@passwor') + + def test_creating_new_superuser(self): + """Test creating a new superuser""" + user = get_user_model().objects.create_superuser( + username='superuser@ticketing.sample', + password='1234@password' + ) + self.assertTrue(user.is_superuser) + self.assertTrue(user.is_active) + + def test_create_new_user_with_duplicate_username_unsuccessful(self): + """Test creating a new user with duplicate username is unsuccessful""" + user = sample_user() + with self.assertRaises(ValidationError): + get_user_model().objects.create_user(username=user.username, password='123@pass') + + def test_create_new_superuser_with_duplicate_username_unsuccessful(self): + """Test creating a new superuser with duplicate username is unsuccessful""" + user = sample_superuser() + with self.assertRaises(ValidationError): + get_user_model().objects.create_superuser(username=user.username, password='123@pass') diff --git a/src/user_management/urls.py b/src/user_management/urls.py new file mode 100644 index 00000000..954b09ce --- /dev/null +++ b/src/user_management/urls.py @@ -0,0 +1,10 @@ +from . import views + +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register('permissions', views.PermissionViewSet, basename='permissions') +router.register('groups', views.GroupViewSet, basename='groups') +router.register('users', views.UserViewSet, basename='users') + +urlpatterns = router.urls diff --git a/src/user_management/views/__init__.py b/src/user_management/views/__init__.py new file mode 100644 index 00000000..50484b57 --- /dev/null +++ b/src/user_management/views/__init__.py @@ -0,0 +1,9 @@ +from .permission import PermissionViewSet +from .group import GroupViewSet +from .user import UserViewSet + +__all__ = [ + 'PermissionViewSet', + 'GroupViewSet', + 'UserViewSet', +] diff --git a/src/user_management/views/group.py b/src/user_management/views/group.py new file mode 100644 index 00000000..a45bd9ce --- /dev/null +++ b/src/user_management/views/group.py @@ -0,0 +1,36 @@ +from django.db.models import ProtectedError +from django.contrib.auth.models import Group + +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ModelViewSet + +from extensions.custom_permissions import CustomDjangoModelPermission + +from user_management.serializers import GroupSerializer, CreateGroupSerializer + + +class GroupViewSet(ModelViewSet): + """ + ViewSet for the 'Group' model objects + """ + serializer_class = GroupSerializer + queryset = Group.objects.all().order_by('id') + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ('name',) + search_fields = ['name'] + + def get_serializer_class(self): + return CreateGroupSerializer if self.action in ('create', 'partial_update') else GroupSerializer + + def destroy(self, request, *args, **kwargs): + users_in_this_group = self.get_object().user_set.all() + if users_in_this_group.count() > 0: + raise ProtectedError( + msg="Cannot delete this instance of model 'Group' due to protected foreign keys.", + protected_objects=users_in_this_group + ) + else: + return super().destroy(request, *args, **kwargs) diff --git a/src/user_management/views/permission.py b/src/user_management/views/permission.py new file mode 100644 index 00000000..d51f03fd --- /dev/null +++ b/src/user_management/views/permission.py @@ -0,0 +1,23 @@ +from django.contrib.auth.models import Permission + +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ReadOnlyModelViewSet + +from extensions.custom_permissions import CustomDjangoModelPermission + + +from user_management.serializers import PermissionSerializer + + +class PermissionViewSet(ReadOnlyModelViewSet): + """ + ViewSet for the 'Permission' model objects + """ + serializer_class = PermissionSerializer + queryset = Permission.objects.all().order_by('id') + permission_classes = (CustomDjangoModelPermission,) + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', 'content_type', 'codename'] + search_fields = ['name'] diff --git a/src/user_management/views/user.py b/src/user_management/views/user.py new file mode 100644 index 00000000..1dac2cfa --- /dev/null +++ b/src/user_management/views/user.py @@ -0,0 +1,95 @@ +from django.contrib.auth import get_user_model + +from django_filters.rest_framework import DjangoFilterBackend + +from rest_framework.filters import SearchFilter +from rest_framework.viewsets import ModelViewSet +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework import status + +from extensions.custom_permissions import ( + CustomDjangoModelPermission, + UnauthenticatedPost, + OwnUserPermission +) + +from user_management.serializers import ( + UserSerializer, + UserPasswordChangeSerializer, + ProfileSerializer +) + + +class UserViewSet(ModelViewSet): + """ + ViewSet for the 'User' model objects + """ + serializer_class = UserSerializer + queryset = get_user_model().objects.select_related('profile_user').all() + permission_classes = [CustomDjangoModelPermission | UnauthenticatedPost] + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['username', 'is_active', 'is_superuser', 'is_staff'] + search_fields = ['username'] + + @action( + detail=True, + methods=["put"], + url_path='change_password', + url_name='change_password', + permission_classes=[OwnUserPermission] + ) + def change_password(self, request, pk=None): + """ + This will use for change the current user password + :return: The current user information + """ + user = self.get_object() + serializer = UserPasswordChangeSerializer( + user, + data=request.data, + many=False, + context={ + "user": user + } + ) + serializer.is_valid(raise_exception=True) + user = serializer.save() + return Response(self.serializer_class(user).data, status=status.HTTP_200_OK) + + @action( + detail=True, + methods=["get"], + url_path='user_profile', + url_name='user_profile', + permission_classes=[OwnUserPermission] + ) + def user_profile(self, request, pk=None): + """ + This will use for getting the current user profile + :return: The current user profile + """ + user = self.get_object() + return Response(data=ProfileSerializer(user.profile_user).data, status=status.HTTP_200_OK) + + @action( + detail=True, + methods=["put"], + url_path='update_user_profile', + url_name='update_user_profile', + permission_classes=[OwnUserPermission] + ) + def update_user_profile(self, request, pk=None): + """ + This will use for change the current user profile + :return: The current user profile + """ + user = self.get_object() + serializer = ProfileSerializer( + user.profile_user, + data=request.data, + many=False, + ) + serializer.is_valid(raise_exception=True) + user_profile = serializer.save() + return Response(ProfileSerializer(user_profile).data, status=status.HTTP_200_OK)