├── .editorconfig
├── .gitattributes
├── .gitignore
├── .pre-commit-config.yaml
├── .pylintrc
├── .travis.yml
├── CONTRIBUTORS.txt
├── GOALS
├── LICENSE
├── Makefile
├── README.rst
├── api
├── __init__.py
├── admin.py
├── apps.py
├── constants.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20200610_1213.py
│ ├── 0003_auto_20200610_1313.py
│ ├── 0004_auto_20200610_1316.py
│ ├── 0005_auto_20200610_1648.py
│ ├── 0006_auto_20200610_1736.py
│ ├── 0007_game_uuid.py
│ ├── 0008_auto_20200610_1904.py
│ ├── 0009_auto_20200611_0905.py
│ ├── 0010_auto_20200611_1027.py
│ ├── 0011_auto_20200611_1029.py
│ ├── 0012_auto_20200611_1101.py
│ ├── 0013_board_game_uuid.py
│ ├── 0014_auto_20200611_1750.py
│ ├── 0015_remove_player_guest.py
│ ├── 0016_auto_20200614_1012.py
│ ├── 0017_auto_20200614_1133.py
│ ├── 0018_auto_20200615_1345.py
│ ├── 0019_auto_20200615_1843.py
│ ├── 0020_auto_20200615_1851.py
│ ├── 0021_piece_square.py
│ ├── 0022_auto_20200618_1134.py
│ ├── 0023_auto_20200618_1152.py
│ ├── 0024_board_castling_rights.py
│ ├── 0025_auto_20200618_1319.py
│ ├── 0026_auto_20200618_1400.py
│ ├── 0027_auto_20200618_1422.py
│ ├── 0028_auto_20200618_1422.py
│ ├── 0029_auto_20200618_1426.py
│ ├── 0030_auto_20200618_1434.py
│ ├── 0031_auto_20200618_1636.py
│ ├── 0032_board_board_fen.py
│ ├── 0033_auto_20200625_1202.py
│ ├── 0034_board_board_fen_flipped.py
│ ├── 0035_auto_20200715_1212.py
│ ├── 0036_auto_20200716_1021.py
│ ├── 0037_auto_20200716_1115.py
│ ├── 0038_remove_elo_k_factor.py
│ ├── 0039_elo_previous_rating.py
│ ├── 0040_elo_uuid.py
│ ├── 0041_auto_20200721_1015.py
│ └── __init__.py
├── models.py
├── permissions.py
├── pgn_games
│ └── fools_mate.pgn
├── serializers.py
├── services.py
├── tests
│ ├── fixtures.py
│ ├── test_api.py
│ ├── test_models.py
│ ├── test_services.py
│ └── test_token.py
└── views.py
├── chess_api_project
├── __init__.py
├── conftest.py
├── contrib
│ ├── __init__.py
│ └── sites
│ │ ├── __init__.py
│ │ └── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_alter_domain_unique.py
│ │ ├── 0003_set_site_domain_and_name.py
│ │ └── __init__.py
├── static
│ ├── css
│ │ └── project.css
│ ├── fonts
│ │ └── .gitkeep
│ ├── images
│ │ └── favicons
│ │ │ └── favicon.ico
│ ├── js
│ │ └── project.js
│ └── sass
│ │ ├── custom_bootstrap_vars.scss
│ │ └── project.scss
├── templates
│ ├── 403.html
│ ├── 404.html
│ ├── 500.html
│ ├── account
│ │ ├── account_inactive.html
│ │ ├── base.html
│ │ ├── email.html
│ │ ├── email_confirm.html
│ │ ├── login.html
│ │ ├── logout.html
│ │ ├── password_change.html
│ │ ├── password_reset.html
│ │ ├── password_reset_done.html
│ │ ├── password_reset_from_key.html
│ │ ├── password_reset_from_key_done.html
│ │ ├── password_set.html
│ │ ├── signup.html
│ │ ├── signup_closed.html
│ │ ├── verification_sent.html
│ │ └── verified_email_required.html
│ ├── base.html
│ ├── pages
│ │ ├── about.html
│ │ └── home.html
│ └── users
│ │ ├── user_detail.html
│ │ └── user_form.html
├── users
│ ├── __init__.py
│ ├── adapters.py
│ ├── admin.py
│ ├── api
│ │ ├── serializers.py
│ │ └── views.py
│ ├── apps.py
│ ├── forms.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_user_active.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── factories.py
│ │ ├── test_drf_urls.py
│ │ ├── test_drf_views.py
│ │ ├── test_forms.py
│ │ ├── test_models.py
│ │ ├── test_urls.py
│ │ └── test_views.py
│ ├── urls.py
│ └── views.py
└── utils
│ ├── __init__.py
│ └── context_processors.py
├── config
├── __init__.py
├── api_router.py
├── routing.py
├── settings
│ ├── __init__.py
│ ├── base.py
│ ├── local.py
│ ├── production.py
│ └── test.py
├── urls.py
└── wsgi.py
├── docs
├── #results.org#
├── Chess.postman_collection.json
├── Makefile
├── __init__.py
├── api_coverage
│ ├── api___init___py.html
│ ├── api_admin_py.html
│ ├── api_apps_py.html
│ ├── api_constants_py.html
│ ├── api_models_py.html
│ ├── api_permissions_py.html
│ ├── api_serializers_py.html
│ ├── api_services_py.html
│ ├── api_urls_py.html
│ ├── api_views_py.html
│ ├── coverage_html.js
│ ├── index.html
│ ├── jquery.ba-throttle-debounce.min.js
│ ├── jquery.hotkeys.js
│ ├── jquery.isonscreen.js
│ ├── jquery.min.js
│ ├── jquery.tablesorter.min.js
│ ├── keybd_closed.png
│ ├── keybd_open.png
│ ├── status.json
│ ├── stream_app___init___py.html
│ ├── stream_app_admin_py.html
│ ├── stream_app_apps_py.html
│ ├── stream_app_consumers_py.html
│ ├── stream_app_models_py.html
│ ├── stream_app_routing_py.html
│ ├── stream_app_services_py.html
│ ├── stream_app_views_py.html
│ └── style.css
├── chess_api.pdf
├── client_implementation_guide.html
├── client_implementation_guide.org
├── conf.py
├── index.rst
├── make.bat
├── pgn
│ └── Morphy.pgn
├── results.html
├── results.org
└── time_control.org
├── locale
└── README.rst
├── manage.py
├── openapi-schema.yml
├── pytest.ini
├── requirements
├── #local.txt#
├── base.txt
├── local.txt
└── production.txt
├── setup.cfg
├── stream_app
├── __init__.py
├── admin.py
├── apps.py
├── consumers.py
├── migrations
│ └── __init__.py
├── models.py
├── routing.py
├── services.py
├── tests
│ ├── fixtures.py
│ └── test_consumers.py
└── views.py
└── utility
├── install_os_dependencies.sh
├── install_python_dependencies.sh
├── requirements-bionic.apt
├── requirements-buster.apt
├── requirements-jessie.apt
├── requirements-stretch.apt
├── requirements-trusty.apt
└── requirements-xenial.apt
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{py,rst,ini}]
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [*.py]
16 | line_length = 88
17 | known_first_party = chess_api_project,config
18 | multi_line_output = 3
19 | default_section = THIRDPARTY
20 | recursive = true
21 | skip = venv/
22 | skip_glob = **/migrations/*.py
23 | include_trailing_comma = true
24 | force_grid_wrap = 0
25 | use_parentheses = true
26 |
27 | [*.{html,css,scss,json,yml}]
28 | indent_style = space
29 | indent_size = 2
30 |
31 | [*.md]
32 | trim_trailing_whitespace = false
33 |
34 | [Makefile]
35 | indent_style = tab
36 |
37 | [nginx.conf]
38 | indent_style = space
39 | indent_size = 2
40 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Python template
2 | # Byte-compiled / optimized / DLL files
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | staticfiles/
55 |
56 | # Sphinx documentation
57 | docs/_build/
58 |
59 | # PyBuilder
60 | target/
61 |
62 | # pyenv
63 | .python-version
64 |
65 |
66 |
67 | # Environments
68 | .venv
69 | venv/
70 | ENV/
71 |
72 | # Rope project settings
73 | .ropeproject
74 |
75 | # mkdocs documentation
76 | /site
77 |
78 | # mypy
79 | .mypy_cache/
80 |
81 |
82 | ### Node template
83 | # Logs
84 | logs
85 | *.log
86 | npm-debug.log*
87 | yarn-debug.log*
88 | yarn-error.log*
89 |
90 | # Runtime data
91 | pids
92 | *.pid
93 | *.seed
94 | *.pid.lock
95 |
96 | # Directory for instrumented libs generated by jscoverage/JSCover
97 | lib-cov
98 |
99 | # Coverage directory used by tools like istanbul
100 | coverage
101 |
102 | # nyc test coverage
103 | .nyc_output
104 |
105 | # Bower dependency directory (https://bower.io/)
106 | bower_components
107 |
108 | # node-waf configuration
109 | .lock-wscript
110 |
111 | # Compiled binary addons (http://nodejs.org/api/addons.html)
112 | build/Release
113 |
114 | # Dependency directories
115 | node_modules/
116 | jspm_packages/
117 |
118 | # Typescript v1 declaration files
119 | typings/
120 |
121 | # Optional npm cache directory
122 | .npm
123 |
124 | # Optional eslint cache
125 | .eslintcache
126 |
127 | # Optional REPL history
128 | .node_repl_history
129 |
130 | # Output of 'npm pack'
131 | *.tgz
132 |
133 | # Yarn Integrity file
134 | .yarn-integrity
135 |
136 |
137 | ### Linux template
138 | *~
139 |
140 | # temporary files which can be created if a process still has a handle open of a deleted file
141 | .fuse_hidden*
142 |
143 | # KDE directory preferences
144 | .directory
145 |
146 | # Linux trash folder which might appear on any partition or disk
147 | .Trash-*
148 |
149 | # .nfs files are created when an open file is removed but is still being accessed
150 | .nfs*
151 |
152 |
153 | ### VisualStudioCode template
154 | .vscode/*
155 | !.vscode/settings.json
156 | !.vscode/tasks.json
157 | !.vscode/launch.json
158 | !.vscode/extensions.json
159 |
160 |
161 |
162 |
163 |
164 | ### Windows template
165 | # Windows thumbnail cache files
166 | Thumbs.db
167 | ehthumbs.db
168 | ehthumbs_vista.db
169 |
170 | # Dump file
171 | *.stackdump
172 |
173 | # Folder config file
174 | Desktop.ini
175 |
176 | # Recycle Bin used on file shares
177 | $RECYCLE.BIN/
178 |
179 | # Windows Installer files
180 | *.cab
181 | *.msi
182 | *.msm
183 | *.msp
184 |
185 | # Windows shortcuts
186 | *.lnk
187 |
188 |
189 | ### macOS template
190 | # General
191 | *.DS_Store
192 | .AppleDouble
193 | .LSOverride
194 |
195 | # Icon must end with two \r
196 | Icon
197 |
198 | # Thumbnails
199 | ._*
200 |
201 | # Files that might appear in the root of a volume
202 | .DocumentRevisions-V100
203 | .fseventsd
204 | .Spotlight-V100
205 | .TemporaryItems
206 | .Trashes
207 | .VolumeIcon.icns
208 | .com.apple.timemachine.donotpresent
209 |
210 | # Directories potentially created on remote AFP share
211 | .AppleDB
212 | .AppleDesktop
213 | Network Trash Folder
214 | Temporary Items
215 | .apdisk
216 |
217 |
218 | ### SublimeText template
219 | # Cache files for Sublime Text
220 | *.tmlanguage.cache
221 | *.tmPreferences.cache
222 | *.stTheme.cache
223 |
224 | # Workspace files are user-specific
225 | *.sublime-workspace
226 |
227 | # Project files should be checked into the repository, unless a significant
228 | # proportion of contributors will probably not be using Sublime Text
229 | # *.sublime-project
230 |
231 | # SFTP configuration file
232 | sftp-config.json
233 |
234 | # Package control specific files
235 | Package Control.last-run
236 | Package Control.ca-list
237 | Package Control.ca-bundle
238 | Package Control.system-ca-bundle
239 | Package Control.cache/
240 | Package Control.ca-certs/
241 | Package Control.merged-ca-bundle
242 | Package Control.user-ca-bundle
243 | oscrypto-ca-bundle.crt
244 | bh_unicode_properties.cache
245 |
246 | # Sublime-github package stores a github token in this file
247 | # https://packagecontrol.io/packages/sublime-github
248 | GitHub.sublime-settings
249 |
250 |
251 | ### Vim template
252 | # Swap
253 | [._]*.s[a-v][a-z]
254 | [._]*.sw[a-p]
255 | [._]s[a-v][a-z]
256 | [._]sw[a-p]
257 |
258 | # Session
259 | Session.vim
260 |
261 | # Temporary
262 | .netrwhist
263 |
264 | # Auto-generated tag files
265 | tags
266 |
267 |
268 | ### VirtualEnv template
269 | # Virtualenv
270 | [Bb]in
271 | [Ii]nclude
272 | [Ll]ib
273 | [Ll]ib64
274 | [Ss]cripts
275 | pyvenv.cfg
276 | pip-selfcheck.json
277 | .env
278 |
279 |
280 | ### Project template
281 |
282 | chess_api_project/media/
283 |
284 | .pytest_cache/
285 |
286 |
287 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | exclude: 'docs|node_modules|migrations|.git|.tox'
2 | default_stages: [commit]
3 | fail_fast: true
4 |
5 | repos:
6 | - repo: https://github.com/pre-commit/pre-commit-hooks
7 | rev: master
8 | hooks:
9 | - id: trailing-whitespace
10 | - id: end-of-file-fixer
11 | - id: check-yaml
12 |
13 | - repo: https://github.com/psf/black
14 | rev: 19.10b0
15 | hooks:
16 | - id: black
17 |
18 | - repo: https://gitlab.com/pycqa/flake8
19 | rev: 3.8.1
20 | hooks:
21 | - id: flake8
22 | args: ['--config=setup.cfg']
23 | additional_dependencies: [flake8-isort]
24 |
25 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | load-plugins=pylint_django
3 |
4 | [FORMAT]
5 | max-line-length=120
6 |
7 | [MESSAGES CONTROL]
8 | disable=missing-docstring,invalid-name
9 |
10 | [DESIGN]
11 | max-parents=13
12 |
13 | [TYPECHECK]
14 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.8"
4 | install:
5 | - pip install -r requirements/base.txt
6 | - pip install -r requirements/local.txt
7 | services:
8 | - postgresql
9 | - redis-server
10 | before_script:
11 | psql -c "create database chess_api_project;" -U postgres
12 | script:
13 | - make test
14 |
15 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.txt:
--------------------------------------------------------------------------------
1 | Mikel Losada
2 |
--------------------------------------------------------------------------------
/GOALS:
--------------------------------------------------------------------------------
1 | v0.1
2 | - Players can join games and make legal moves
3 | v0.2
4 | - Checks
5 | - Attacks
6 | - Game end detection
7 | v0.9
8 | - Repetition detection with half-move clock
9 | - Claims
10 | - Results
11 | - PGN parsing and writing
12 | - ELO
13 | - Code refactor
14 | - API Documentation and Postman collection
15 | v1.0
16 | - Player vs Stockfish mode
17 |
18 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | tidyup:
2 | autoflake --recursive . --remove-all-unused-imports --remove-unused-variables --in-place
3 | isort **/*.py
4 | black .
5 | find . -name "*~" -type f -delete
6 |
7 | test:
8 | pytest api/tests stream_app/tests --cov=api --cov stream_app
9 |
10 | testpudb:
11 | pytest api/tests/ --pudb
12 |
13 | testreport:
14 | pytest --cov-report html:docs/api_coverage --cov=api --cov stream_app api/tests stream_app/tests
15 |
16 | coveralls:
17 | coverage run --source api,stream_app -m pytest api/tests stream_app/tests
18 | coveralls
19 |
20 | erdiagram:
21 | eralchemy -i postgres:///chess_api_project -o docs/chess_api.pdf
22 |
23 | openemacs:
24 | emacs api api/views.py &
25 |
26 | makemigrations:
27 | ./manage.py makemigrations
28 |
29 | migrate:
30 | ./manage.py migrate
31 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Capablanca Chess API
2 | ====================
3 |
4 | Django Chess API powered by python-chess
5 |
6 | .. image:: https://api.travis-ci.com/WorkShoft/capablanca-api.svg?branch=master&status=passed
7 | :target: https://travis-ci.com/github/WorkShoft/capablanca-api
8 | :alt: TravisCI build status
9 | .. image:: https://coveralls.io/repos/github/WorkShoft/capablanca-api/badge.svg?branch=master
10 | :target: https://coveralls.io/github/WorkShoft/capablanca-api?branch=master
11 | :alt: Coverage status
12 | .. image:: https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg
13 | :target: https://github.com/pydanny/cookiecutter-django/
14 | :alt: Built with Cookiecutter Django
15 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
16 | :target: https://github.com/ambv/black
17 | :alt: Black code style
18 |
19 |
20 | :License: Apache Software License 2.0
21 |
22 |
23 | Settings
24 | --------
25 |
26 | Moved to settings_.
27 |
28 | .. _settings: http://cookiecutter-django.readthedocs.io/en/latest/settings.html
29 |
30 | Basic Commands
31 | --------------
32 |
33 | Setting Up Your Users
34 | ^^^^^^^^^^^^^^^^^^^^^
35 |
36 | * To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go.
37 |
38 | * To create an **superuser account**, use this command::
39 |
40 | $ python manage.py createsuperuser
41 |
42 | For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users.
43 |
44 | Type checks
45 | ^^^^^^^^^^^
46 |
47 | Running type checks with mypy:
48 |
49 | ::
50 |
51 | $ mypy chess_api_project
52 |
53 | Test coverage
54 | ^^^^^^^^^^^^^
55 |
56 | To run the tests, check your test coverage, and generate an HTML coverage report::
57 |
58 | $ coverage run -m pytest
59 | $ coverage html
60 | $ open htmlcov/index.html
61 |
62 | Running tests with py.test
63 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
64 |
65 | ::
66 |
67 | $ pytest
68 |
69 | Live reloading and Sass CSS compilation
70 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
71 |
72 | Moved to `Live reloading and SASS compilation`_.
73 |
74 | .. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html
75 |
76 |
77 |
78 |
79 |
80 | Deployment
81 | ----------
82 |
83 | The following details how to deploy this application.
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/api/__init__.py
--------------------------------------------------------------------------------
/api/admin.py:
--------------------------------------------------------------------------------
1 | # Register your models here.
2 |
--------------------------------------------------------------------------------
/api/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ApiConfig(AppConfig):
5 | name = "api"
6 |
--------------------------------------------------------------------------------
/api/constants.py:
--------------------------------------------------------------------------------
1 | K_FACTOR = 32
2 |
--------------------------------------------------------------------------------
/api/migrations/0002_auto_20200610_1213.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 10:13
2 |
3 | import django.db.models.deletion
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ("api", "0001_initial"),
13 | ]
14 |
15 | operations = [
16 | migrations.AlterField(
17 | model_name="player",
18 | name="user",
19 | field=models.ForeignKey(
20 | null=True,
21 | on_delete=django.db.models.deletion.CASCADE,
22 | to=settings.AUTH_USER_MODEL,
23 | ),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/api/migrations/0003_auto_20200610_1313.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 11:13
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0002_auto_20200610_1213"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="game",
15 | name="end_timestamp",
16 | field=models.DateTimeField(null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/api/migrations/0004_auto_20200610_1316.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 11:16
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0003_auto_20200610_1313"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board", name="layout", field=models.TextField(),
15 | ),
16 | ]
17 |
--------------------------------------------------------------------------------
/api/migrations/0005_auto_20200610_1648.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 14:48
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("api", "0004_auto_20200610_1316"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="player",
16 | name="elo",
17 | field=models.ForeignKey(
18 | null=True, on_delete=django.db.models.deletion.CASCADE, to="api.Elo"
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/api/migrations/0006_auto_20200610_1736.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 15:36
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0005_auto_20200610_1648"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board",
15 | name="layout",
16 | field=models.TextField(
17 | default="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
18 | ),
19 | ),
20 | migrations.AlterField(
21 | model_name="result",
22 | name="description",
23 | field=models.TextField(
24 | choices=[
25 | ("Scheduled", "Scheduled"),
26 | ("Postponed", "Postponed"),
27 | ("Finished (no moves)", "Finished without any moves played"),
28 | ("In progress", "In progress"),
29 | ("Adjourned", "Adjourned"),
30 | (
31 | "Finished (basic rules)",
32 | "Finished according to the Basic Rules of Play",
33 | ),
34 | ("Finished (clock)", "Finished by the clock"),
35 | ("Draw", "Draw"),
36 | (
37 | "Finished (breach)",
38 | "Finished because of a breach of rules of one player",
39 | ),
40 | (
41 | "Finished (compliance)",
42 | "Finished because both players persistently refuse to comply with the laws of chess",
43 | ),
44 | ("TBD", "To be decided"),
45 | ("Abandoned", "Abandoned"),
46 | ("Unknown", "Unknown"),
47 | ],
48 | default="In progress",
49 | ),
50 | ),
51 | ]
52 |
--------------------------------------------------------------------------------
/api/migrations/0007_game_uuid.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 16:25
2 |
3 | import uuid
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ("api", "0006_auto_20200610_1736"),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name="game", name="uuid", field=models.UUIDField(default=uuid.uuid4),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/api/migrations/0008_auto_20200610_1904.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 17:04
2 |
3 | import uuid
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ("api", "0007_game_uuid"),
12 | ]
13 |
14 | operations = [
15 | migrations.RemoveField(model_name="game", name="id",),
16 | migrations.AlterField(
17 | model_name="game",
18 | name="uuid",
19 | field=models.UUIDField(
20 | default=uuid.uuid4, primary_key=True, serialize=False
21 | ),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/api/migrations/0009_auto_20200611_0905.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 07:05
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0008_auto_20200610_1904"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="elo", name="k_factor", field=models.IntegerField(default=32),
15 | ),
16 | migrations.AlterField(
17 | model_name="elo", name="rating", field=models.IntegerField(default=1200),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/api/migrations/0010_auto_20200611_1027.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 08:27
2 |
3 | import django.db.models.deletion
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ("api", "0009_auto_20200611_0905"),
13 | ]
14 |
15 | operations = [
16 | migrations.RemoveField(model_name="player", name="elo",),
17 | migrations.AddField(
18 | model_name="elo",
19 | name="player",
20 | field=models.ForeignKey(
21 | null=True,
22 | on_delete=django.db.models.deletion.CASCADE,
23 | to=settings.AUTH_USER_MODEL,
24 | ),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/api/migrations/0011_auto_20200611_1029.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 08:29
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("api", "0010_auto_20200611_1027"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="elo",
16 | name="player",
17 | field=models.ForeignKey(
18 | null=True, on_delete=django.db.models.deletion.CASCADE, to="api.Player"
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/api/migrations/0012_auto_20200611_1101.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 09:01
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0011_auto_20200611_1029"),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(model_name="board", old_name="layout", new_name="fen",),
14 | ]
15 |
--------------------------------------------------------------------------------
/api/migrations/0013_board_game_uuid.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 09:32
2 |
3 | import uuid
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ("api", "0012_auto_20200611_1101"),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name="board",
17 | name="game_uuid",
18 | field=models.UUIDField(default=uuid.uuid4),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/api/migrations/0014_auto_20200611_1750.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 15:50
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("api", "0013_board_game_uuid"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="game",
16 | name="board",
17 | field=models.OneToOneField(
18 | on_delete=django.db.models.deletion.CASCADE, to="api.Board"
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/api/migrations/0015_remove_player_guest.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-11 16:31
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0014_auto_20200611_1750"),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(model_name="player", name="guest",),
14 | ]
15 |
--------------------------------------------------------------------------------
/api/migrations/0016_auto_20200614_1012.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-14 08:12
2 |
3 | import django.db.models.deletion
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ("api", "0015_remove_player_guest"),
13 | ]
14 |
15 | operations = [
16 | migrations.AlterField(
17 | model_name="claimitem",
18 | name="player",
19 | field=models.ForeignKey(
20 | on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
21 | ),
22 | ),
23 | migrations.AlterField(
24 | model_name="elo",
25 | name="player",
26 | field=models.ForeignKey(
27 | null=True,
28 | on_delete=django.db.models.deletion.CASCADE,
29 | to=settings.AUTH_USER_MODEL,
30 | ),
31 | ),
32 | migrations.AlterField(
33 | model_name="game",
34 | name="blacks_player",
35 | field=models.ForeignKey(
36 | null=True,
37 | on_delete=django.db.models.deletion.CASCADE,
38 | related_name="blacks_player",
39 | to=settings.AUTH_USER_MODEL,
40 | ),
41 | ),
42 | migrations.AlterField(
43 | model_name="game",
44 | name="whites_player",
45 | field=models.ForeignKey(
46 | null=True,
47 | on_delete=django.db.models.deletion.CASCADE,
48 | related_name="whites_player",
49 | to=settings.AUTH_USER_MODEL,
50 | ),
51 | ),
52 | migrations.DeleteModel(name="Player",),
53 | ]
54 |
--------------------------------------------------------------------------------
/api/migrations/0017_auto_20200614_1133.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-14 09:33
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0016_auto_20200614_1012"),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name="game", old_name="start_timestamp", new_name="created_at",
15 | ),
16 | migrations.RenameField(
17 | model_name="game", old_name="end_timestamp", new_name="finished_at",
18 | ),
19 | migrations.AddField(
20 | model_name="game", name="started_at", field=models.DateTimeField(null=True),
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/api/migrations/0018_auto_20200615_1345.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-15 11:45
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("api", "0017_auto_20200614_1133"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="game",
16 | name="result",
17 | field=models.OneToOneField(
18 | on_delete=django.db.models.deletion.CASCADE, to="api.Result"
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/api/migrations/0019_auto_20200615_1843.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-15 16:43
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0018_auto_20200615_1345"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="result",
15 | name="description",
16 | field=models.TextField(
17 | choices=[
18 | ("Abandoned", "Abandoned game."),
19 | ("Adjudication", "Result due to third party adjudication process."),
20 | (
21 | "Death",
22 | "One or both players died during the course of this game.",
23 | ),
24 | ("Emergency", "Game concluded due to unforeseen circumstances."),
25 | ("Normal", "Game terminated in a normal fashion."),
26 | (
27 | "Rules infraction",
28 | "Administrative forfeit due to losing player's failure to observe either the Laws of Chess or the event regulations.",
29 | ),
30 | (
31 | "Time forfeit",
32 | "Loss due to losing player's failure to meet time control requirements.",
33 | ),
34 | ("Unterminated", "Game not terminated."),
35 | ],
36 | default="Unterminated",
37 | ),
38 | ),
39 | ]
40 |
--------------------------------------------------------------------------------
/api/migrations/0020_auto_20200615_1851.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-15 16:51
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0019_auto_20200615_1843"),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name="result", old_name="description", new_name="termination",
15 | ),
16 | migrations.AddField(
17 | model_name="result",
18 | name="result",
19 | field=models.TextField(
20 | choices=[
21 | ("White wins", "White wins"),
22 | ("Black wins", "Black wins"),
23 | ("Draw", "Drawn game"),
24 | (
25 | "In progress",
26 | "Game still in progress, game abandoned, or result otherwise unknown",
27 | ),
28 | ],
29 | default="In progress",
30 | ),
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/api/migrations/0021_piece_square.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-17 17:00
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0020_auto_20200615_1851"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="piece",
15 | name="square",
16 | field=models.CharField(
17 | choices=[
18 | (0, "A1"),
19 | (1, "B1"),
20 | (2, "C1"),
21 | (3, "D1"),
22 | (4, "E1"),
23 | (5, "F1"),
24 | (6, "G1"),
25 | (7, "H1"),
26 | (8, "A2"),
27 | (9, "B2"),
28 | (10, "C2"),
29 | (11, "D2"),
30 | (12, "E2"),
31 | (13, "F2"),
32 | (14, "G2"),
33 | (15, "H2"),
34 | (16, "A3"),
35 | (17, "B3"),
36 | (18, "C3"),
37 | (19, "D3"),
38 | (20, "E3"),
39 | (21, "F3"),
40 | (22, "G3"),
41 | (23, "H3"),
42 | (24, "A4"),
43 | (25, "B4"),
44 | (26, "C4"),
45 | (27, "D4"),
46 | (28, "E4"),
47 | (29, "F4"),
48 | (30, "G4"),
49 | (31, "H4"),
50 | (32, "A5"),
51 | (33, "B5"),
52 | (34, "C5"),
53 | (35, "D5"),
54 | (36, "E5"),
55 | (37, "F5"),
56 | (38, "G5"),
57 | (39, "H5"),
58 | (40, "A6"),
59 | (41, "B6"),
60 | (42, "C6"),
61 | (43, "D6"),
62 | (44, "E6"),
63 | (45, "F6"),
64 | (46, "G6"),
65 | (47, "H6"),
66 | (48, "A7"),
67 | (49, "B7"),
68 | (50, "C7"),
69 | (51, "D7"),
70 | (52, "E7"),
71 | (53, "F7"),
72 | (54, "G7"),
73 | (55, "H7"),
74 | (56, "A8"),
75 | (57, "B8"),
76 | (58, "C8"),
77 | (59, "D8"),
78 | (60, "E8"),
79 | (61, "F8"),
80 | (62, "G8"),
81 | (63, "H8"),
82 | ],
83 | max_length=2,
84 | null=True,
85 | ),
86 | ),
87 | ]
88 |
--------------------------------------------------------------------------------
/api/migrations/0022_auto_20200618_1134.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 09:34
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0021_piece_square"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="board",
15 | name="castling_rights",
16 | field=models.TextField(null=True),
17 | ),
18 | migrations.AddField(
19 | model_name="board",
20 | name="ep_square",
21 | field=models.CharField(max_length=2, null=True),
22 | ),
23 | migrations.AddField(
24 | model_name="board",
25 | name="fullmove_number",
26 | field=models.IntegerField(default=1),
27 | ),
28 | migrations.AddField(
29 | model_name="board",
30 | name="halfmove_clock",
31 | field=models.IntegerField(default=0),
32 | ),
33 | migrations.AddField(
34 | model_name="board", name="turn", field=models.IntegerField(null=True),
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/api/migrations/0023_auto_20200618_1152.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 09:52
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0022_auto_20200618_1134"),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name="board", old_name="castling_rights", new_name="castling_xfen",
15 | ),
16 | ]
17 |
--------------------------------------------------------------------------------
/api/migrations/0024_board_castling_rights.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 10:50
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0023_auto_20200618_1152"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="board",
15 | name="castling_rights",
16 | field=models.TextField(null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/api/migrations/0025_auto_20200618_1319.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 11:19
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("api", "0024_board_castling_rights"),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(model_name="move", name="piece",),
15 | migrations.AddField(
16 | model_name="move",
17 | name="board",
18 | field=models.ForeignKey(
19 | null=True, on_delete=django.db.models.deletion.CASCADE, to="api.Board"
20 | ),
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/api/migrations/0026_auto_20200618_1400.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 12:00
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0025_auto_20200618_1319"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name="move", options={"ordering": ["created_at"]},
15 | ),
16 | migrations.RenameField(
17 | model_name="move", old_name="timestamp", new_name="created_at",
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/api/migrations/0027_auto_20200618_1422.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 12:22
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0026_auto_20200618_1400"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board", name="ep_square", field=models.IntegerField(null=True),
15 | ),
16 | ]
17 |
--------------------------------------------------------------------------------
/api/migrations/0028_auto_20200618_1422.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 12:22
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0027_auto_20200618_1422"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board",
15 | name="castling_rights",
16 | field=models.IntegerField(null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/api/migrations/0029_auto_20200618_1426.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 12:26
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0028_auto_20200618_1422"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board", name="ep_square", field=models.IntegerField(default=0),
15 | ),
16 | ]
17 |
--------------------------------------------------------------------------------
/api/migrations/0030_auto_20200618_1434.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 12:34
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0029_auto_20200618_1426"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board",
15 | name="castling_rights",
16 | field=models.TextField(null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name="board", name="ep_square", field=models.IntegerField(null=True),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/api/migrations/0031_auto_20200618_1636.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-18 14:36
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0030_auto_20200618_1434"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board", name="turn", field=models.BooleanField(default=True),
15 | ),
16 | ]
17 |
--------------------------------------------------------------------------------
/api/migrations/0032_board_board_fen.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-23 14:34
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0031_auto_20200618_1636"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="board",
15 | name="board_fen",
16 | field=models.TextField(
17 | default="rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR"
18 | ),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/api/migrations/0033_auto_20200625_1202.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-06-25 10:02
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0032_board_board_fen"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board",
15 | name="board_fen",
16 | field=models.TextField(
17 | default="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
18 | ),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/api/migrations/0034_board_board_fen_flipped.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-15 06:58
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0033_auto_20200625_1202"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="board",
15 | name="board_fen_flipped",
16 | field=models.TextField(
17 | default="RNBQKBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbqkbnr"
18 | ),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/api/migrations/0035_auto_20200715_1212.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-15 10:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0034_board_board_fen_flipped"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="board",
15 | name="board_fen_flipped",
16 | field=models.TextField(
17 | default="RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr"
18 | ),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/api/migrations/0036_auto_20200716_1021.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-16 08:21
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ("api", "0035_auto_20200715_1212"),
13 | ]
14 |
15 | operations = [
16 | migrations.AlterField(
17 | model_name="elo",
18 | name="player",
19 | field=models.OneToOneField(
20 | null=True,
21 | on_delete=django.db.models.deletion.CASCADE,
22 | related_name="elo",
23 | to=settings.AUTH_USER_MODEL,
24 | ),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/api/migrations/0037_auto_20200716_1115.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-16 09:15
2 |
3 | import annoying.fields
4 | from django.conf import settings
5 | from django.db import migrations
6 | import django.db.models.deletion
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ("api", "0036_auto_20200716_1021"),
14 | ]
15 |
16 | operations = [
17 | migrations.AlterField(
18 | model_name="elo",
19 | name="player",
20 | field=annoying.fields.AutoOneToOneField(
21 | null=True,
22 | on_delete=django.db.models.deletion.CASCADE,
23 | related_name="elo",
24 | to=settings.AUTH_USER_MODEL,
25 | ),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/api/migrations/0038_remove_elo_k_factor.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-16 13:30
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0037_auto_20200716_1115"),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(model_name="elo", name="k_factor",),
14 | ]
15 |
--------------------------------------------------------------------------------
/api/migrations/0039_elo_previous_rating.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-16 15:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("api", "0038_remove_elo_k_factor"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="elo",
15 | name="previous_rating",
16 | field=models.IntegerField(default=1200),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/api/migrations/0040_elo_uuid.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-17 07:34
2 |
3 | from django.db import migrations, models
4 | import uuid
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("api", "0039_elo_previous_rating"),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name="elo", name="uuid", field=models.UUIDField(default=uuid.uuid4),
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/api/migrations/0041_auto_20200721_1015.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.7 on 2020-07-21 08:15
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ("api", "0040_elo_uuid"),
13 | ]
14 |
15 | operations = [
16 | migrations.RemoveField(model_name="game", name="blacks_player",),
17 | migrations.RemoveField(model_name="game", name="whites_player",),
18 | migrations.AddField(
19 | model_name="game",
20 | name="black_player",
21 | field=models.ForeignKey(
22 | null=True,
23 | on_delete=django.db.models.deletion.CASCADE,
24 | related_name="black_player",
25 | to=settings.AUTH_USER_MODEL,
26 | ),
27 | ),
28 | migrations.AddField(
29 | model_name="game",
30 | name="white_player",
31 | field=models.ForeignKey(
32 | null=True,
33 | on_delete=django.db.models.deletion.CASCADE,
34 | related_name="white_player",
35 | to=settings.AUTH_USER_MODEL,
36 | ),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/api/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/api/migrations/__init__.py
--------------------------------------------------------------------------------
/api/permissions.py:
--------------------------------------------------------------------------------
1 | """
2 | API permissions for checks such as piece ownership
3 | """
4 |
5 | import chess
6 | from django.contrib.auth import get_user_model
7 | from rest_framework import permissions
8 |
9 | User = get_user_model()
10 |
11 |
12 | class GamePermission(permissions.BasePermission):
13 | message = "That move is not valid or allowed"
14 |
15 | def has_object_permission(self, request, view, obj):
16 | """
17 | Allow players to see each other's pieces
18 | Only allow the owner of a piece to move it (i.e. if you play as White you can only move white pieces)
19 |
20 | python-chess Piece.color is True for white pieces, False for black ones
21 | obj: Game instance
22 | """
23 |
24 | if request.method in permissions.SAFE_METHODS:
25 | return True
26 |
27 | if view.action == "move":
28 | from_square = request.data.get("from_square")
29 | square = getattr(chess, from_square.upper())
30 | board = chess.Board(obj.board.fen)
31 | user = User.objects.get(username=request.user)
32 |
33 | if user in (obj.white_player, obj.black_player):
34 | if obj.white_player == obj.black_player:
35 | return True
36 |
37 | player_color = "white" if user == obj.white_player else "black"
38 |
39 | else:
40 | return False
41 |
42 | piece_color = "white" if board.color_at(square) else "black"
43 |
44 | return player_color == piece_color
45 |
46 | return True
47 |
--------------------------------------------------------------------------------
/api/pgn_games/fools_mate.pgn:
--------------------------------------------------------------------------------
1 | [Event "UKR-ch U08"]
2 | [Site "Evpatoria"]
3 | [Date "2007.05.12"]
4 | [EventDate "2007.05.09"]
5 | [Round "4"]
6 | [Result "0-1"]
7 | [White "Ivan Skrypin"]
8 | [Black "Alexey Glebov"]
9 | [ECO "A02"]
10 | [WhiteElo "?"]
11 | [BlackElo "?"]
12 | [PlyCount "4"]
13 |
14 | 1. f4 e6 2. g4 Qh4# 0-1
--------------------------------------------------------------------------------
/api/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import serializers
3 | from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
4 |
5 | from . import services
6 | from .models import Board, Elo, Game, Result
7 |
8 |
9 | class EloSerializer(serializers.ModelSerializer):
10 | class Meta:
11 | model = Elo
12 | fields = (
13 | "rating",
14 | "previous_rating",
15 | "wins",
16 | "losses",
17 | "draws",
18 | "uuid",
19 | )
20 |
21 |
22 | class UserEloSerializer(serializers.ModelSerializer):
23 | elo = EloSerializer()
24 |
25 | class Meta:
26 | model = get_user_model()
27 | fields = (
28 | "username",
29 | "active",
30 | "elo",
31 | )
32 |
33 |
34 | class BoardSerializer(serializers.ModelSerializer):
35 | class Meta:
36 | model = Board
37 | fields = (
38 | "fen",
39 | "board_fen",
40 | "board_fen_flipped",
41 | "updated_at",
42 | "game_uuid",
43 | )
44 |
45 |
46 | class ResultSerializer(serializers.ModelSerializer):
47 | class Meta:
48 | model = Result
49 | fields = (
50 | "result",
51 | "termination",
52 | )
53 |
54 |
55 | class GameSerializer(serializers.ModelSerializer):
56 | black_player = UserEloSerializer(required=False)
57 | white_player = UserEloSerializer(required=False)
58 | board = BoardSerializer(required=False)
59 | result = ResultSerializer(required=False)
60 |
61 | class Meta:
62 | model = Game
63 | fields = (
64 | "uuid",
65 | "black_player",
66 | "white_player",
67 | "created_at",
68 | "started_at",
69 | "finished_at",
70 | "board",
71 | "result",
72 | )
73 |
74 | def create(self, validated_data):
75 | result_data = validated_data.pop("result", {})
76 | board_data = validated_data.pop("board", {})
77 |
78 | preferred_color = self.context["request"].data.get("preferred_color", "random")
79 |
80 | auth_username = self.context["request"].user
81 |
82 | game = services.create_game(
83 | result_data=result_data, board_data=board_data, **validated_data
84 | )
85 |
86 | services.assign_color(game, auth_username, preferred_color)
87 |
88 | return game
89 |
90 |
91 | class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
92 | def validate(self, attrs):
93 | data = super().validate(attrs)
94 |
95 | data["name"] = self.user.username
96 | data["active"] = self.user.active
97 |
98 | return data
99 |
--------------------------------------------------------------------------------
/api/tests/fixtures.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import chess
3 |
4 | from api import services
5 | from api.models import Board, Game, Result
6 |
7 |
8 | JWT_TOKEN = {
9 | "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTU5NTIzMjMxMCwianRpIjoiMTViM2ZiZGNhODJlNDBiMDkyNTBiYzA5ZTlkODQwMmYiLCJ1c2VyX2lkIjoxfQ.3UZIhcS4X14zb9V7wRnf0G3TJ1f7G6UMijThokvOD_M",
10 | "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTk1MTQ1OTIyLCJqdGkiOiI1ODBjNmM3ZDcyNTk0NmJmOWJiNGY1YmMyMzcyMjY0MiIsInVzZXJfaWQiOjF9.44RKjIbKXmWqjzJA0TtpbnBTt3-3tAMxUP1EZDMJais",
11 | }
12 |
13 | CHESS_BOARD = chess.Board()
14 | BOARD_INSTANCE = Board.from_fen(CHESS_BOARD.fen())
15 |
16 |
17 | @pytest.fixture
18 | def users():
19 | user_one = services.User.objects.create(
20 | username="walterwhite",
21 | name="Walter Hartwell White",
22 | email="walter@graymatter.tech",
23 | password="albuquerque1992",
24 | is_active=True,
25 | )
26 |
27 | user_two = services.User.objects.create(
28 | username="jessepinkman",
29 | name="Jesse Bruce Pinkman",
30 | email="jesseyo@outlook.com",
31 | password="therealog",
32 | is_active=True,
33 | )
34 |
35 | return user_one, user_two
36 |
37 |
38 | @pytest.fixture
39 | def game_instance(users):
40 | player, opponent = users
41 |
42 | return Game(
43 | board=BOARD_INSTANCE,
44 | white_player=player,
45 | black_player=opponent,
46 | result=Result(result=Result.BLACK_WINS, termination=Result.NORMAL),
47 | )
48 |
--------------------------------------------------------------------------------
/api/tests/test_models.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import chess
3 |
4 | from api.models import Board
5 |
6 |
7 | def test_board_from_fen():
8 | chess_board = chess.Board()
9 | fen = chess_board.fen()
10 | board = Board.from_fen(fen)
11 |
12 | assert board.ep_square is None
13 | assert board.halfmove_clock == 0
14 | assert board.fullmove_number == 1
15 | assert board.castling_xfen == "KQkq"
16 |
17 |
18 | @pytest.mark.django_db
19 | def test_get_empty_move_stack():
20 | board = Board()
21 |
22 | assert board.move_stack == []
23 |
24 |
25 | @pytest.mark.django_db
26 | def test_board_model_string():
27 | board = Board()
28 | assert str(board) == board.fen
29 |
--------------------------------------------------------------------------------
/api/tests/test_services.py:
--------------------------------------------------------------------------------
1 | from unittest import mock
2 |
3 | import chess
4 | import chess.pgn
5 | import pytest
6 | from api import services
7 | from api.models import Board, Game, Result
8 | from fixtures import users, game_instance
9 |
10 |
11 | @pytest.mark.django_db
12 | def test_chess_board_from_uuid():
13 | """
14 | Test a python-chess Board can be fully recovered from a Board model instance
15 | by providing the game uuid
16 | """
17 |
18 | # Create board and make two moves
19 | chess_board = chess.Board()
20 | board_instance = Board.from_fen(chess_board.fen())
21 | board_instance.save()
22 |
23 | first_move = "e2e4"
24 | second_move = "e7e5"
25 |
26 | services.move_piece(
27 | board_instance, first_move[:2], first_move[2:], chess_board=chess_board
28 | )
29 | services.move_piece(
30 | board_instance, second_move[:2], second_move[2:], chess_board=chess_board
31 | )
32 |
33 | new_chess_board = services.chess_board_from_uuid(board_instance.game_uuid)
34 |
35 | assert chess_board.ep_square == int(new_chess_board.ep_square)
36 | assert chess_board.castling_rights == int(new_chess_board.castling_rights)
37 | assert chess_board.turn == new_chess_board.turn
38 | assert chess_board.fullmove_number == new_chess_board.fullmove_number
39 | assert chess_board.halfmove_clock == new_chess_board.halfmove_clock
40 | assert chess_board.move_stack == new_chess_board.move_stack
41 | assert chess_board.fen() == new_chess_board.fen()
42 | assert chess_board.board_fen() == new_chess_board.board_fen()
43 |
44 |
45 | @pytest.mark.django_db
46 | def test_create_board_from_pgn():
47 | board_instance, chess_board = services.create_board_from_pgn(
48 | "api/pgn_games/fools_mate.pgn", starting_at=0
49 | )
50 |
51 | assert board_instance.fen == chess.STARTING_FEN
52 | assert chess_board.fen() == chess.STARTING_FEN
53 |
54 |
55 | @pytest.mark.django_db
56 | def test_create_board_from_pgn_starting_at():
57 | board_instance, chess_board = services.create_board_from_pgn(
58 | "api/pgn_games/fools_mate.pgn", starting_at=4
59 | )
60 |
61 | assert board_instance.fullmove_number == 3
62 | assert chess_board.is_checkmate()
63 |
64 |
65 | @pytest.mark.django_db
66 | def test_get_expected_score():
67 | player_rating = 1200
68 | opponent_rating = 1300
69 |
70 | expected_player_score = services._get_expected_score(player_rating, opponent_rating)
71 |
72 | expected_opponent_score = services._get_expected_score(
73 | opponent_rating, player_rating
74 | )
75 |
76 | assert expected_player_score == 0.36
77 | assert expected_opponent_score == 0.64
78 |
79 |
80 | @pytest.mark.django_db
81 | @mock.patch("api.services.K_FACTOR", 32)
82 | def test_get_rating(users):
83 | player, opponent = users
84 |
85 | rating = services.get_rating(1, player.elo.rating, opponent.elo.rating)
86 |
87 | assert rating == 1216
88 |
89 |
90 | @pytest.mark.django_db
91 | @mock.patch("api.services.K_FACTOR", 32)
92 | def test_update_elo_rating(users):
93 | player, opponent = users
94 |
95 | new_rating = services.update_elo_rating(
96 | player_score=1, player=player, opponent=opponent
97 | )
98 |
99 | assert new_rating == 1216
100 | assert player.elo.rating == 1216
101 |
102 |
103 | @pytest.mark.django_db
104 | def test_update_elo(users):
105 | """
106 | Test ELO is recalculated correctly
107 | The default result is Result(result=Result.WHITE_WINS, termination=Result.NORMAL)
108 | """
109 |
110 | player, opponent = users
111 |
112 | board_instance, _ = services.create_board_from_pgn(
113 | "api/pgn_games/fools_mate.pgn", starting_at=4
114 | )
115 |
116 | game = Game(
117 | board=board_instance,
118 | white_player=player,
119 | black_player=opponent,
120 | result=Result(result=Result.BLACK_WINS, termination=Result.NORMAL),
121 | )
122 |
123 | services.update_elo(game)
124 |
125 | assert player.elo.wins == 0
126 | assert opponent.elo.wins == 1
127 |
128 | assert player.elo.rating == 1184
129 | assert opponent.elo.rating == 1216
130 |
131 |
132 | @pytest.mark.django_db
133 | def test_update_elo_draw(users, game_instance):
134 | """
135 | game.result = Result(result=Result.DRAW, termination=Result.NORMAL)
136 | """
137 |
138 | player, opponent = users
139 | game = game_instance
140 |
141 | game.result = Result(result=Result.DRAW)
142 | game.result.save()
143 |
144 | services.update_elo(game)
145 |
146 | assert player.elo.draws == 1
147 | assert opponent.elo.draws == 1
148 |
149 |
150 | @pytest.mark.django_db
151 | def test_update_elo_white_wins(users, game_instance):
152 | """
153 | game.result = Result(result=Result.WHITE_WINS, termination=Result.NORMAL)
154 | """
155 |
156 | player, opponent = users
157 | game = game_instance
158 |
159 | game.result = Result(result=Result.WHITE_WINS)
160 | game.result.save()
161 |
162 | services.update_elo(game)
163 |
164 | assert player.elo.wins == 1
165 | assert opponent.elo.wins == 0
166 |
--------------------------------------------------------------------------------
/api/tests/test_token.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
3 |
4 | from api.serializers import CustomTokenObtainPairSerializer
5 |
6 | from unittest.mock import MagicMock
7 |
8 | from fixtures import JWT_TOKEN, users
9 |
10 |
11 | TokenObtainPairSerializer.validate = MagicMock(return_value=JWT_TOKEN)
12 |
13 |
14 | @pytest.mark.django_db
15 | def test_custom_jwt_token(users):
16 | user_one, _ = users
17 |
18 | custom_token_serializer = CustomTokenObtainPairSerializer()
19 | custom_token_serializer.user = user_one
20 |
21 | data = custom_token_serializer.validate("foo")
22 |
23 | assert data.get("refresh", "") == JWT_TOKEN["refresh"]
24 | assert data.get("access", "") == JWT_TOKEN["access"]
25 | assert data.get("name", "") == user_one.username
26 | assert data.get("active", "") == user_one.active
27 |
--------------------------------------------------------------------------------
/api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.db.models import Q
3 | from django.shortcuts import get_object_or_404
4 | from rest_framework import mixins, status, viewsets
5 | from rest_framework.decorators import action
6 | from rest_framework.response import Response
7 | from rest_framework_simplejwt.views import TokenObtainPairView
8 |
9 | from . import services
10 | from .models import Elo, Game
11 | from .permissions import GamePermission
12 | from .serializers import CustomTokenObtainPairSerializer, EloSerializer, GameSerializer
13 |
14 | User = get_user_model()
15 |
16 |
17 | class GameViewSet(viewsets.ModelViewSet):
18 | serializer_class = GameSerializer
19 | queryset = Game.objects.all().order_by("-created_at")
20 |
21 | permission_classes = [
22 | GamePermission,
23 | ]
24 |
25 | @action(detail=True, methods=["put"])
26 | def move(self, request, *args, **kwargs):
27 | """
28 | Move a piece
29 | """
30 |
31 | from_square = request.data.get("from_square")
32 | to_square = request.data.get("to_square")
33 |
34 | game_uuid = kwargs.get("pk")
35 |
36 | game = get_object_or_404(Game, uuid=game_uuid)
37 |
38 | board = game.board
39 | auth_user = User.objects.get(username=request.user)
40 |
41 | self.check_object_permissions(self.request, game)
42 |
43 | move = services.move_piece(board, from_square, to_square)
44 |
45 | if move:
46 | return Response(self.serializer_class(game).data)
47 |
48 | else:
49 | return Response(
50 | data={"detail": f"{from_square}{to_square} is not a valid move."},
51 | status=status.HTTP_400_BAD_REQUEST,
52 | )
53 |
54 | @action(detail=True, methods=["put"])
55 | def join(self, request, *args, **kwargs):
56 | """
57 | Join a game
58 | """
59 |
60 | game_uuid = kwargs.get("pk")
61 | preferred_color = request.data.get("preferred_color")
62 |
63 | game = get_object_or_404(Game, uuid=game_uuid)
64 |
65 | services.assign_color(game, request.user, preferred_color=preferred_color)
66 |
67 | serialized_game = GameSerializer(game).data
68 |
69 | return Response(data=serialized_game)
70 |
71 | @action(detail=False, methods=["get"])
72 | def get_unfinished_games(self, request, *args, **kwargs):
73 | """
74 | Get a list of unfinished games played by the user
75 | """
76 |
77 | user = self.request.user
78 | games = Game.objects.filter(
79 | Q(white_player=user) | Q(black_player=user)
80 | ).order_by("-created_at")
81 |
82 | page = self.paginate_queryset(games)
83 | serialized_games = self.get_serializer(page, many=True).data
84 |
85 | return (
86 | self.get_paginated_response(serialized_games)
87 | if page
88 | else Response(data=serialized_games)
89 | )
90 |
91 |
92 | class EloViewSet(
93 | mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet
94 | ):
95 | queryset = Elo.objects.all()
96 | serializer_class = EloSerializer
97 | lookup_field = "uuid"
98 |
99 |
100 | class CustomTokenObtainPairView(TokenObtainPairView):
101 | serializer_class = CustomTokenObtainPairSerializer
102 |
--------------------------------------------------------------------------------
/chess_api_project/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.1.0"
2 | __version_info__ = tuple(
3 | [
4 | int(num) if num.isdigit() else num
5 | for num in __version__.replace("-", ".", 1).split(".")
6 | ]
7 | )
8 |
--------------------------------------------------------------------------------
/chess_api_project/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from chess_api_project.users.models import User
4 | from chess_api_project.users.tests.factories import UserFactory
5 |
6 |
7 | @pytest.fixture(autouse=True)
8 | def media_storage(settings, tmpdir):
9 | settings.MEDIA_ROOT = tmpdir.strpath
10 |
11 |
12 | @pytest.fixture
13 | def user() -> User:
14 | return UserFactory()
15 |
--------------------------------------------------------------------------------
/chess_api_project/contrib/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/chess_api_project/contrib/sites/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/chess_api_project/contrib/sites/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.contrib.sites.models import _simple_domain_name_validator
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = []
9 |
10 | operations = [
11 | migrations.CreateModel(
12 | name="Site",
13 | fields=[
14 | (
15 | "id",
16 | models.AutoField(
17 | verbose_name="ID",
18 | serialize=False,
19 | auto_created=True,
20 | primary_key=True,
21 | ),
22 | ),
23 | (
24 | "domain",
25 | models.CharField(
26 | max_length=100,
27 | verbose_name="domain name",
28 | validators=[_simple_domain_name_validator],
29 | ),
30 | ),
31 | ("name", models.CharField(max_length=50, verbose_name="display name")),
32 | ],
33 | options={
34 | "ordering": ("domain",),
35 | "db_table": "django_site",
36 | "verbose_name": "site",
37 | "verbose_name_plural": "sites",
38 | },
39 | bases=(models.Model,),
40 | managers=[("objects", django.contrib.sites.models.SiteManager())],
41 | )
42 | ]
43 |
--------------------------------------------------------------------------------
/chess_api_project/contrib/sites/migrations/0002_alter_domain_unique.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.db import migrations, models
3 |
4 |
5 | class Migration(migrations.Migration):
6 |
7 | dependencies = [("sites", "0001_initial")]
8 |
9 | operations = [
10 | migrations.AlterField(
11 | model_name="site",
12 | name="domain",
13 | field=models.CharField(
14 | max_length=100,
15 | unique=True,
16 | validators=[django.contrib.sites.models._simple_domain_name_validator],
17 | verbose_name="domain name",
18 | ),
19 | )
20 | ]
21 |
--------------------------------------------------------------------------------
/chess_api_project/contrib/sites/migrations/0003_set_site_domain_and_name.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 | from django.conf import settings
7 | from django.db import migrations
8 |
9 |
10 | def update_site_forward(apps, schema_editor):
11 | """Set site domain and name."""
12 | Site = apps.get_model("sites", "Site")
13 | Site.objects.update_or_create(
14 | id=settings.SITE_ID,
15 | defaults={"domain": "fullstackdjango.com", "name": "Django Chess API",},
16 | )
17 |
18 |
19 | def update_site_backward(apps, schema_editor):
20 | """Revert site domain and name to default."""
21 | Site = apps.get_model("sites", "Site")
22 | Site.objects.update_or_create(
23 | id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
24 | )
25 |
26 |
27 | class Migration(migrations.Migration):
28 |
29 | dependencies = [("sites", "0002_alter_domain_unique")]
30 |
31 | operations = [migrations.RunPython(update_site_forward, update_site_backward)]
32 |
--------------------------------------------------------------------------------
/chess_api_project/contrib/sites/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/chess_api_project/static/css/project.css:
--------------------------------------------------------------------------------
1 | /* These styles are generated from project.scss. */
2 |
3 | .alert-debug {
4 | color: black;
5 | background-color: white;
6 | border-color: #d6e9c6;
7 | }
8 |
9 | .alert-error {
10 | color: #b94a48;
11 | background-color: #f2dede;
12 | border-color: #eed3d7;
13 | }
14 |
--------------------------------------------------------------------------------
/chess_api_project/static/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/static/fonts/.gitkeep
--------------------------------------------------------------------------------
/chess_api_project/static/images/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/static/images/favicons/favicon.ico
--------------------------------------------------------------------------------
/chess_api_project/static/js/project.js:
--------------------------------------------------------------------------------
1 | /* Project specific Javascript goes here. */
2 |
--------------------------------------------------------------------------------
/chess_api_project/static/sass/custom_bootstrap_vars.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/static/sass/custom_bootstrap_vars.scss
--------------------------------------------------------------------------------
/chess_api_project/static/sass/project.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | // project specific CSS goes here
6 |
7 | ////////////////////////////////
8 | //Variables//
9 | ////////////////////////////////
10 |
11 | // Alert colors
12 |
13 | $white: #fff;
14 | $mint-green: #d6e9c6;
15 | $black: #000;
16 | $pink: #f2dede;
17 | $dark-pink: #eed3d7;
18 | $red: #b94a48;
19 |
20 | ////////////////////////////////
21 | //Alerts//
22 | ////////////////////////////////
23 |
24 | // bootstrap alert CSS, translated to the django-standard levels of
25 | // debug, info, success, warning, error
26 |
27 | .alert-debug {
28 | background-color: $white;
29 | border-color: $mint-green;
30 | color: $black;
31 | }
32 |
33 | .alert-error {
34 | background-color: $pink;
35 | border-color: $dark-pink;
36 | color: $red;
37 | }
38 |
--------------------------------------------------------------------------------
/chess_api_project/templates/403.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Forbidden (403){% endblock %}
4 |
5 | {% block content %}
6 |
Forbidden (403)
7 |
8 | CSRF verification failed. Request aborted.
9 | {% endblock content %}
10 |
--------------------------------------------------------------------------------
/chess_api_project/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Page not found{% endblock %}
4 |
5 | {% block content %}
6 | Page not found
7 |
8 | This is not the page you were looking for.
9 | {% endblock content %}
10 |
--------------------------------------------------------------------------------
/chess_api_project/templates/500.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Server Error{% endblock %}
4 |
5 | {% block content %}
6 | Ooops!!! 500
7 |
8 | Looks like something went wrong!
9 |
10 | We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.
11 | {% endblock content %}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/account_inactive.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% trans "Account Inactive" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% trans "Account Inactive" %}
9 |
10 | {% trans "This account is inactive." %}
11 | {% endblock %}
12 |
13 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/base.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %}
3 |
4 | {% block content %}
5 |
6 |
7 | {% block inner %}{% endblock %}
8 |
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/email.html:
--------------------------------------------------------------------------------
1 |
2 | {% extends "account/base.html" %}
3 |
4 | {% load i18n %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% trans "Account" %}{% endblock %}
8 |
9 | {% block inner %}
10 | {% trans "E-mail Addresses" %}
11 |
12 | {% if user.emailaddress_set.all %}
13 | {% trans 'The following e-mail addresses are associated with your account:' %}
14 |
15 |
44 |
45 | {% else %}
46 | {% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
47 |
48 | {% endif %}
49 |
50 |
51 | {% trans "Add E-mail Address" %}
52 |
53 |
58 |
59 | {% endblock %}
60 |
61 |
62 | {% block javascript %}
63 | {{ block.super }}
64 |
79 | {% endblock %}
80 |
81 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/email_confirm.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
7 |
8 |
9 | {% block inner %}
10 | {% trans "Confirm E-mail Address" %}
11 |
12 | {% if confirmation %}
13 |
14 | {% user_display confirmation.email_address.user as user_display %}
15 |
16 | {% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}
17 |
18 |
22 |
23 | {% else %}
24 |
25 | {% url 'account_email' as email_url %}
26 |
27 | {% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request .{% endblocktrans %}
28 |
29 | {% endif %}
30 |
31 | {% endblock %}
32 |
33 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/login.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account socialaccount %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% trans "Sign In" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 | {% trans "Sign In" %}
12 |
13 | {% get_providers as socialaccount_providers %}
14 |
15 | {% if socialaccount_providers %}
16 | {% blocktrans with site.name as site_name %}Please sign in with one
17 | of your existing third party accounts. Or, sign up
18 | for a {{ site_name }} account and sign in below:{% endblocktrans %}
19 |
20 |
21 |
22 |
23 | {% include "socialaccount/snippets/provider_list.html" with process="login" %}
24 |
25 |
26 |
{% trans 'or' %}
27 |
28 |
29 |
30 | {% include "socialaccount/snippets/login_extra.html" %}
31 |
32 | {% else %}
33 | {% blocktrans %}If you have not created an account yet, then please
34 | sign up first.{% endblocktrans %}
35 | {% endif %}
36 |
37 |
46 |
47 | {% endblock %}
48 |
49 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/logout.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% trans "Sign Out" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% trans "Sign Out" %}
9 |
10 | {% trans 'Are you sure you want to sign out?' %}
11 |
12 |
19 |
20 |
21 | {% endblock %}
22 |
23 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/password_change.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% trans "Change Password" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% trans "Change Password" %}
10 |
11 |
16 | {% endblock %}
17 |
18 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/password_reset.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% trans "Password Reset" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 | {% trans "Password Reset" %}
12 | {% if user.is_authenticated %}
13 | {% include "account/snippets/already_logged_in.html" %}
14 | {% endif %}
15 |
16 | {% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}
17 |
18 |
23 |
24 | {% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}
25 | {% endblock %}
26 |
27 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/password_reset_done.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}{% trans "Password Reset" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% trans "Password Reset" %}
10 |
11 | {% if user.is_authenticated %}
12 | {% include "account/snippets/already_logged_in.html" %}
13 | {% endif %}
14 |
15 | {% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}
16 | {% endblock %}
17 |
18 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/password_reset_from_key.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 | {% block head_title %}{% trans "Change Password" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}
9 |
10 | {% if token_fail %}
11 | {% url 'account_reset_password' as passwd_reset_url %}
12 | {% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset .{% endblocktrans %}
13 | {% else %}
14 | {% if form %}
15 |
20 | {% else %}
21 | {% trans 'Your password is now changed.' %}
22 | {% endif %}
23 | {% endif %}
24 | {% endblock %}
25 |
26 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/password_reset_from_key_done.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% block head_title %}{% trans "Change Password" %}{% endblock %}
5 |
6 | {% block inner %}
7 | {% trans "Change Password" %}
8 | {% trans 'Your password is now changed.' %}
9 | {% endblock %}
10 |
11 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/password_set.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% trans "Set Password" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% trans "Set Password" %}
10 |
11 |
16 | {% endblock %}
17 |
18 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/signup.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% trans "Signup" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% trans "Sign Up" %}
10 |
11 | {% blocktrans %}Already have an account? Then please sign in .{% endblocktrans %}
12 |
13 |
21 |
22 | {% endblock %}
23 |
24 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/signup_closed.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% trans "Sign Up Closed" %}
9 |
10 | {% trans "We are sorry, but the sign up is currently closed." %}
11 | {% endblock %}
12 |
13 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/verification_sent.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% trans "Verify Your E-mail Address" %}
9 |
10 | {% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}
11 |
12 | {% endblock %}
13 |
14 |
--------------------------------------------------------------------------------
/chess_api_project/templates/account/verified_email_required.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% trans "Verify Your E-mail Address" %}
9 |
10 | {% url 'account_email' as email_url %}
11 |
12 | {% blocktrans %}This part of the site requires us to verify that
13 | you are who you claim to be. For this purpose, we require that you
14 | verify ownership of your e-mail address. {% endblocktrans %}
15 |
16 | {% blocktrans %}We have sent an e-mail to you for
17 | verification. Please click on the link inside this e-mail. Please
18 | contact us if you do not receive it within a few minutes.{% endblocktrans %}
19 |
20 | {% blocktrans %}Note: you can still change your e-mail address .{% endblocktrans %}
21 |
22 |
23 | {% endblock %}
24 |
25 |
--------------------------------------------------------------------------------
/chess_api_project/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static i18n %}
2 |
3 |
4 |
5 |
6 | {% block title %}Django Chess API{% endblock title %}
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 | {% block css %}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {% endblock %}
32 |
33 |
34 |
35 |
36 |
37 |
76 |
77 |
78 |
79 | {% if messages %}
80 | {% for message in messages %}
81 |
{{ message }}×
82 | {% endfor %}
83 | {% endif %}
84 |
85 | {% block content %}
86 |
Use this document as a way to quick start any new project.
87 | {% endblock content %}
88 |
89 |
90 |
91 | {% block modal %}{% endblock modal %}
92 |
93 |
95 |
96 | {% block javascript %}
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | {% endblock javascript %}
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/chess_api_project/templates/pages/about.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
--------------------------------------------------------------------------------
/chess_api_project/templates/pages/home.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
--------------------------------------------------------------------------------
/chess_api_project/templates/users/user_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load static %}
3 |
4 | {% block title %}User: {{ object.username }}{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
{{ object.username }}
13 | {% if object.name %}
14 |
{{ object.name }}
15 | {% endif %}
16 |
17 |
18 |
19 | {% if object == request.user %}
20 |
21 |
30 |
31 | {% endif %}
32 |
33 |
34 |
35 | {% endblock content %}
36 |
37 |
--------------------------------------------------------------------------------
/chess_api_project/templates/users/user_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load crispy_forms_tags %}
3 |
4 | {% block title %}{{ user.username }}{% endblock %}
5 |
6 | {% block content %}
7 | {{ user.username }}
8 |
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/chess_api_project/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/users/__init__.py
--------------------------------------------------------------------------------
/chess_api_project/users/adapters.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from allauth.account.adapter import DefaultAccountAdapter
4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
5 | from django.conf import settings
6 | from django.http import HttpRequest
7 |
8 |
9 | class AccountAdapter(DefaultAccountAdapter):
10 | def is_open_for_signup(self, request: HttpRequest):
11 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
12 |
13 |
14 | class SocialAccountAdapter(DefaultSocialAccountAdapter):
15 | def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
16 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
17 |
--------------------------------------------------------------------------------
/chess_api_project/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth import admin as auth_admin
3 | from django.contrib.auth import get_user_model
4 |
5 | from chess_api_project.users.forms import UserChangeForm, UserCreationForm
6 |
7 | User = get_user_model()
8 |
9 |
10 | @admin.register(User)
11 | class UserAdmin(auth_admin.UserAdmin):
12 |
13 | form = UserChangeForm
14 | add_form = UserCreationForm
15 | fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets
16 | list_display = ["username", "name", "is_superuser"]
17 | search_fields = ["name"]
18 |
--------------------------------------------------------------------------------
/chess_api_project/users/api/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from chess_api_project.users.models import User
4 |
5 |
6 | class UserSerializer(serializers.ModelSerializer):
7 | class Meta:
8 | model = User
9 | fields = ["username", "email", "name", "url"]
10 |
11 | extra_kwargs = {
12 | "url": {"view_name": "api:user-detail", "lookup_field": "username"}
13 | }
14 |
--------------------------------------------------------------------------------
/chess_api_project/users/api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import status
3 | from rest_framework.decorators import action
4 | from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
5 | from rest_framework.response import Response
6 | from rest_framework.viewsets import GenericViewSet
7 |
8 | from .serializers import UserSerializer
9 |
10 | User = get_user_model()
11 |
12 |
13 | class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
14 | serializer_class = UserSerializer
15 | queryset = User.objects.all()
16 | lookup_field = "username"
17 |
18 | def get_queryset(self, *args, **kwargs):
19 | return self.queryset.filter(id=self.request.user.id)
20 |
21 | @action(detail=False, methods=["GET"])
22 | def me(self, request):
23 | serializer = UserSerializer(request.user, context={"request": request})
24 | return Response(status=status.HTTP_200_OK, data=serializer.data)
25 |
--------------------------------------------------------------------------------
/chess_api_project/users/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 | from django.utils.translation import gettext_lazy as _
3 |
4 |
5 | class UsersConfig(AppConfig):
6 | name = "chess_api_project.users"
7 | verbose_name = _("Users")
8 |
9 | def ready(self):
10 | try:
11 | import chess_api_project.users.signals # noqa F401
12 | except ImportError:
13 | pass
14 |
--------------------------------------------------------------------------------
/chess_api_project/users/forms.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import forms, get_user_model
2 | from django.core.exceptions import ValidationError
3 | from django.utils.translation import ugettext_lazy as _
4 |
5 | User = get_user_model()
6 |
7 |
8 | class UserChangeForm(forms.UserChangeForm):
9 | class Meta(forms.UserChangeForm.Meta):
10 | model = User
11 |
12 |
13 | class UserCreationForm(forms.UserCreationForm):
14 |
15 | error_message = forms.UserCreationForm.error_messages.update(
16 | {"duplicate_username": _("This username has already been taken.")}
17 | )
18 |
19 | class Meta(forms.UserCreationForm.Meta):
20 | model = User
21 |
22 | def clean_username(self):
23 | username = self.cleaned_data["username"]
24 |
25 | try:
26 | User.objects.get(username=username)
27 | except User.DoesNotExist:
28 | return username
29 |
30 | raise ValidationError(self.error_messages["duplicate_username"])
31 |
--------------------------------------------------------------------------------
/chess_api_project/users/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.auth.models
2 | import django.contrib.auth.validators
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [("auth", "0008_alter_user_username_max_length")]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name="User",
16 | fields=[
17 | (
18 | "id",
19 | models.AutoField(
20 | auto_created=True,
21 | primary_key=True,
22 | serialize=False,
23 | verbose_name="ID",
24 | ),
25 | ),
26 | ("password", models.CharField(max_length=128, verbose_name="password")),
27 | (
28 | "last_login",
29 | models.DateTimeField(
30 | blank=True, null=True, verbose_name="last login"
31 | ),
32 | ),
33 | (
34 | "is_superuser",
35 | models.BooleanField(
36 | default=False,
37 | help_text="Designates that this user has all permissions without explicitly assigning them.",
38 | verbose_name="superuser status",
39 | ),
40 | ),
41 | (
42 | "username",
43 | models.CharField(
44 | error_messages={
45 | "unique": "A user with that username already exists."
46 | },
47 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
48 | max_length=150,
49 | unique=True,
50 | validators=[
51 | django.contrib.auth.validators.UnicodeUsernameValidator()
52 | ],
53 | verbose_name="username",
54 | ),
55 | ),
56 | (
57 | "first_name",
58 | models.CharField(
59 | blank=True, max_length=30, verbose_name="first name"
60 | ),
61 | ),
62 | (
63 | "last_name",
64 | models.CharField(
65 | blank=True, max_length=150, verbose_name="last name"
66 | ),
67 | ),
68 | (
69 | "email",
70 | models.EmailField(
71 | blank=True, max_length=254, verbose_name="email address"
72 | ),
73 | ),
74 | (
75 | "is_staff",
76 | models.BooleanField(
77 | default=False,
78 | help_text="Designates whether the user can log into this admin site.",
79 | verbose_name="staff status",
80 | ),
81 | ),
82 | (
83 | "is_active",
84 | models.BooleanField(
85 | default=True,
86 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
87 | verbose_name="active",
88 | ),
89 | ),
90 | (
91 | "date_joined",
92 | models.DateTimeField(
93 | default=django.utils.timezone.now, verbose_name="date joined"
94 | ),
95 | ),
96 | (
97 | "name",
98 | models.CharField(
99 | blank=True, max_length=255, verbose_name="Name of User"
100 | ),
101 | ),
102 | (
103 | "groups",
104 | models.ManyToManyField(
105 | blank=True,
106 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
107 | related_name="user_set",
108 | related_query_name="user",
109 | to="auth.Group",
110 | verbose_name="groups",
111 | ),
112 | ),
113 | (
114 | "user_permissions",
115 | models.ManyToManyField(
116 | blank=True,
117 | help_text="Specific permissions for this user.",
118 | related_name="user_set",
119 | related_query_name="user",
120 | to="auth.Permission",
121 | verbose_name="user permissions",
122 | ),
123 | ),
124 | ],
125 | options={
126 | "verbose_name_plural": "users",
127 | "verbose_name": "user",
128 | "abstract": False,
129 | },
130 | managers=[("objects", django.contrib.auth.models.UserManager())],
131 | )
132 | ]
133 |
--------------------------------------------------------------------------------
/chess_api_project/users/migrations/0002_user_active.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-06-10 09:56
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("users", "0001_initial"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="user", name="active", field=models.BooleanField(default=True),
15 | ),
16 | ]
17 |
--------------------------------------------------------------------------------
/chess_api_project/users/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/users/migrations/__init__.py
--------------------------------------------------------------------------------
/chess_api_project/users/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import AbstractUser
2 | from django.db.models import CharField, BooleanField
3 | from django.urls import reverse
4 | from django.utils.translation import ugettext_lazy as _
5 |
6 |
7 | class User(AbstractUser):
8 |
9 | # First Name and Last Name do not cover name patterns
10 | # around the globe.
11 | name = CharField(_("Name of User"), blank=True, max_length=255)
12 | active = BooleanField(default=True)
13 |
14 | def get_absolute_url(self):
15 | return reverse("users:detail", kwargs={"username": self.username})
16 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/users/tests/__init__.py
--------------------------------------------------------------------------------
/chess_api_project/users/tests/factories.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Sequence
2 |
3 | from django.contrib.auth import get_user_model
4 | from factory import DjangoModelFactory, Faker, post_generation
5 |
6 |
7 | class UserFactory(DjangoModelFactory):
8 |
9 | username = Faker("user_name")
10 | email = Faker("email")
11 | name = Faker("name")
12 |
13 | @post_generation
14 | def password(self, create: bool, extracted: Sequence[Any], **kwargs):
15 | password = (
16 | extracted
17 | if extracted
18 | else Faker(
19 | "password",
20 | length=42,
21 | special_chars=True,
22 | digits=True,
23 | upper_case=True,
24 | lower_case=True,
25 | ).generate(extra_kwargs={})
26 | )
27 | self.set_password(password)
28 |
29 | class Meta:
30 | model = get_user_model()
31 | django_get_or_create = ["username"]
32 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/test_drf_urls.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.urls import resolve, reverse
3 |
4 | from chess_api_project.users.models import User
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | def test_user_detail(user: User):
10 | assert (
11 | reverse("api:user-detail", kwargs={"username": user.username})
12 | == f"/api/users/{user.username}/"
13 | )
14 | assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
15 |
16 |
17 | def test_user_list():
18 | assert reverse("api:user-list") == "/api/users/"
19 | assert resolve("/api/users/").view_name == "api:user-list"
20 |
21 |
22 | def test_user_me():
23 | assert reverse("api:user-me") == "/api/users/me/"
24 | assert resolve("/api/users/me/").view_name == "api:user-me"
25 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/test_drf_views.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.test import RequestFactory
3 |
4 | from chess_api_project.users.api.views import UserViewSet
5 | from chess_api_project.users.models import User
6 |
7 | pytestmark = pytest.mark.django_db
8 |
9 |
10 | class TestUserViewSet:
11 | def test_get_queryset(self, user: User, rf: RequestFactory):
12 | view = UserViewSet()
13 | request = rf.get("/fake-url/")
14 | request.user = user
15 |
16 | view.request = request
17 |
18 | assert user in view.get_queryset()
19 |
20 | def test_me(self, user: User, rf: RequestFactory):
21 | view = UserViewSet()
22 | request = rf.get("/fake-url/")
23 | request.user = user
24 |
25 | view.request = request
26 |
27 | response = view.me(request)
28 |
29 | assert response.data == {
30 | "username": user.username,
31 | "email": user.email,
32 | "name": user.name,
33 | "url": f"http://testserver/api/users/{user.username}/",
34 | }
35 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from chess_api_project.users.forms import UserCreationForm
4 | from chess_api_project.users.tests.factories import UserFactory
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | class TestUserCreationForm:
10 | def test_clean_username(self):
11 | # A user with proto_user params does not exist yet.
12 | proto_user = UserFactory.build()
13 |
14 | form = UserCreationForm(
15 | {
16 | "username": proto_user.username,
17 | "password1": proto_user._password,
18 | "password2": proto_user._password,
19 | }
20 | )
21 |
22 | assert form.is_valid()
23 | assert form.clean_username() == proto_user.username
24 |
25 | # Creating a user.
26 | form.save()
27 |
28 | # The user with proto_user params already exists,
29 | # hence cannot be created.
30 | form = UserCreationForm(
31 | {
32 | "username": proto_user.username,
33 | "password1": proto_user._password,
34 | "password2": proto_user._password,
35 | }
36 | )
37 |
38 | assert not form.is_valid()
39 | assert len(form.errors) == 1
40 | assert "username" in form.errors
41 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/test_models.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from chess_api_project.users.models import User
4 |
5 | pytestmark = pytest.mark.django_db
6 |
7 |
8 | def test_user_get_absolute_url(user: User):
9 | assert user.get_absolute_url() == f"/users/{user.username}/"
10 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/test_urls.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.urls import resolve, reverse
3 |
4 | from chess_api_project.users.models import User
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | def test_detail(user: User):
10 | assert (
11 | reverse("users:detail", kwargs={"username": user.username})
12 | == f"/users/{user.username}/"
13 | )
14 | assert resolve(f"/users/{user.username}/").view_name == "users:detail"
15 |
16 |
17 | def test_update():
18 | assert reverse("users:update") == "/users/~update/"
19 | assert resolve("/users/~update/").view_name == "users:update"
20 |
21 |
22 | def test_redirect():
23 | assert reverse("users:redirect") == "/users/~redirect/"
24 | assert resolve("/users/~redirect/").view_name == "users:redirect"
25 |
--------------------------------------------------------------------------------
/chess_api_project/users/tests/test_views.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.contrib.auth.models import AnonymousUser
3 | from django.http.response import Http404
4 | from django.test import RequestFactory
5 |
6 | from chess_api_project.users.models import User
7 | from chess_api_project.users.tests.factories import UserFactory
8 | from chess_api_project.users.views import (
9 | UserRedirectView,
10 | UserUpdateView,
11 | user_detail_view,
12 | )
13 |
14 | pytestmark = pytest.mark.django_db
15 |
16 |
17 | class TestUserUpdateView:
18 | """
19 | TODO:
20 | extracting view initialization code as class-scoped fixture
21 | would be great if only pytest-django supported non-function-scoped
22 | fixture db access -- this is a work-in-progress for now:
23 | https://github.com/pytest-dev/pytest-django/pull/258
24 | """
25 |
26 | def test_get_success_url(self, user: User, rf: RequestFactory):
27 | view = UserUpdateView()
28 | request = rf.get("/fake-url/")
29 | request.user = user
30 |
31 | view.request = request
32 |
33 | assert view.get_success_url() == f"/users/{user.username}/"
34 |
35 | def test_get_object(self, user: User, rf: RequestFactory):
36 | view = UserUpdateView()
37 | request = rf.get("/fake-url/")
38 | request.user = user
39 |
40 | view.request = request
41 |
42 | assert view.get_object() == user
43 |
44 |
45 | class TestUserRedirectView:
46 | def test_get_redirect_url(self, user: User, rf: RequestFactory):
47 | view = UserRedirectView()
48 | request = rf.get("/fake-url")
49 | request.user = user
50 |
51 | view.request = request
52 |
53 | assert view.get_redirect_url() == f"/users/{user.username}/"
54 |
55 |
56 | class TestUserDetailView:
57 | def test_authenticated(self, user: User, rf: RequestFactory):
58 | request = rf.get("/fake-url/")
59 | request.user = UserFactory()
60 |
61 | response = user_detail_view(request, username=user.username)
62 |
63 | assert response.status_code == 200
64 |
65 | def test_not_authenticated(self, user: User, rf: RequestFactory):
66 | request = rf.get("/fake-url/")
67 | request.user = AnonymousUser() # type: ignore
68 |
69 | response = user_detail_view(request, username=user.username)
70 |
71 | assert response.status_code == 302
72 | assert response.url == "/accounts/login/?next=/fake-url/"
73 |
74 | def test_case_sensitivity(self, rf: RequestFactory):
75 | request = rf.get("/fake-url/")
76 | request.user = UserFactory(username="UserName")
77 |
78 | with pytest.raises(Http404):
79 | user_detail_view(request, username="username")
80 |
--------------------------------------------------------------------------------
/chess_api_project/users/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from chess_api_project.users.views import (
4 | user_detail_view,
5 | user_redirect_view,
6 | user_update_view,
7 | )
8 |
9 | app_name = "users"
10 | urlpatterns = [
11 | path("~redirect/", view=user_redirect_view, name="redirect"),
12 | path("~update/", view=user_update_view, name="update"),
13 | path("/", view=user_detail_view, name="detail"),
14 | ]
15 |
--------------------------------------------------------------------------------
/chess_api_project/users/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth import get_user_model
3 | from django.contrib.auth.mixins import LoginRequiredMixin
4 | from django.urls import reverse
5 | from django.utils.translation import ugettext_lazy as _
6 | from django.views.generic import DetailView, RedirectView, UpdateView
7 |
8 | User = get_user_model()
9 |
10 |
11 | class UserDetailView(LoginRequiredMixin, DetailView):
12 |
13 | model = User
14 | slug_field = "username"
15 | slug_url_kwarg = "username"
16 |
17 |
18 | user_detail_view = UserDetailView.as_view()
19 |
20 |
21 | class UserUpdateView(LoginRequiredMixin, UpdateView):
22 |
23 | model = User
24 | fields = ["name"]
25 |
26 | def get_success_url(self):
27 | return reverse("users:detail", kwargs={"username": self.request.user.username})
28 |
29 | def get_object(self):
30 | return User.objects.get(username=self.request.user.username)
31 |
32 | def form_valid(self, form):
33 | messages.add_message(
34 | self.request, messages.INFO, _("Infos successfully updated")
35 | )
36 | return super().form_valid(form)
37 |
38 |
39 | user_update_view = UserUpdateView.as_view()
40 |
41 |
42 | class UserRedirectView(LoginRequiredMixin, RedirectView):
43 |
44 | permanent = False
45 |
46 | def get_redirect_url(self):
47 | return reverse("users:detail", kwargs={"username": self.request.user.username})
48 |
49 |
50 | user_redirect_view = UserRedirectView.as_view()
51 |
--------------------------------------------------------------------------------
/chess_api_project/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/chess_api_project/utils/__init__.py
--------------------------------------------------------------------------------
/chess_api_project/utils/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | def settings_context(_request):
5 | return {"settings": settings}
6 |
--------------------------------------------------------------------------------
/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/config/__init__.py
--------------------------------------------------------------------------------
/config/api_router.py:
--------------------------------------------------------------------------------
1 | from api.views import EloViewSet, GameViewSet
2 | from django.conf import settings
3 | from rest_framework.routers import DefaultRouter, SimpleRouter
4 |
5 | from chess_api_project.users.api.views import UserViewSet
6 |
7 | if settings.DEBUG:
8 | router = DefaultRouter()
9 | else:
10 | router = SimpleRouter()
11 |
12 |
13 | router.register("users", UserViewSet)
14 | router.register("game", GameViewSet)
15 | router.register("elo", EloViewSet)
16 |
17 | app_name = "api"
18 | urlpatterns = router.urls
19 |
--------------------------------------------------------------------------------
/config/routing.py:
--------------------------------------------------------------------------------
1 | import stream_app.routing
2 | from channels.auth import AuthMiddlewareStack
3 | from channels.routing import ProtocolTypeRouter, URLRouter
4 |
5 | application = ProtocolTypeRouter(
6 | {
7 | "websocket": AuthMiddlewareStack(
8 | URLRouter(stream_app.routing.websocket_urlpatterns)
9 | ),
10 | }
11 | )
12 |
--------------------------------------------------------------------------------
/config/settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/config/settings/__init__.py
--------------------------------------------------------------------------------
/config/settings/local.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 | from .base import env
3 |
4 | # GENERAL
5 | # ------------------------------------------------------------------------------
6 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug
7 | DEBUG = True
8 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
9 | SECRET_KEY = env(
10 | "DJANGO_SECRET_KEY",
11 | default="NeitZd8pH2YMdJcDtT2D1S1wBrpTYrdfo1QPjYvWMDQxGmWf9n0dM06TfBkV4wFF",
12 | )
13 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
14 | ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"]
15 |
16 | # CACHES
17 | # ------------------------------------------------------------------------------
18 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches
19 | CACHES = {
20 | "default": {
21 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
22 | "LOCATION": "",
23 | }
24 | }
25 |
26 | # EMAIL
27 | # ------------------------------------------------------------------------------
28 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
29 | EMAIL_BACKEND = env(
30 | "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
31 | )
32 |
33 | # WhiteNoise
34 | # ------------------------------------------------------------------------------
35 | # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development
36 | INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405
37 |
38 |
39 | # django-debug-toolbar
40 | # ------------------------------------------------------------------------------
41 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites
42 | INSTALLED_APPS += ["debug_toolbar"] # noqa F405
43 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware
44 | MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405
45 | # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config
46 | DEBUG_TOOLBAR_CONFIG = {
47 | "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"],
48 | "SHOW_TEMPLATE_CONTEXT": True,
49 | }
50 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips
51 | INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"]
52 |
53 |
54 | # django-extensions
55 | # ------------------------------------------------------------------------------
56 | # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
57 | INSTALLED_APPS += ["django_extensions"] # noqa F405
58 |
--------------------------------------------------------------------------------
/config/settings/production.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 | from .base import env
3 |
4 | # GENERAL
5 | # ------------------------------------------------------------------------------
6 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
7 | SECRET_KEY = env("DJANGO_SECRET_KEY")
8 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
9 | ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["fullstackdjango.com"])
10 |
11 | # DATABASES
12 | # ------------------------------------------------------------------------------
13 | DATABASES["default"] = env.db("DATABASE_URL") # noqa F405
14 | DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405
15 | DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405
16 |
17 | # CACHES
18 | # ------------------------------------------------------------------------------
19 | CACHES = {
20 | "default": {
21 | "BACKEND": "django_redis.cache.RedisCache",
22 | "LOCATION": env("REDIS_URL"),
23 | "OPTIONS": {
24 | "CLIENT_CLASS": "django_redis.client.DefaultClient",
25 | # Mimicing memcache behavior.
26 | # http://jazzband.github.io/django-redis/latest/#_memcached_exceptions_behavior
27 | "IGNORE_EXCEPTIONS": True,
28 | },
29 | }
30 | }
31 |
32 | # SECURITY
33 | # ------------------------------------------------------------------------------
34 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
35 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
36 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
37 | SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
38 | # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
39 | SESSION_COOKIE_SECURE = True
40 | # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
41 | CSRF_COOKIE_SECURE = True
42 | # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https
43 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds
44 | # TODO: set this to 60 seconds first and then to 518400 once you prove the former works
45 | SECURE_HSTS_SECONDS = 60
46 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
47 | SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
48 | "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True
49 | )
50 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload
51 | SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
52 | # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff
53 | SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
54 | "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True
55 | )
56 |
57 | # STATIC
58 | # ------------------------
59 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
60 | # MEDIA
61 | # ------------------------------------------------------------------------------
62 |
63 | # TEMPLATES
64 | # ------------------------------------------------------------------------------
65 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates
66 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
67 | (
68 | "django.template.loaders.cached.Loader",
69 | [
70 | "django.template.loaders.filesystem.Loader",
71 | "django.template.loaders.app_directories.Loader",
72 | ],
73 | )
74 | ]
75 |
76 | # EMAIL
77 | # ------------------------------------------------------------------------------
78 | # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
79 | DEFAULT_FROM_EMAIL = env(
80 | "DJANGO_DEFAULT_FROM_EMAIL",
81 | default="Django Chess API ",
82 | )
83 | # https://docs.djangoproject.com/en/dev/ref/settings/#server-email
84 | SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
85 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
86 | EMAIL_SUBJECT_PREFIX = env("DJANGO_EMAIL_SUBJECT_PREFIX", default="[Django Chess API]")
87 |
88 | # ADMIN
89 | # ------------------------------------------------------------------------------
90 | # Django Admin URL regex.
91 | ADMIN_URL = env("DJANGO_ADMIN_URL")
92 |
93 | # Anymail
94 | # ------------------------------------------------------------------------------
95 | # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
96 | INSTALLED_APPS += ["anymail"] # noqa F405
97 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
98 | # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference
99 | # https://anymail.readthedocs.io/en/stable/esps
100 | EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
101 | ANYMAIL = {}
102 |
103 |
104 | # LOGGING
105 | # ------------------------------------------------------------------------------
106 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging
107 | # See https://docs.djangoproject.com/en/dev/topics/logging for
108 | # more details on how to customize your logging configuration.
109 | # A sample logging configuration. The only tangible logging
110 | # performed by this configuration is to send an email to
111 | # the site admins on every HTTP 500 error when DEBUG=False.
112 | LOGGING = {
113 | "version": 1,
114 | "disable_existing_loggers": False,
115 | "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
116 | "formatters": {
117 | "verbose": {
118 | "format": "%(levelname)s %(asctime)s %(module)s "
119 | "%(process)d %(thread)d %(message)s"
120 | }
121 | },
122 | "handlers": {
123 | "mail_admins": {
124 | "level": "ERROR",
125 | "filters": ["require_debug_false"],
126 | "class": "django.utils.log.AdminEmailHandler",
127 | },
128 | "console": {
129 | "level": "DEBUG",
130 | "class": "logging.StreamHandler",
131 | "formatter": "verbose",
132 | },
133 | },
134 | "root": {"level": "INFO", "handlers": ["console"]},
135 | "loggers": {
136 | "django.request": {
137 | "handlers": ["mail_admins"],
138 | "level": "ERROR",
139 | "propagate": True,
140 | },
141 | "django.security.DisallowedHost": {
142 | "level": "ERROR",
143 | "handlers": ["console", "mail_admins"],
144 | "propagate": True,
145 | },
146 | },
147 | }
148 |
149 | # Your stuff...
150 | # ------------------------------------------------------------------------------
151 |
--------------------------------------------------------------------------------
/config/settings/test.py:
--------------------------------------------------------------------------------
1 | """
2 | With these settings, tests run faster.
3 | """
4 |
5 | from .base import * # noqa
6 | from .base import env
7 |
8 | # GENERAL
9 | # ------------------------------------------------------------------------------
10 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
11 | SECRET_KEY = env(
12 | "DJANGO_SECRET_KEY",
13 | default="sVoKpv0zoA21Ze4LRDGhIz8O2lkCnC6UEEqjzK7trefNZ27fo0VBSZKuIBGETQBe",
14 | )
15 | # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
16 | TEST_RUNNER = "django.test.runner.DiscoverRunner"
17 |
18 | # CACHES
19 | # ------------------------------------------------------------------------------
20 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches
21 | CACHES = {
22 | "default": {
23 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
24 | "LOCATION": "",
25 | }
26 | }
27 |
28 | # PASSWORDS
29 | # ------------------------------------------------------------------------------
30 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
31 | PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
32 |
33 | # TEMPLATES
34 | # ------------------------------------------------------------------------------
35 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
36 | (
37 | "django.template.loaders.cached.Loader",
38 | [
39 | "django.template.loaders.filesystem.Loader",
40 | "django.template.loaders.app_directories.Loader",
41 | ],
42 | )
43 | ]
44 |
45 | # EMAIL
46 | # ------------------------------------------------------------------------------
47 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
48 | EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
49 |
50 | # Your stuff...
51 | # ------------------------------------------------------------------------------
52 |
--------------------------------------------------------------------------------
/config/urls.py:
--------------------------------------------------------------------------------
1 | from api.views import CustomTokenObtainPairView
2 |
3 | from django.conf import settings
4 | from django.conf.urls.static import static
5 | from django.contrib import admin
6 | from django.urls import include, path
7 | from django.views import defaults as default_views
8 | from django.views.generic import TemplateView
9 |
10 | from rest_framework.authtoken.views import obtain_auth_token
11 | from rest_framework_simplejwt.views import TokenRefreshView
12 |
13 | from drf_yasg.views import get_schema_view
14 | from drf_yasg import openapi
15 |
16 |
17 | schema_view = get_schema_view(
18 | openapi.Info(
19 | title="Capablanca API",
20 | description="Multiplayer Chess API",
21 | default_version="0.2.0",
22 | urlconf="config.api_router",
23 | ),
24 | )
25 |
26 | urlpatterns = [
27 | path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
28 | path(
29 | "about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
30 | ),
31 | # Django Admin, use {% url 'admin:index' %}
32 | path(settings.ADMIN_URL, admin.site.urls),
33 | # User management
34 | path("users/", include("chess_api_project.users.urls", namespace="users")),
35 | path("accounts/", include("allauth.urls")),
36 | # OpenAPI
37 | path(
38 | "docs/", schema_view.with_ui("swagger", cache_timeout=0), name="openapi-schema",
39 | ),
40 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
41 |
42 | # API URLS
43 | urlpatterns += [
44 | # API base url
45 | path("api/", include("config.api_router")),
46 | # Simple JWT
47 | path("api/token/", CustomTokenObtainPairView.as_view(), name="token_obtain_pair"),
48 | path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
49 | # DRF auth token
50 | path("auth-token/", obtain_auth_token),
51 | # DRF auth
52 | path("api-auth/", include("rest_framework.urls")),
53 | ]
54 |
55 | if settings.DEBUG:
56 | # This allows the error pages to be debugged during development, just visit
57 | # these url in browser to see how these error pages look like.
58 | urlpatterns += [
59 | path(
60 | "400/",
61 | default_views.bad_request,
62 | kwargs={"exception": Exception("Bad Request!")},
63 | ),
64 | path(
65 | "403/",
66 | default_views.permission_denied,
67 | kwargs={"exception": Exception("Permission Denied")},
68 | ),
69 | path(
70 | "404/",
71 | default_views.page_not_found,
72 | kwargs={"exception": Exception("Page not Found")},
73 | ),
74 | path("500/", default_views.server_error),
75 | ]
76 | if "debug_toolbar" in settings.INSTALLED_APPS:
77 | import debug_toolbar
78 |
79 | urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
80 |
--------------------------------------------------------------------------------
/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for Django Chess API project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 | import sys
18 | from pathlib import Path
19 |
20 | from django.core.wsgi import get_wsgi_application
21 |
22 | # This allows easy placement of apps within the interior
23 | # chess_api_project directory.
24 | ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
25 | sys.path.append(str(ROOT_DIR / "chess_api_project"))
26 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
27 | # if running multiple sites in the same mod_wsgi process. To fix this, use
28 | # mod_wsgi daemon mode with each site in its own daemon process, or use
29 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production"
30 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
31 |
32 | # This application object is used by any WSGI server configured to use this
33 | # file. This includes Django's development server, if the WSGI_APPLICATION
34 | # setting points here.
35 | application = get_wsgi_application()
36 | # Apply WSGI middleware here.
37 | # from helloworld.wsgi import HelloWorldApplication
38 | # application = HelloWorldApplication(application)
39 |
--------------------------------------------------------------------------------
/docs/#results.org#:
--------------------------------------------------------------------------------
1 | * [[https://chess.stackexchange.com/questions/12403/what-are-the-possible-results-of-a-game][Results]]
2 |
3 | ** Scheduled
4 | ** Postponed
5 | ** Finished without any moves played
6 | ** In progress
7 | ** Adjourned
8 | ** Finished according to the Basic Rules of Play
9 | ** Finished by the clock
10 | ** Draw
11 | ** Finished because of a breach of rules of one player
12 | ** Finished because both players persistently refuse to comply with the laws of chess
13 | ** To be decided
14 | ** Abandoned
15 | ** Unknown
16 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/__init__.py:
--------------------------------------------------------------------------------
1 | # Included so that Django's startproject comment runs against the docs directory
2 |
--------------------------------------------------------------------------------
/docs/api_coverage/api___init___py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for api/__init__.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/docs/api_coverage/api_admin_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for api/admin.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 # Register your models here.
55 |
56 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/docs/api_coverage/api_apps_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for api/apps.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 from django . apps import AppConfig
55 |
2
56 |
3
57 |
4 class ApiConfig ( AppConfig ) :
58 |
5 name = "api"
59 |
60 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/docs/api_coverage/api_constants_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for api/constants.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
56 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/docs/api_coverage/api_urls_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for api/urls.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 # posts/urls.py
55 |
2 from django . urls import include , path
56 |
3 from rest_framework . routers import DefaultRouter
57 |
4
58 |
5 from . views import EloViewSet , GameViewSet
59 |
6
60 |
7 router = DefaultRouter ( )
61 |
8 router . register ( r"game" , GameViewSet )
62 |
9 router . register ( r"elo" , EloViewSet )
63 |
10
64 |
11
65 |
12 urlpatterns = [
66 |
13 path ( "" , include ( router . urls ) ) ,
67 |
14 ]
68 |
69 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/docs/api_coverage/jquery.ba-throttle-debounce.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery throttle / debounce - v1.1 - 3/7/2010
3 | * http://benalman.com/projects/jquery-throttle-debounce-plugin/
4 | *
5 | * Copyright (c) 2010 "Cowboy" Ben Alman
6 | * Dual licensed under the MIT and GPL licenses.
7 | * http://benalman.com/about/license/
8 | */
9 | (function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this);
10 |
--------------------------------------------------------------------------------
/docs/api_coverage/jquery.hotkeys.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Hotkeys Plugin
3 | * Copyright 2010, John Resig
4 | * Dual licensed under the MIT or GPL Version 2 licenses.
5 | *
6 | * Based upon the plugin by Tzury Bar Yochay:
7 | * http://github.com/tzuryby/hotkeys
8 | *
9 | * Original idea by:
10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
11 | */
12 |
13 | (function(jQuery){
14 |
15 | jQuery.hotkeys = {
16 | version: "0.8",
17 |
18 | specialKeys: {
19 | 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
21 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
22 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
23 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
24 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
25 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
26 | },
27 |
28 | shiftNums: {
29 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
30 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
31 | ".": ">", "/": "?", "\\": "|"
32 | }
33 | };
34 |
35 | function keyHandler( handleObj ) {
36 | // Only care when a possible input has been specified
37 | if ( typeof handleObj.data !== "string" ) {
38 | return;
39 | }
40 |
41 | var origHandler = handleObj.handler,
42 | keys = handleObj.data.toLowerCase().split(" ");
43 |
44 | handleObj.handler = function( event ) {
45 | // Don't fire in text-accepting inputs that we didn't directly bind to
46 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
47 | event.target.type === "text") ) {
48 | return;
49 | }
50 |
51 | // Keypress represents characters, not special keys
52 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
53 | character = String.fromCharCode( event.which ).toLowerCase(),
54 | key, modif = "", possible = {};
55 |
56 | // check combinations (alt|ctrl|shift+anything)
57 | if ( event.altKey && special !== "alt" ) {
58 | modif += "alt+";
59 | }
60 |
61 | if ( event.ctrlKey && special !== "ctrl" ) {
62 | modif += "ctrl+";
63 | }
64 |
65 | // TODO: Need to make sure this works consistently across platforms
66 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
67 | modif += "meta+";
68 | }
69 |
70 | if ( event.shiftKey && special !== "shift" ) {
71 | modif += "shift+";
72 | }
73 |
74 | if ( special ) {
75 | possible[ modif + special ] = true;
76 |
77 | } else {
78 | possible[ modif + character ] = true;
79 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
80 |
81 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
82 | if ( modif === "shift+" ) {
83 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
84 | }
85 | }
86 |
87 | for ( var i = 0, l = keys.length; i < l; i++ ) {
88 | if ( possible[ keys[i] ] ) {
89 | return origHandler.apply( this, arguments );
90 | }
91 | }
92 | };
93 | }
94 |
95 | jQuery.each([ "keydown", "keyup", "keypress" ], function() {
96 | jQuery.event.special[ this ] = { add: keyHandler };
97 | });
98 |
99 | })( jQuery );
100 |
--------------------------------------------------------------------------------
/docs/api_coverage/jquery.isonscreen.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2010
2 | * @author Laurence Wheway
3 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
4 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
5 | *
6 | * @version 1.2.0
7 | */
8 | (function($) {
9 | jQuery.extend({
10 | isOnScreen: function(box, container) {
11 | //ensure numbers come in as intgers (not strings) and remove 'px' is it's there
12 | for(var i in box){box[i] = parseFloat(box[i])};
13 | for(var i in container){container[i] = parseFloat(container[i])};
14 |
15 | if(!container){
16 | container = {
17 | left: $(window).scrollLeft(),
18 | top: $(window).scrollTop(),
19 | width: $(window).width(),
20 | height: $(window).height()
21 | }
22 | }
23 |
24 | if( box.left+box.width-container.left > 0 &&
25 | box.left < container.width+container.left &&
26 | box.top+box.height-container.top > 0 &&
27 | box.top < container.height+container.top
28 | ) return true;
29 | return false;
30 | }
31 | })
32 |
33 |
34 | jQuery.fn.isOnScreen = function (container) {
35 | for(var i in container){container[i] = parseFloat(container[i])};
36 |
37 | if(!container){
38 | container = {
39 | left: $(window).scrollLeft(),
40 | top: $(window).scrollTop(),
41 | width: $(window).width(),
42 | height: $(window).height()
43 | }
44 | }
45 |
46 | if( $(this).offset().left+$(this).width()-container.left > 0 &&
47 | $(this).offset().left < container.width+container.left &&
48 | $(this).offset().top+$(this).height()-container.top > 0 &&
49 | $(this).offset().top < container.height+container.top
50 | ) return true;
51 | return false;
52 | }
53 | })(jQuery);
54 |
--------------------------------------------------------------------------------
/docs/api_coverage/keybd_closed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/docs/api_coverage/keybd_closed.png
--------------------------------------------------------------------------------
/docs/api_coverage/keybd_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/docs/api_coverage/keybd_open.png
--------------------------------------------------------------------------------
/docs/api_coverage/status.json:
--------------------------------------------------------------------------------
1 | {"format":2,"version":"5.1","globals":"3729063479803456f8cdc27da4ccc9fd","files":{"api___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"api___init___py.html","relative_filename":"api/__init__.py"}},"api_admin_py":{"hash":"3964635549ef6016964175c9db70b348","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"api_admin_py.html","relative_filename":"api/admin.py"}},"api_apps_py":{"hash":"c821bdebef7d32ffdf1f25f69837ef74","index":{"nums":[1,3,0,0,0,0,0],"html_filename":"api_apps_py.html","relative_filename":"api/apps.py"}},"api_constants_py":{"hash":"baadc6b7606d9f07be3417a354847df8","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"api_constants_py.html","relative_filename":"api/constants.py"}},"api_models_py":{"hash":"a48396a43f9169991bffa0f465d636a0","index":{"nums":[1,138,0,6,0,0,0],"html_filename":"api_models_py.html","relative_filename":"api/models.py"}},"api_permissions_py":{"hash":"e63abecc72487d0f42a638fc7189495a","index":{"nums":[1,22,0,1,0,0,0],"html_filename":"api_permissions_py.html","relative_filename":"api/permissions.py"}},"api_serializers_py":{"hash":"7f06c4a2b888bcbee3cc63439bb4e0d7","index":{"nums":[1,44,0,0,0,0,0],"html_filename":"api_serializers_py.html","relative_filename":"api/serializers.py"}},"api_services_py":{"hash":"4789efa84e73a7ced8d25bc5aec19c73","index":{"nums":[1,113,0,1,0,0,0],"html_filename":"api_services_py.html","relative_filename":"api/services.py"}},"api_views_py":{"hash":"1dc74532c431108f97db8e56e11a811e","index":{"nums":[1,50,0,0,0,0,0],"html_filename":"api_views_py.html","relative_filename":"api/views.py"}},"stream_app___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"stream_app___init___py.html","relative_filename":"stream_app/__init__.py"}},"stream_app_admin_py":{"hash":"3964635549ef6016964175c9db70b348","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"stream_app_admin_py.html","relative_filename":"stream_app/admin.py"}},"stream_app_apps_py":{"hash":"fe9eeb10169c9d06e90ea215f0ca10c4","index":{"nums":[1,3,0,0,0,0,0],"html_filename":"stream_app_apps_py.html","relative_filename":"stream_app/apps.py"}},"stream_app_consumers_py":{"hash":"6ab43602e551c3028f97d89296480f00","index":{"nums":[1,20,0,0,0,0,0],"html_filename":"stream_app_consumers_py.html","relative_filename":"stream_app/consumers.py"}},"stream_app_models_py":{"hash":"8716d289d5120793bf2752a20189df39","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"stream_app_models_py.html","relative_filename":"stream_app/models.py"}},"stream_app_routing_py":{"hash":"94450d32d897b440b9babb2d8018cd60","index":{"nums":[1,3,0,0,0,0,0],"html_filename":"stream_app_routing_py.html","relative_filename":"stream_app/routing.py"}},"stream_app_services_py":{"hash":"9028cf1a00c87346bbbeb658b7621046","index":{"nums":[1,7,0,0,0,0,0],"html_filename":"stream_app_services_py.html","relative_filename":"stream_app/services.py"}},"stream_app_views_py":{"hash":"8f59a7e9210db62370cd600c1c607645","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"stream_app_views_py.html","relative_filename":"stream_app/views.py"}}}}
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app___init___py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/__init__.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app_admin_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/admin.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 # Register your models here.
55 |
56 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app_apps_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/apps.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 from django . apps import AppConfig
55 |
2
56 |
3
57 |
4 class StreamAppConfig ( AppConfig ) :
58 |
5 name = "stream_app"
59 |
60 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app_models_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/models.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 # Create your models here.
55 |
56 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app_routing_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/routing.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 from django . urls import path
55 |
2
56 |
3 from . import consumers
57 |
4
58 |
5 websocket_urlpatterns = [
59 |
6 path ( r"ws/game/<uuid>/" , consumers . GameConsumer ) ,
60 |
7 ]
61 |
62 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app_services_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/services.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
1 from channels . db import database_sync_to_async
55 |
2 from api . models import Game
56 |
3 from api . serializers import GameSerializer
57 |
4
58 |
5
59 |
6 @ database_sync_to_async
60 |
7 def get_serialized_game ( uuid ) :
61 |
8 game = Game . objects . get ( uuid = uuid )
62 |
9 return GameSerializer ( game ) . data
63 |
64 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/docs/api_coverage/stream_app_views_py.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Coverage for stream_app/views.py: 100%
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
31 |
32 |
33 |
Hot-keys on this page
34 |
35 |
36 | r
37 | m
38 | x
39 | p toggle line displays
40 |
41 |
42 | j
43 | k next/prev highlighted chunk
44 |
45 |
46 | 0 (zero) top of page
47 |
48 |
49 | 1 (one) first highlighted chunk
50 |
51 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/docs/chess_api.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/docs/chess_api.pdf
--------------------------------------------------------------------------------
/docs/client_implementation_guide.org:
--------------------------------------------------------------------------------
1 | * [[https://www.codementor.io/@obabichev/react-token-auth-12os8txqo1][Token Auth]]
2 | 1. Tokens should be stored in local storage
3 | 2. Tokens should be restored on page reload
4 | 3. Access token should be passed in the network requests
5 | 4. After expiration access token should be updated by refresh token if the last one is presented
6 | 5. React components should have access to the auth information to render appropriate UI
7 | 6. The solution should be made with pure React (without Redux, thunk, etc..)
8 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 |
14 | # import django
15 | # sys.path.insert(0, os.path.abspath('..'))
16 | # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
17 | # django.setup()
18 |
19 |
20 | # -- Project information -----------------------------------------------------
21 |
22 | project = "Django Chess API"
23 | copyright = """2020, Mikel Losada"""
24 | author = "Mikel Losada"
25 |
26 |
27 | # -- General configuration ---------------------------------------------------
28 |
29 | # Add any Sphinx extension module names here, as strings. They can be
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 | # ones.
32 | extensions = []
33 |
34 | # Add any paths that contain templates here, relative to this directory.
35 | templates_path = ["_templates"]
36 |
37 | # List of patterns, relative to source directory, that match files and
38 | # directories to ignore when looking for source files.
39 | # This pattern also affects html_static_path and html_extra_path.
40 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
41 |
42 |
43 | # -- Options for HTML output -------------------------------------------------
44 |
45 | # The theme to use for HTML and HTML Help pages. See the documentation for
46 | # a list of builtin themes.
47 | #
48 | html_theme = "alabaster"
49 |
50 | # Add any paths that contain custom static files (such as style sheets) here,
51 | # relative to this directory. They are copied after the builtin static files,
52 | # so a file named "default.css" will overwrite the builtin "default.css".
53 | html_static_path = ["_static"]
54 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Django Chess API documentation master file, created by
2 | sphinx-quickstart.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Django Chess API's documentation!
7 | ======================================================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | pycharm/configuration
14 |
15 |
16 |
17 | Indices and tables
18 | ==================
19 |
20 | * :ref:`genindex`
21 | * :ref:`modindex`
22 | * :ref:`search`
23 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/results.org:
--------------------------------------------------------------------------------
1 |
2 | * [[https://chess.stackexchange.com/questions/12403/what-are-the-possible-results-of-a-game][Results]]
3 |
4 | ** Scheduled
5 | ** Postponed
6 | ** Finished without any moves played
7 | ** In progress
8 | ** Adjourned
9 | ** Finished according to the Basic Rules of Play
10 | ** Finished by the clock
11 | ** Draw
12 | ** Finished because of a breach of rules of one player
13 | ** Finished because both players persistently refuse to comply with the laws of chess
14 | ** To be decided
15 | ** Abandoned
16 | ** Unknown
17 |
--------------------------------------------------------------------------------
/docs/time_control.org:
--------------------------------------------------------------------------------
1 | * FIDE time control
2 | There is a single time control for all major FIDE events: 90 minutes for the first 40 moves followed by 30 minutes for the rest of the game with an addition of 30 seconds per move starting from move one.
3 |
--------------------------------------------------------------------------------
/locale/README.rst:
--------------------------------------------------------------------------------
1 | Translations
2 | ============
3 |
4 | Translations will be placed in this folder when running::
5 |
6 | python manage.py makemessages
7 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | from pathlib import Path
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
8 |
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError:
12 | # The above import may fail for some other reason. Ensure that the
13 | # issue is really that Django is missing to avoid masking other
14 | # exceptions on Python 2.
15 | try:
16 | import django # noqa
17 | except ImportError:
18 | raise ImportError(
19 | "Couldn't import Django. Are you sure it's installed and "
20 | "available on your PYTHONPATH environment variable? Did you "
21 | "forget to activate a virtual environment?"
22 | )
23 |
24 | raise
25 |
26 | # This allows easy placement of apps within the interior
27 | # chess_api_project directory.
28 | current_path = Path(__file__).parent.resolve()
29 | sys.path.append(str(current_path / "chess_api_project"))
30 |
31 | execute_from_command_line(sys.argv)
32 |
--------------------------------------------------------------------------------
/openapi-schema.yml:
--------------------------------------------------------------------------------
1 | info:
2 | description: ''
3 | title: ''
4 | version: ''
5 | openapi: 3.0.0
6 | paths:
7 | /auth-token/:
8 | post:
9 | operationId: auth-token_create
10 | tags:
11 | - auth-token
12 | servers:
13 | - url: ''
14 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --ds=config.settings.test --reuse-db
3 | python_files = tests.py test_*.py
4 |
--------------------------------------------------------------------------------
/requirements/#local.txt#:
--------------------------------------------------------------------------------
1 | -r ./base.txt
2 |
3 | Werkzeug==1.0.1 # https://github.com/pallets/werkzeug
4 | ipdb==0.13.2 # https://github.com/gotcha/ipdb
5 | Sphinx==3.0.4 # https://github.com/sphinx-doc/sphinx
6 | psycopg2-binary==2.8.5 # https://github.com/psycopg/psycopg2
7 |
8 | # Testing
9 | # ------------------------------------------------------------------------------
10 | mypy==0.770 # https://github.com/python/mypy
11 | django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs
12 | pytest==5.4.3 # https://github.com/pytest-dev/pytest
13 | pytest-sugar==0.9.3 # https://github.com/Frozenball/pytest-sugar
14 |
15 | # Code quality
16 | # ------------------------------------------------------------------------------
17 | flake8==3.8.2 # https://github.com/PyCQA/flake8
18 | flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort
19 | coverage==5.1 # https://github.com/nedbat/coveragepy
20 | black==19.10b0 # https://github.com/ambv/black
21 | pylint-django==2.0.15 # https://github.com/PyCQA/pylint-django
22 | pre-commit==2.4.0 # https://github.com/pre-commit/pre-commit
23 |
24 | # Django
25 | # ------------------------------------------------------------------------------
26 | factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
27 |
28 | django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar
29 | django-extensions==2.2.9 # https://github.com/django-extensions/django-extensions
30 | django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin
31 | pytest-django==3.9.0 # https://github.com/pytest-dev/pytest-django
32 |
--------------------------------------------------------------------------------
/requirements/base.txt:
--------------------------------------------------------------------------------
1 | pytz==2020.1 # https://github.com/stub42/pytz
2 | python-slugify==4.0.0 # https://github.com/un33k/python-slugify
3 | Pillow==7.1.2 # https://github.com/python-pillow/Pillow
4 | argon2-cffi==19.2.0 # https://github.com/hynek/argon2_cffi
5 | whitenoise==5.0.1 # https://github.com/evansd/whitenoise
6 | redis==3.5.0 # https://github.com/andymccurdy/redis-py
7 | hiredis==1.0.1 # https://github.com/redis/hiredis-py
8 | python-chess==0.31.2
9 | coveralls==2.1.1
10 |
11 | # Django
12 | # ------------------------------------------------------------------------------
13 | django==3.0.7 # pyup: < 3.1 # https://www.djangoproject.com/
14 | django-environ==0.4.5 # https://github.com/joke2k/django-environ
15 | django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
16 | django-allauth==0.41.0 # https://github.com/pennersr/django-allauth
17 | django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms
18 | django-redis==4.11.0 # https://github.com/jazzband/django-redis
19 | django-annoying==0.10.6
20 | # Django REST Framework
21 | djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
22 | djangorestframework-simplejwt==4.4.0
23 | django-cors-headers==3.4.0
24 | drf-yasg==1.17.1
25 | # Django Channels
26 | channels-redis==3.0.0
27 |
--------------------------------------------------------------------------------
/requirements/local.txt:
--------------------------------------------------------------------------------
1 | -r ./base.txt
2 |
3 | Werkzeug==1.0.1 # https://github.com/pallets/werkzeug
4 | ipdb==0.13.2 # https://github.com/gotcha/ipdb
5 | Sphinx==3.0.4 # https://github.com/sphinx-doc/sphinx
6 | psycopg2-binary==2.8.5 # https://github.com/psycopg/psycopg2
7 |
8 | # Testing
9 | # ------------------------------------------------------------------------------
10 | mypy==0.770 # https://github.com/python/mypy
11 | django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs
12 | pytest==5.4.3 # https://github.com/pytest-dev/pytest
13 | pytest-sugar==0.9.3 # https://github.com/Frozenball/pytest-sugar
14 | pytest-cov==2.10.0
15 | pytest-asyncio==0.14.0
16 |
17 | # Code quality
18 | # ------------------------------------------------------------------------------
19 | flake8==3.8.2 # https://github.com/PyCQA/flake8
20 | flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort
21 | coverage==5.1 # https://github.com/nedbat/coveragepy
22 | black==19.10b0 # https://github.com/ambv/black
23 | pylint-django==2.0.15 # https://github.com/PyCQA/pylint-django
24 | pre-commit==2.4.0 # https://github.com/pre-commit/pre-commit
25 |
26 | # Django
27 | # ------------------------------------------------------------------------------
28 | factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
29 |
30 | django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar
31 | django-extensions==2.2.9 # https://github.com/django-extensions/django-extensions
32 | django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin
33 | pytest-django==3.9.0 # https://github.com/pytest-dev/pytest-django
34 |
--------------------------------------------------------------------------------
/requirements/production.txt:
--------------------------------------------------------------------------------
1 | # PRECAUTION: avoid production dependencies that aren't in development
2 |
3 | -r ./base.txt
4 |
5 | gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
6 | psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
7 |
8 | # Django
9 | # ------------------------------------------------------------------------------
10 | django-anymail==7.1.0 # https://github.com/anymail/django-anymail
11 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
4 |
5 | [pycodestyle]
6 | max-line-length = 120
7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
8 |
9 | [mypy]
10 | python_version = 3.8
11 | check_untyped_defs = True
12 | ignore_missing_imports = True
13 | warn_unused_ignores = True
14 | warn_redundant_casts = True
15 | warn_unused_configs = True
16 | plugins = mypy_django_plugin.main
17 |
18 | [mypy.plugins.django-stubs]
19 | django_settings_module = config.settings.test
20 |
21 | [mypy-*.migrations.*]
22 | # Django migrations should not produce any errors:
23 | ignore_errors = True
24 |
25 | [coverage:run]
26 | include = chess_api_project/*
27 | omit = *migrations*, *tests*
28 | plugins =
29 | django_coverage_plugin
30 |
--------------------------------------------------------------------------------
/stream_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/stream_app/__init__.py
--------------------------------------------------------------------------------
/stream_app/admin.py:
--------------------------------------------------------------------------------
1 | # Register your models here.
2 |
--------------------------------------------------------------------------------
/stream_app/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class StreamAppConfig(AppConfig):
5 | name = "stream_app"
6 |
--------------------------------------------------------------------------------
/stream_app/consumers.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 |
4 | from channels.generic.websocket import AsyncWebsocketConsumer
5 |
6 | from stream_app.services import get_serialized_game
7 |
8 |
9 | class GameConsumer(AsyncWebsocketConsumer):
10 | async def connect(self):
11 | self.uuid = self.scope["url_route"]["kwargs"]["uuid"]
12 | self.game_group_name = f"game_{self.uuid}"
13 |
14 | # Join room group
15 | await self.channel_layer.group_add(self.game_group_name, self.channel_name)
16 |
17 | await self.accept()
18 |
19 | async def receive(self, text_data):
20 | data_json = json.loads(text_data)
21 |
22 | if "update" in data_json:
23 | game = await get_serialized_game(data_json.get("uuid"))
24 |
25 | # Update both players' game data
26 | await self.channel_layer.group_send(
27 | self.game_group_name, {"type": "game_data", "game": game,}
28 | )
29 |
30 | async def game_data(self, data):
31 | game = data["game"]
32 |
33 | # Send game over WebSocket
34 | await self.send(text_data=json.dumps(game))
35 |
36 | async def disconnect(self, *args, **kwargs):
37 | await self.channel_layer.group_discard(self.game_group_name, self.channel_name)
38 |
--------------------------------------------------------------------------------
/stream_app/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/stream_app/migrations/__init__.py
--------------------------------------------------------------------------------
/stream_app/models.py:
--------------------------------------------------------------------------------
1 | # Create your models here.
2 |
--------------------------------------------------------------------------------
/stream_app/routing.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import consumers
4 |
5 | websocket_urlpatterns = [
6 | path(r"ws/game//", consumers.GameConsumer),
7 | ]
8 |
--------------------------------------------------------------------------------
/stream_app/services.py:
--------------------------------------------------------------------------------
1 | from channels.db import database_sync_to_async
2 | from api.models import Game
3 | from api.serializers import GameSerializer
4 |
5 |
6 | @database_sync_to_async
7 | def get_serialized_game(uuid):
8 | game = Game.objects.get(uuid=uuid)
9 | return GameSerializer(game).data
10 |
--------------------------------------------------------------------------------
/stream_app/tests/fixtures.py:
--------------------------------------------------------------------------------
1 | GAME_UUID = "fe73b193-4f0d-4b0a-b964-b2bce3540413"
2 |
--------------------------------------------------------------------------------
/stream_app/tests/test_consumers.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import json
3 |
4 | from uuid import uuid4
5 | from unittest import mock
6 |
7 | from channels.testing import WebsocketCommunicator
8 | from stream_app.consumers import GameConsumer
9 |
10 |
11 | from api.models import Game
12 |
13 | from stream_app.tests.fixtures import GAME_UUID
14 |
15 |
16 | @pytest.mark.asyncio
17 | @pytest.fixture
18 | def url_route():
19 | return {
20 | "kwargs": {"uuid": str(uuid4()),},
21 | }
22 |
23 |
24 | @pytest.mark.asyncio
25 | @pytest.fixture
26 | def game_communicator(url_route):
27 | game_uuid = url_route["kwargs"]["uuid"]
28 | communicator = WebsocketCommunicator(GameConsumer, f"/ws/game/{game_uuid}/")
29 | communicator.scope["url_route"] = url_route
30 |
31 | return communicator
32 |
33 |
34 | @pytest.mark.asyncio
35 | async def test_connect(game_communicator):
36 | connected, _ = await game_communicator.connect()
37 |
38 | assert connected
39 |
40 | await game_communicator.disconnect()
41 |
42 |
43 | @pytest.mark.asyncio
44 | @pytest.mark.django_db
45 | async def test_receive(game_communicator):
46 | connected, _ = await game_communicator.connect()
47 |
48 | Game.objects.get = mock.MagicMock(return_value=Game(uuid=GAME_UUID))
49 |
50 | await game_communicator.send_to(json.dumps({"update": "foo"}))
51 | received_message = await game_communicator.receive_json_from()
52 |
53 | assert received_message["uuid"] == GAME_UUID
54 |
55 | await game_communicator.disconnect()
56 |
57 |
58 | @pytest.mark.asyncio
59 | async def test_routing():
60 | from stream_app import routing
61 |
62 | assert routing.websocket_urlpatterns is not None
63 |
--------------------------------------------------------------------------------
/stream_app/views.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorkShoft/capablanca-api/28b81b164dee79d13299d7569c5382e1af4e2c91/stream_app/views.py
--------------------------------------------------------------------------------
/utility/install_os_dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | WORK_DIR="$(dirname "$0")"
4 | DISTRO_NAME=$(lsb_release -sc)
5 | OS_REQUIREMENTS_FILENAME="requirements-$DISTRO_NAME.apt"
6 |
7 | cd $WORK_DIR
8 |
9 | # Check if a requirements file exist for the current distribution.
10 | if [ ! -r "$OS_REQUIREMENTS_FILENAME" ]; then
11 | cat <<-EOF >&2
12 | There is no requirements file for your distribution.
13 | You can see one of the files listed below to help search the equivalent package in your system:
14 | $(find ./ -name "requirements-*.apt" -printf " - %f\n")
15 | EOF
16 | exit 1;
17 | fi
18 |
19 | # Handle call with wrong command
20 | function wrong_command()
21 | {
22 | echo "${0##*/} - unknown command: '${1}'" >&2
23 | usage_message
24 | }
25 |
26 | # Print help / script usage
27 | function usage_message()
28 | {
29 | cat <<-EOF
30 | Usage: $WORK_DIR/${0##*/}
31 | Available commands are:
32 | list Print a list of all packages defined on ${OS_REQUIREMENTS_FILENAME} file
33 | help Print this help
34 |
35 | Commands that require superuser permission:
36 | install Install packages defined on ${OS_REQUIREMENTS_FILENAME} file. Note: This
37 | does not upgrade the packages already installed for new versions, even if
38 | new version is available in the repository.
39 | upgrade Same that install, but upgrade the already installed packages, if new
40 | version is available.
41 | EOF
42 | }
43 |
44 | # Read the requirements.apt file, and remove comments and blank lines
45 | function list_packages(){
46 | grep -v "#" "${OS_REQUIREMENTS_FILENAME}" | grep -v "^$";
47 | }
48 |
49 | function install_packages()
50 | {
51 | list_packages | xargs apt-get --no-upgrade install -y;
52 | }
53 |
54 | function upgrade_packages()
55 | {
56 | list_packages | xargs apt-get install -y;
57 | }
58 |
59 | function install_or_upgrade()
60 | {
61 | P=${1}
62 | PARAN=${P:-"install"}
63 |
64 | if [[ $EUID -ne 0 ]]; then
65 | cat <<-EOF >&2
66 | You must run this script with root privilege
67 | Please do:
68 | sudo $WORK_DIR/${0##*/} $PARAN
69 | EOF
70 | exit 1
71 | else
72 |
73 | apt-get update
74 |
75 | # Install the basic compilation dependencies and other required libraries of this project
76 | if [ "$PARAN" == "install" ]; then
77 | install_packages;
78 | else
79 | upgrade_packages;
80 | fi
81 |
82 | # cleaning downloaded packages from apt-get cache
83 | apt-get clean
84 |
85 | exit 0
86 | fi
87 | }
88 |
89 | # Handle command argument
90 | case "$1" in
91 | install) install_or_upgrade;;
92 | upgrade) install_or_upgrade "upgrade";;
93 | list) list_packages;;
94 | help|"") usage_message;;
95 | *) wrong_command "$1";;
96 | esac
97 |
--------------------------------------------------------------------------------
/utility/install_python_dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | WORK_DIR="$(dirname "$0")"
4 | PROJECT_DIR="$(dirname "$WORK_DIR")"
5 |
6 | pip --version >/dev/null 2>&1 || {
7 | echo >&2 -e "\npip is required but it's not installed."
8 | echo >&2 -e "You can install it by running the following command:\n"
9 | echo >&2 "wget https://bootstrap.pypa.io/get-pip.py --output-document=get-pip.py; chmod +x get-pip.py; sudo -H python3 get-pip.py"
10 | echo >&2 -e "\n"
11 | echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/"
12 | exit 1;
13 | }
14 |
15 | virtualenv --version >/dev/null 2>&1 || {
16 | echo >&2 -e "\nvirtualenv is required but it's not installed."
17 | echo >&2 -e "You can install it by running the following command:\n"
18 | echo >&2 "sudo -H pip3 install virtualenv"
19 | echo >&2 -e "\n"
20 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/"
21 | exit 1;
22 | }
23 |
24 | if [ -z "$VIRTUAL_ENV" ]; then
25 | echo >&2 -e "\nYou need activate a virtualenv first"
26 | echo >&2 -e 'If you do not have a virtualenv created, run the following command to create and automatically activate a new virtualenv named "venv" on current folder:\n'
27 | echo >&2 -e "virtualenv venv --python=\`which python3\`"
28 | echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n"
29 | echo >&2 "deactivate"
30 | echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n"
31 | echo >&2 "source venv/bin/activate"
32 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/"
33 | echo >&2 -e "\n"
34 | exit 1;
35 | else
36 |
37 | pip install -r $PROJECT_DIR/requirements/local.txt
38 |
39 | fi
40 |
--------------------------------------------------------------------------------
/utility/requirements-bionic.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Bionic 18.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | libgraphviz-dev
24 |
--------------------------------------------------------------------------------
/utility/requirements-buster.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Jessie 10.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | libgraphviz-dev
24 |
--------------------------------------------------------------------------------
/utility/requirements-jessie.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Jessie 8.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/utility/requirements-stretch.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Jessie 9.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/utility/requirements-trusty.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Trusty 14.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff4-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms1-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/utility/requirements-xenial.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Xenial 16.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------