diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..bc22ecb24
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "uv"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..7bca521b0
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,20 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ smoke-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: astral-sh/setup-uv@v7
+
+ - name: Install dependencies
+ run: uv sync --locked
+
+ - name: Run Django check
+ run: uv run manage.py check
diff --git a/.gitignore b/.gitignore
index 1769fbd75..82e5b9469 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,9 @@
*.db
+*.sqlite3
*.pyc
*~
-.*
-env
+.venv
+venv
# Accept these files in the repository
!.gitignore
diff --git a/.python-version b/.python-version
new file mode 100644
index 000000000..24ee5b1be
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.13
diff --git a/Procfile b/Procfile
index c09a55210..95717e567 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-web: python manage.py runserver 0.0.0.0:$PORT --noreload
+web: gunicorn tutorial.wsgi --log-file -
diff --git a/README.md b/README.md
index 0b0116d76..de291ac4c 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,6 @@
Source code for the [Django REST framework tutorial][tut].
-[tut]: http://tomchristie.github.com/django-rest-framework/tutorial/1-serialization
\ No newline at end of file
+[tut]: http://www.django-rest-framework.org/tutorial/1-serialization
+
+[](https://www.heroku.com/deploy?template=https://github.com/encode/rest-framework-tutorial)
diff --git a/app.json b/app.json
new file mode 100644
index 000000000..1bf63d632
--- /dev/null
+++ b/app.json
@@ -0,0 +1,25 @@
+{
+ "name": "rest-framework-tutorial",
+ "scripts": {
+ "postdeploy": "python manage.py migrate && ./restore.sh"
+ },
+ "env": {
+ "ENVIRONMENT": "production",
+ "SECRET_KEY": {
+ "generator": "secret"
+ }
+ },
+ "formation": {
+ "web": {
+ "quantity": 1
+ }
+ },
+ "addons": [
+ "heroku-postgresql"
+ ],
+ "buildpacks": [
+ {
+ "url": "heroku/python"
+ }
+ ]
+}
diff --git a/manage.py b/manage.py
index 02401187d..a1a766399 100755
--- a/manage.py
+++ b/manage.py
@@ -1,22 +1,22 @@
#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
import os
import sys
-if __name__ == "__main__":
+
+def main():
+ """Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tutorial.settings")
try:
from django.core.management import execute_from_command_line
- except ImportError:
- # The above import may fail for some other reason. Ensure that the
- # issue is really that Django is missing to avoid masking other
- # exceptions on Python 2.
- try:
- import django
- except ImportError:
- raise ImportError(
- "Couldn't import Django. Are you sure it's installed and "
- "available on your PYTHONPATH environment variable? Did you "
- "forget to activate a virtual environment?"
- )
- raise
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
execute_from_command_line(sys.argv)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..179c9f8d0
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,19 @@
+[project]
+name = "rest-framework-tutorial"
+version = "1.0.0"
+description = "The Django REST framework tutorial. 🎓"
+readme = "README.md"
+requires-python = "==3.13.*"
+dependencies = [
+ "dj-database-url==3.1.2",
+ "django==6.0.5",
+ "djangorestframework==3.17.1",
+ "drf-spectacular==0.29.0",
+ "gunicorn==26.0.0",
+ "inflection==0.5.1",
+ "markdown==3.10.2",
+ "psycopg[binary]==3.3.4",
+ "pygments==2.20.0",
+ "pyyaml==6.0.3",
+ "whitenoise==6.12.0",
+]
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 0c15cb8dd..000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-djangorestframework==3.5.3
-Django==1.10.2
-Pygments==2.0.2
-Markdown==2.6.3
-
-# heroku
-# run these commands to setup heroku properly
-# heroku create --stack cedar --buildpack git://github.com/heroku/heroku-buildpack-python.git
-# heroku config:add BUILDPACK_URL=git@github.com:heroku/heroku-buildpack-python.git#purge
-# heroku config:set HEROKU=1
-psycopg2==2.6.1
-dj-database-url==0.3.0
diff --git a/restore.sh b/restore.sh
new file mode 100755
index 000000000..f2ead5c98
--- /dev/null
+++ b/restore.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+echo "==> Removing all data from the database..."
+python manage.py flush --noinput
+
+echo "==> Loading user fixtures..."
+python manage.py loaddata snippets/fixtures/users.json
+
+echo "==> Loading snippets fixtures..."
+python manage.py loaddata snippets/fixtures/snippets.json
+
+echo "==> Done!"
diff --git a/snippets/apps.py b/snippets/apps.py
new file mode 100644
index 000000000..c598a6743
--- /dev/null
+++ b/snippets/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class SnippetsConfig(AppConfig):
+ name = 'snippets'
diff --git a/snippets/fixtures/snippets.json b/snippets/fixtures/snippets.json
new file mode 100644
index 000000000..97f17bab5
--- /dev/null
+++ b/snippets/fixtures/snippets.json
@@ -0,0 +1,16 @@
+[
+{
+ "model": "snippets.snippet",
+ "pk": 1,
+ "fields": {
+ "created": "2018-01-04T18:09:36.612Z",
+ "title": "serializers.py",
+ "code": "from django.contrib.auth.models import User, Group\r\nfrom rest_framework import serializers\r\n\r\n\r\nclass UserSerializer(serializers.HyperlinkedModelSerializer):\r\n class Meta:\r\n model = User\r\n fields = ('url', 'username', 'email', 'groups')\r\n\r\n\r\nclass GroupSerializer(serializers.HyperlinkedModelSerializer):\r\n class Meta:\r\n model = Group\r\n fields = ('url', 'name')",
+ "linenos": false,
+ "language": "python",
+ "style": "default",
+ "owner": 1,
+ "highlighted": "\n\n\n
\n serializers.py\n \n \n\n\nserializers.py
\n\nfrom django.contrib.auth.models import User, Group\nfrom rest_framework import serializers\n\n\nclass UserSerializer(serializers.HyperlinkedModelSerializer):\n class Meta:\n model = User\n fields = ('url', 'username', 'email', 'groups')\n\n\nclass GroupSerializer(serializers.HyperlinkedModelSerializer):\n class Meta:\n model = Group\n fields = ('url', 'name')\n \n\n\n"
+ }
+}
+]
diff --git a/snippets/fixtures/users.json b/snippets/fixtures/users.json
index 04fe50492..af0cb04a9 100644
--- a/snippets/fixtures/users.json
+++ b/snippets/fixtures/users.json
@@ -1,74 +1,20 @@
[
- {
- "pk": 2,
- "model": "auth.user",
- "fields": {
- "username": "aziz",
- "first_name": "",
- "last_name": "",
- "is_active": true,
- "is_superuser": false,
- "is_staff": false,
- "last_login": "2012-10-28T21:36:57.799Z",
- "groups": [],
- "user_permissions": [],
- "password": "pbkdf2_sha256$10000$2x3LOiFCb9sq$3jiN4zgb3wYHQ9fpn/elUrcn1S1Bq/q0djZFBjb8Y2w=",
- "email": "",
- "date_joined": "2012-10-28T21:36:57.799Z"
- }
- },
- {
- "pk": 3,
- "model": "auth.user",
- "fields": {
- "username": "amy",
- "first_name": "",
- "last_name": "",
- "is_active": true,
- "is_superuser": false,
- "is_staff": false,
- "last_login": "2012-10-28T21:42:34.081Z",
- "groups": [],
- "user_permissions": [],
- "password": "pbkdf2_sha256$10000$wjy2WvTgw1Ro$oR8pVFHIBqZUmD9XEHdRl5m4TxWOF+qnvZn/q+npTlc=",
- "email": "",
- "date_joined": "2012-10-28T21:42:34.081Z"
- }
- },
- {
- "pk": 4,
- "model": "auth.user",
- "fields": {
- "username": "max",
- "first_name": "",
- "last_name": "",
- "is_active": true,
- "is_superuser": false,
- "is_staff": false,
- "last_login": "2012-10-28T21:42:45.711Z",
- "groups": [],
- "user_permissions": [],
- "password": "pbkdf2_sha256$10000$NhDNt8GNYfjC$0FlcsJxt8Sac0dvGv+xcaUVSUfh1UYZfWEq3C1WoHjc=",
- "email": "",
- "date_joined": "2012-10-28T21:42:45.711Z"
- }
- },
- {
- "pk": 5,
- "model": "auth.user",
- "fields": {
- "username": "jose",
- "first_name": "",
- "last_name": "",
- "is_active": true,
- "is_superuser": false,
- "is_staff": false,
- "last_login": "2012-10-28T21:43:03.591Z",
- "groups": [],
- "user_permissions": [],
- "password": "pbkdf2_sha256$10000$ibnHXtPQKwBN$Kab9tTROT6bpxnAI1imgb0kdlAmBuAQ5Z0fAzHbkL68=",
- "email": "",
- "date_joined": "2012-10-28T21:43:03.591Z"
- }
+ {
+ "model": "auth.user",
+ "pk": 1,
+ "fields": {
+ "password": "pbkdf2_sha256$36000$OLyqJOZgL7U6$B6ejXjtO0A6f3GWKZ+v9FYSoR6qFuajeG4MLmOK8oug=",
+ "last_login": null,
+ "is_superuser": true,
+ "username": "admin",
+ "first_name": "",
+ "last_name": "",
+ "email": "admin@example.com",
+ "is_staff": true,
+ "is_active": true,
+ "date_joined": "2017-12-16T17:42:43.941Z",
+ "groups": [],
+ "user_permissions": []
}
+ }
]
diff --git a/snippets/migrations/0001_initial.py b/snippets/migrations/0001_initial.py
new file mode 100644
index 000000000..3884b2bdd
--- /dev/null
+++ b/snippets/migrations/0001_initial.py
@@ -0,0 +1,32 @@
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Snippet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('title', models.CharField(blank=True, default="", max_length=100)),
+ ('code', models.TextField()),
+ ('linenos', models.BooleanField(default=False)),
+ ('language', models.CharField(choices=[(b'abap', b'ABAP'), (b'abnf', b'ABNF'), (b'ada', b'Ada'), (b'adl', b'ADL'), (b'agda', b'Agda'), (b'aheui', b'Aheui'), (b'ahk', b'autohotkey'), (b'alloy', b'Alloy'), (b'ampl', b'Ampl'), (b'antlr', b'ANTLR'), (b'antlr-as', b'ANTLR With ActionScript Target'), (b'antlr-cpp', b'ANTLR With CPP Target'), (b'antlr-csharp', b'ANTLR With C# Target'), (b'antlr-java', b'ANTLR With Java Target'), (b'antlr-objc', b'ANTLR With ObjectiveC Target'), (b'antlr-perl', b'ANTLR With Perl Target'), (b'antlr-python', b'ANTLR With Python Target'), (b'antlr-ruby', b'ANTLR With Ruby Target'), (b'apacheconf', b'ApacheConf'), (b'apl', b'APL'), (b'applescript', b'AppleScript'), (b'arduino', b'Arduino'), (b'as', b'ActionScript'), (b'as3', b'ActionScript 3'), (b'aspectj', b'AspectJ'), (b'aspx-cs', b'aspx-cs'), (b'aspx-vb', b'aspx-vb'), (b'asy', b'Asymptote'), (b'at', b'AmbientTalk'), (b'autoit', b'AutoIt'), (b'awk', b'Awk'), (b'basemake', b'Base Makefile'), (b'bash', b'Bash'), (b'bat', b'Batchfile'), (b'bbcode', b'BBCode'), (b'bc', b'BC'), (b'befunge', b'Befunge'), (b'bib', b'BibTeX'), (b'blitzbasic', b'BlitzBasic'), (b'blitzmax', b'BlitzMax'), (b'bnf', b'BNF'), (b'boo', b'Boo'), (b'boogie', b'Boogie'), (b'brainfuck', b'Brainfuck'), (b'bro', b'Bro'), (b'bst', b'BST'), (b'bugs', b'BUGS'), (b'c', b'C'), (b'c-objdump', b'c-objdump'), (b'ca65', b'ca65 assembler'), (b'cadl', b'cADL'), (b'camkes', b'CAmkES'), (b'capdl', b'CapDL'), (b'capnp', b"Cap'n Proto"), (b'cbmbas', b'CBM BASIC V2'), (b'ceylon', b'Ceylon'), (b'cfc', b'Coldfusion CFC'), (b'cfengine3', b'CFEngine3'), (b'cfm', b'Coldfusion HTML'), (b'cfs', b'cfstatement'), (b'chai', b'ChaiScript'), (b'chapel', b'Chapel'), (b'cheetah', b'Cheetah'), (b'cirru', b'Cirru'), (b'clay', b'Clay'), (b'clean', b'Clean'), (b'clojure', b'Clojure'), (b'clojurescript', b'ClojureScript'), (b'cmake', b'CMake'), (b'cobol', b'COBOL'), (b'cobolfree', b'COBOLFree'), (b'coffee-script', b'CoffeeScript'), (b'common-lisp', b'Common Lisp'), (b'componentpascal', b'Component Pascal'), (b'console', b'Bash Session'), (b'control', b'Debian Control file'), (b'coq', b'Coq'), (b'cpp', b'C++'), (b'cpp-objdump', b'cpp-objdump'), (b'cpsa', b'CPSA'), (b'cr', b'Crystal'), (b'crmsh', b'Crmsh'), (b'croc', b'Croc'), (b'cryptol', b'Cryptol'), (b'csharp', b'C#'), (b'csound', b'Csound Orchestra'), (b'csound-document', b'Csound Document'), (b'csound-score', b'Csound Score'), (b'css', b'CSS'), (b'css+django', b'CSS+Django/Jinja'), (b'css+erb', b'CSS+Ruby'), (b'css+genshitext', b'CSS+Genshi Text'), (b'css+lasso', b'CSS+Lasso'), (b'css+mako', b'CSS+Mako'), (b'css+mozpreproc', b'CSS+mozpreproc'), (b'css+myghty', b'CSS+Myghty'), (b'css+php', b'CSS+PHP'), (b'css+smarty', b'CSS+Smarty'), (b'cucumber', b'Gherkin'), (b'cuda', b'CUDA'), (b'cypher', b'Cypher'), (b'cython', b'Cython'), (b'd', b'D'), (b'd-objdump', b'd-objdump'), (b'dart', b'Dart'), (b'delphi', b'Delphi'), (b'dg', b'dg'), (b'diff', b'Diff'), (b'django', b'Django/Jinja'), (b'docker', b'Docker'), (b'doscon', b'MSDOS Session'), (b'dpatch', b'Darcs Patch'), (b'dtd', b'DTD'), (b'duel', b'Duel'), (b'dylan', b'Dylan'), (b'dylan-console', b'Dylan session'), (b'dylan-lid', b'DylanLID'), (b'earl-grey', b'Earl Grey'), (b'easytrieve', b'Easytrieve'), (b'ebnf', b'EBNF'), (b'ec', b'eC'), (b'ecl', b'ECL'), (b'eiffel', b'Eiffel'), (b'elixir', b'Elixir'), (b'elm', b'Elm'), (b'emacs', b'EmacsLisp'), (b'erb', b'ERB'), (b'erl', b'Erlang erl session'), (b'erlang', b'Erlang'), (b'evoque', b'Evoque'), (b'extempore', b'xtlang'), (b'ezhil', b'Ezhil'), (b'factor', b'Factor'), (b'fan', b'Fantom'), (b'fancy', b'Fancy'), (b'felix', b'Felix'), (b'fish', b'Fish'), (b'flatline', b'Flatline'), (b'forth', b'Forth'), (b'fortran', b'Fortran'), (b'fortranfixed', b'FortranFixed'), (b'foxpro', b'FoxPro'), (b'fsharp', b'FSharp'), (b'gap', b'GAP'), (b'gas', b'GAS'), (b'genshi', b'Genshi'), (b'genshitext', b'Genshi Text'), (b'glsl', b'GLSL'), (b'gnuplot', b'Gnuplot'), (b'go', b'Go'), (b'golo', b'Golo'), (b'gooddata-cl', b'GoodData-CL'), (b'gosu', b'Gosu'), (b'groff', b'Groff'), (b'groovy', b'Groovy'), (b'gst', b'Gosu Template'), (b'haml', b'Haml'), (b'handlebars', b'Handlebars'), (b'haskell', b'Haskell'), (b'haxeml', b'Hxml'), (b'hexdump', b'Hexdump'), (b'hsail', b'HSAIL'), (b'html', b'HTML'), (b'html+cheetah', b'HTML+Cheetah'), (b'html+django', b'HTML+Django/Jinja'), (b'html+evoque', b'HTML+Evoque'), (b'html+genshi', b'HTML+Genshi'), (b'html+handlebars', b'HTML+Handlebars'), (b'html+lasso', b'HTML+Lasso'), (b'html+mako', b'HTML+Mako'), (b'html+myghty', b'HTML+Myghty'), (b'html+ng2', b'HTML + Angular2'), (b'html+php', b'HTML+PHP'), (b'html+smarty', b'HTML+Smarty'), (b'html+twig', b'HTML+Twig'), (b'html+velocity', b'HTML+Velocity'), (b'http', b'HTTP'), (b'hx', b'Haxe'), (b'hybris', b'Hybris'), (b'hylang', b'Hy'), (b'i6t', b'Inform 6 template'), (b'idl', b'IDL'), (b'idris', b'Idris'), (b'iex', b'Elixir iex session'), (b'igor', b'Igor'), (b'inform6', b'Inform 6'), (b'inform7', b'Inform 7'), (b'ini', b'INI'), (b'io', b'Io'), (b'ioke', b'Ioke'), (b'irc', b'IRC logs'), (b'isabelle', b'Isabelle'), (b'j', b'J'), (b'jags', b'JAGS'), (b'jasmin', b'Jasmin'), (b'java', b'Java'), (b'javascript+mozpreproc', b'Javascript+mozpreproc'), (b'jcl', b'JCL'), (b'jlcon', b'Julia console'), (b'js', b'JavaScript'), (b'js+cheetah', b'JavaScript+Cheetah'), (b'js+django', b'JavaScript+Django/Jinja'), (b'js+erb', b'JavaScript+Ruby'), (b'js+genshitext', b'JavaScript+Genshi Text'), (b'js+lasso', b'JavaScript+Lasso'), (b'js+mako', b'JavaScript+Mako'), (b'js+myghty', b'JavaScript+Myghty'), (b'js+php', b'JavaScript+PHP'), (b'js+smarty', b'JavaScript+Smarty'), (b'jsgf', b'JSGF'), (b'json', b'JSON'), (b'json-object', b'JSONBareObject'), (b'jsonld', b'JSON-LD'), (b'jsp', b'Java Server Page'), (b'julia', b'Julia'), (b'juttle', b'Juttle'), (b'kal', b'Kal'), (b'kconfig', b'Kconfig'), (b'koka', b'Koka'), (b'kotlin', b'Kotlin'), (b'lagda', b'Literate Agda'), (b'lasso', b'Lasso'), (b'lcry', b'Literate Cryptol'), (b'lean', b'Lean'), (b'less', b'LessCss'), (b'lhs', b'Literate Haskell'), (b'lidr', b'Literate Idris'), (b'lighty', b'Lighttpd configuration file'), (b'limbo', b'Limbo'), (b'liquid', b'liquid'), (b'live-script', b'LiveScript'), (b'llvm', b'LLVM'), (b'logos', b'Logos'), (b'logtalk', b'Logtalk'), (b'lsl', b'LSL'), (b'lua', b'Lua'), (b'make', b'Makefile'), (b'mako', b'Mako'), (b'maql', b'MAQL'), (b'mask', b'Mask'), (b'mason', b'Mason'), (b'mathematica', b'Mathematica'), (b'matlab', b'Matlab'), (b'matlabsession', b'Matlab session'), (b'md', b'markdown'), (b'minid', b'MiniD'), (b'modelica', b'Modelica'), (b'modula2', b'Modula-2'), (b'monkey', b'Monkey'), (b'monte', b'Monte'), (b'moocode', b'MOOCode'), (b'moon', b'MoonScript'), (b'mozhashpreproc', b'mozhashpreproc'), (b'mozpercentpreproc', b'mozpercentpreproc'), (b'mql', b'MQL'), (b'mscgen', b'Mscgen'), (b'mupad', b'MuPAD'), (b'mxml', b'MXML'), (b'myghty', b'Myghty'), (b'mysql', b'MySQL'), (b'nasm', b'NASM'), (b'ncl', b'NCL'), (b'nemerle', b'Nemerle'), (b'nesc', b'nesC'), (b'newlisp', b'NewLisp'), (b'newspeak', b'Newspeak'), (b'ng2', b'Angular2'), (b'nginx', b'Nginx configuration file'), (b'nim', b'Nimrod'), (b'nit', b'Nit'), (b'nixos', b'Nix'), (b'nsis', b'NSIS'), (b'numpy', b'NumPy'), (b'nusmv', b'NuSMV'), (b'objdump', b'objdump'), (b'objdump-nasm', b'objdump-nasm'), (b'objective-c', b'Objective-C'), (b'objective-c++', b'Objective-C++'), (b'objective-j', b'Objective-J'), (b'ocaml', b'OCaml'), (b'octave', b'Octave'), (b'odin', b'ODIN'), (b'ooc', b'Ooc'), (b'opa', b'Opa'), (b'openedge', b'OpenEdge ABL'), (b'pacmanconf', b'PacmanConf'), (b'pan', b'Pan'), (b'parasail', b'ParaSail'), (b'pawn', b'Pawn'), (b'perl', b'Perl'), (b'perl6', b'Perl6'), (b'php', b'PHP'), (b'pig', b'Pig'), (b'pike', b'Pike'), (b'pkgconfig', b'PkgConfig'), (b'plpgsql', b'PL/pgSQL'), (b'postgresql', b'PostgreSQL SQL dialect'), (b'postscript', b'PostScript'), (b'pot', b'Gettext Catalog'), (b'pov', b'POVRay'), (b'powershell', b'PowerShell'), (b'praat', b'Praat'), (b'prolog', b'Prolog'), (b'properties', b'Properties'), (b'protobuf', b'Protocol Buffer'), (b'ps1con', b'PowerShell Session'), (b'psql', b'PostgreSQL console (psql)'), (b'pug', b'Pug'), (b'puppet', b'Puppet'), (b'py3tb', b'Python 3.0 Traceback'), (b'pycon', b'Python console session'), (b'pypylog', b'PyPy Log'), (b'pytb', b'Python Traceback'), (b'python', b'Python'), (b'python3', b'Python 3'), (b'qbasic', b'QBasic'), (b'qml', b'QML'), (b'qvto', b'QVTO'), (b'racket', b'Racket'), (b'ragel', b'Ragel'), (b'ragel-c', b'Ragel in C Host'), (b'ragel-cpp', b'Ragel in CPP Host'), (b'ragel-d', b'Ragel in D Host'), (b'ragel-em', b'Embedded Ragel'), (b'ragel-java', b'Ragel in Java Host'), (b'ragel-objc', b'Ragel in Objective C Host'), (b'ragel-ruby', b'Ragel in Ruby Host'), (b'raw', b'Raw token data'), (b'rb', b'Ruby'), (b'rbcon', b'Ruby irb session'), (b'rconsole', b'RConsole'), (b'rd', b'Rd'), (b'rebol', b'REBOL'), (b'red', b'Red'), (b'redcode', b'Redcode'), (b'registry', b'reg'), (b'resource', b'ResourceBundle'), (b'rexx', b'Rexx'), (b'rhtml', b'RHTML'), (b'rnc', b'Relax-NG Compact'), (b'roboconf-graph', b'Roboconf Graph'), (b'roboconf-instances', b'Roboconf Instances'), (b'robotframework', b'RobotFramework'), (b'rql', b'RQL'), (b'rsl', b'RSL'), (b'rst', b'reStructuredText'), (b'rts', b'TrafficScript'), (b'rust', b'Rust'), (b'sas', b'SAS'), (b'sass', b'Sass'), (b'sc', b'SuperCollider'), (b'scala', b'Scala'), (b'scaml', b'Scaml'), (b'scheme', b'Scheme'), (b'scilab', b'Scilab'), (b'scss', b'SCSS'), (b'shen', b'Shen'), (b'silver', b'Silver'), (b'slim', b'Slim'), (b'smali', b'Smali'), (b'smalltalk', b'Smalltalk'), (b'smarty', b'Smarty'), (b'sml', b'Standard ML'), (b'snobol', b'Snobol'), (b'snowball', b'Snowball'), (b'sourceslist', b'Debian Sourcelist'), (b'sp', b'SourcePawn'), (b'sparql', b'SPARQL'), (b'spec', b'RPMSpec'), (b'splus', b'S'), (b'sql', b'SQL'), (b'sqlite3', b'sqlite3con'), (b'squidconf', b'SquidConf'), (b'ssp', b'Scalate Server Page'), (b'stan', b'Stan'), (b'stata', b'Stata'), (b'swift', b'Swift'), (b'swig', b'SWIG'), (b'systemverilog', b'systemverilog'), (b'tads3', b'TADS 3'), (b'tap', b'TAP'), (b'tasm', b'TASM'), (b'tcl', b'Tcl'), (b'tcsh', b'Tcsh'), (b'tcshcon', b'Tcsh Session'), (b'tea', b'Tea'), (b'termcap', b'Termcap'), (b'terminfo', b'Terminfo'), (b'terraform', b'Terraform'), (b'tex', b'TeX'), (b'text', b'Text only'), (b'thrift', b'Thrift'), (b'todotxt', b'Todotxt'), (b'trac-wiki', b'MoinMoin/Trac Wiki markup'), (b'treetop', b'Treetop'), (b'ts', b'TypeScript'), (b'tsql', b'Transact-SQL'), (b'turtle', b'Turtle'), (b'twig', b'Twig'), (b'typoscript', b'TypoScript'), (b'typoscriptcssdata', b'TypoScriptCssData'), (b'typoscripthtmldata', b'TypoScriptHtmlData'), (b'urbiscript', b'UrbiScript'), (b'vala', b'Vala'), (b'vb.net', b'VB.net'), (b'vcl', b'VCL'), (b'vclsnippets', b'VCLSnippets'), (b'vctreestatus', b'VCTreeStatus'), (b'velocity', b'Velocity'), (b'verilog', b'verilog'), (b'vgl', b'VGL'), (b'vhdl', b'vhdl'), (b'vim', b'VimL'), (b'wdiff', b'WDiff'), (b'whiley', b'Whiley'), (b'x10', b'X10'), (b'xml', b'XML'), (b'xml+cheetah', b'XML+Cheetah'), (b'xml+django', b'XML+Django/Jinja'), (b'xml+erb', b'XML+Ruby'), (b'xml+evoque', b'XML+Evoque'), (b'xml+lasso', b'XML+Lasso'), (b'xml+mako', b'XML+Mako'), (b'xml+myghty', b'XML+Myghty'), (b'xml+php', b'XML+PHP'), (b'xml+smarty', b'XML+Smarty'), (b'xml+velocity', b'XML+Velocity'), (b'xquery', b'XQuery'), (b'xslt', b'XSLT'), (b'xtend', b'Xtend'), (b'xul+mozpreproc', b'XUL+mozpreproc'), (b'yaml', b'YAML'), (b'yaml+jinja', b'YAML+Jinja'), (b'zephir', b'Zephir')], default=b'python', max_length=100)),
+ ('style', models.CharField(choices=[(b'abap', b'abap'), (b'algol', b'algol'), (b'algol_nu', b'algol_nu'), (b'arduino', b'arduino'), (b'autumn', b'autumn'), (b'borland', b'borland'), (b'bw', b'bw'), (b'colorful', b'colorful'), (b'default', b'default'), (b'emacs', b'emacs'), (b'friendly', b'friendly'), (b'fruity', b'fruity'), (b'igor', b'igor'), (b'lovelace', b'lovelace'), (b'manni', b'manni'), (b'monokai', b'monokai'), (b'murphy', b'murphy'), (b'native', b'native'), (b'paraiso-dark', b'paraiso-dark'), (b'paraiso-light', b'paraiso-light'), (b'pastie', b'pastie'), (b'perldoc', b'perldoc'), (b'rainbow_dash', b'rainbow_dash'), (b'rrt', b'rrt'), (b'tango', b'tango'), (b'trac', b'trac'), (b'vim', b'vim'), (b'vs', b'vs'), (b'xcode', b'xcode')], default=b'friendly', max_length=100)),
+ ('highlighted', models.TextField()),
+ ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snippets', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ('created',),
+ },
+ ),
+ ]
diff --git a/snippets/migrations/__init__.py b/snippets/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/snippets/models.py b/snippets/models.py
index 0270c8272..3ffd7afb4 100644
--- a/snippets/models.py
+++ b/snippets/models.py
@@ -1,9 +1,8 @@
from django.db import models
-from pygments.lexers import get_all_lexers
-from pygments.styles import get_all_styles
-from pygments.lexers import get_lexer_by_name
-from pygments.formatters.html import HtmlFormatter
from pygments import highlight
+from pygments.formatters.html import HtmlFormatter
+from pygments.lexers import get_all_lexers, get_lexer_by_name
+from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
@@ -15,17 +14,16 @@ class Snippet(models.Model):
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
- language = models.CharField(choices=LANGUAGE_CHOICES,
- default='python',
- max_length=100)
- style = models.CharField(choices=STYLE_CHOICES,
- default='friendly',
- max_length=100)
- owner = models.ForeignKey('auth.User', related_name='snippets')
+ language = models.CharField(
+ choices=LANGUAGE_CHOICES, default='python', max_length=100)
+ style = models.CharField(
+ choices=STYLE_CHOICES, default='friendly', max_length=100)
+ owner = models.ForeignKey(
+ 'auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
class Meta:
- ordering = ('created',)
+ ordering = ('created', )
def save(self, *args, **kwargs):
"""
@@ -35,12 +33,7 @@ def save(self, *args, **kwargs):
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
- formatter = HtmlFormatter(style=self.style, linenos=linenos,
- full=True, **options)
+ formatter = HtmlFormatter(
+ style=self.style, linenos=linenos, full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
-
- # limit the number of instances retained
- snippets = Snippet.objects.all()
- if len(snippets) > 100:
- snippets[0].delete()
diff --git a/snippets/permissions.py b/snippets/permissions.py
index e01b54c31..7510b2806 100644
--- a/snippets/permissions.py
+++ b/snippets/permissions.py
@@ -7,9 +7,10 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
"""
def has_object_permission(self, request, view, obj):
- # Read permissions are allowed to any request
+ # Read permissions are allowed to any request,
+ # so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
- # Write permissions are only allowed to the owner of the snippet
+ # Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
diff --git a/snippets/serializers.py b/snippets/serializers.py
index 1072da2d7..5a754715d 100644
--- a/snippets/serializers.py
+++ b/snippets/serializers.py
@@ -1,20 +1,24 @@
+from django.contrib.auth.models import User
from rest_framework import serializers
+
from snippets.models import Snippet
-from django.contrib.auth.models import User
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
- highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
+ highlight = serializers.HyperlinkedIdentityField(
+ view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
- fields = ('url', 'highlight', 'owner',
- 'title', 'code', 'linenos', 'language', 'style')
-
+ fields = ('url', 'id', 'highlight', 'owner', 'title', 'code',
+ 'linenos', 'language', 'style')
+
+
class UserSerializer(serializers.HyperlinkedModelSerializer):
- snippets = serializers.HyperlinkedRelatedField(queryset=Snippet.objects.all(), view_name='snippet-detail', many=True)
+ snippets = serializers.HyperlinkedRelatedField(
+ many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
- fields = ('url', 'username', 'snippets')
+ fields = ('url', 'id', 'username', 'snippets')
diff --git a/snippets/urls.py b/snippets/urls.py
new file mode 100644
index 000000000..5ad33a1a0
--- /dev/null
+++ b/snippets/urls.py
@@ -0,0 +1,15 @@
+from django.urls import include, path
+from rest_framework.routers import DefaultRouter
+
+from snippets import views
+
+# Create a router and register our viewsets with it.
+router = DefaultRouter()
+router.register(r'snippets', views.SnippetViewSet)
+router.register(r'users', views.UserViewSet)
+
+# The API URLs are now determined automatically by the router.
+# Additionally, we include the login URLs for the browsable API.
+urlpatterns = [
+ path('', include(router.urls))
+]
diff --git a/snippets/views.py b/snippets/views.py
index 32d551821..33c9aa9a2 100644
--- a/snippets/views.py
+++ b/snippets/views.py
@@ -1,9 +1,8 @@
from django.contrib.auth.models import User
-from rest_framework import permissions
-from rest_framework import renderers
-from rest_framework import viewsets
-from rest_framework.decorators import detail_route
+from rest_framework import permissions, renderers, viewsets
+from rest_framework.decorators import action
from rest_framework.response import Response
+
from snippets.models import Snippet
from snippets.permissions import IsOwnerOrReadOnly
from snippets.serializers import SnippetSerializer, UserSerializer
@@ -11,36 +10,29 @@
class SnippetViewSet(viewsets.ModelViewSet):
"""
- This endpoint presents code snippets.
-
- The `highlight` field presents a hyperlink to the highlighted HTML
- representation of the code snippet.
-
- The **owner** of the code snippet may update or delete instances
- of the code snippet.
+ This viewset automatically provides `list`, `create`, `retrieve`,
+ `update` and `destroy` actions.
- Try it yourself by logging in as one of these four users: **amy**, **max**,
- **jose** or **aziz**. The passwords are the same as the usernames.
+ Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
- permission_classes = (permissions.IsAuthenticatedOrReadOnly,
- IsOwnerOrReadOnly,)
+ permission_classes = (
+ permissions.IsAuthenticatedOrReadOnly,
+ IsOwnerOrReadOnly, )
- @detail_route(renderer_classes=(renderers.StaticHTMLRenderer,))
+ @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
-
+
+
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
- This endpoint presents the users in the system.
-
- As you can see, the collection of snippet instances owned by a user are
- serialized using a hyperlinked representation.
+ This viewset automatically provides `list` and `detail` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
diff --git a/tutorial/asgi.py b/tutorial/asgi.py
new file mode 100644
index 000000000..ec8a88069
--- /dev/null
+++ b/tutorial/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for tutorial project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/stable/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tutorial.settings")
+
+application = get_asgi_application()
diff --git a/tutorial/settings.py b/tutorial/settings.py
index 8baa99dd3..feb2a9c1f 100644
--- a/tutorial/settings.py
+++ b/tutorial/settings.py
@@ -1,32 +1,31 @@
"""
Django settings for tutorial project.
-Generated by 'django-admin startproject' using Django 1.10.2.
-
For more information on this file, see
-https://docs.djangoproject.com/en/1.10/topics/settings/
+https://docs.djangoproject.com/en/stable/topics/settings/
For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.10/ref/settings/
+https://docs.djangoproject.com/en/stable/ref/settings/
"""
import os
+import dj_database_url
+
+ENVIRONMENT = os.getenv('ENVIRONMENT', 'development')
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
+# See https://docs.djangoproject.com/en/stable/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'us%!@&39o250e79)l!4*0ac4oquo+^nm83vp#y%mw9i$7)i&fy'
+SECRET_KEY = '^+%!mnsrflamqkz!)dv&7kxt%zct-^)_c41+e59nxx$#%r0z*u'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = []
-
+ALLOWED_HOSTS = ['*']
# Application definition
@@ -36,19 +35,16 @@
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
+ 'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
'rest_framework',
+ 'drf_spectacular',
+ 'snippets.apps.SnippetsConfig',
]
-REST_FRAMEWORK = {
- 'DEFAULT_PERMISSION_CLASSES': [
- 'rest_framework.permissions.IsAdminUser',
- ],
- 'PAGE_SIZE': 10
-}
-
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
+ 'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -77,52 +73,80 @@
WSGI_APPLICATION = 'tutorial.wsgi.application'
-
# Database
-# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
+# https://docs.djangoproject.com/en/stable/ref/settings/#databases
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
- }
+ 'default':
+ dj_database_url.config(
+ default='sqlite:///{}'.format(os.path.join(BASE_DIR, 'db.sqlite3'))
+ )
}
-
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Password validation
-# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
+# https://docs.djangoproject.com/en/stable/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ 'NAME':
+ 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'NAME':
+ 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ 'NAME':
+ 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ 'NAME':
+ 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
-
# Internationalization
-# https://docs.djangoproject.com/en/1.10/topics/i18n/
+# https://docs.djangoproject.com/en/stable/topics/i18n/
LANGUAGE_CODE = 'en-us'
+USE_TZ = True
TIME_ZONE = 'UTC'
USE_I18N = True
-USE_L10N = True
-
-USE_TZ = True
-
# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.10/howto/static-files/
+# https://docs.djangoproject.com/en/stable/howto/static-files/
STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
+
+# Django REST Framework
+REST_FRAMEWORK = {
+ 'PAGE_SIZE': 10,
+ 'DEFAULT_PAGINATION_CLASS':
+ 'rest_framework.pagination.PageNumberPagination',
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
+}
+
+# DRF Spectacular settings
+SPECTACULAR_SETTINGS = {
+ 'TITLE': 'Pastebin API',
+ 'DESCRIPTION': 'A Web API for creating and viewing highlighted code snippets.',
+ 'VERSION': '1.0.0',
+ 'SERVE_INCLUDE_SCHEMA': False,
+}
+
+if ENVIRONMENT == 'production':
+ DEBUG = False
+ SECRET_KEY = os.getenv('SECRET_KEY')
+ SESSION_COOKIE_SECURE = True
+ SECURE_BROWSER_XSS_FILTER = True
+ SECURE_CONTENT_TYPE_NOSNIFF = True
+ SECURE_HSTS_INCLUDE_SUBDOMAINS = True
+ SECURE_HSTS_SECONDS = 31536000
+ SECURE_REDIRECT_EXEMPT = []
+ SECURE_SSL_REDIRECT = True
+ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
diff --git a/tutorial/urls.py b/tutorial/urls.py
index 18d3c7392..3a5330e09 100644
--- a/tutorial/urls.py
+++ b/tutorial/urls.py
@@ -1,21 +1,9 @@
-"""tutorial URL Configuration
-
-The `urlpatterns` list routes URLs to views. For more information please see:
- https://docs.djangoproject.com/en/1.10/topics/http/urls/
-Examples:
-Function views
- 1. Add an import: from my_app import views
- 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
-Class-based views
- 1. Add an import: from other_app.views import Home
- 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
-Including another URLconf
- 1. Import the include() function: from django.conf.urls import url, include
- 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
-"""
-from django.conf.urls import url
-from django.contrib import admin
+from django.urls import include, path
+from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
- url(r'^admin/', admin.site.urls),
+ path('', include('snippets.urls')),
+ path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
+ path('schema/', SpectacularAPIView.as_view(), name='schema'),
+ path('docs/', SpectacularSwaggerView.as_view(url_name='schema'))
]
diff --git a/tutorial/wsgi.py b/tutorial/wsgi.py
index ab06b3813..6e3564966 100644
--- a/tutorial/wsgi.py
+++ b/tutorial/wsgi.py
@@ -4,7 +4,7 @@
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
-https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
+https://docs.djangoproject.com/en/stable/howto/deployment/wsgi/
"""
import os
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 000000000..65c52b2f8
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,323 @@
+version = 1
+revision = 3
+requires-python = "==3.13.*"
+
+[[package]]
+name = "asgiref"
+version = "3.11.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "dj-database-url"
+version = "3.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/03/f6/00b625e9d371b980aa261011d0dc906a16444cb688f94215e0dc86996eb5/dj_database_url-3.1.2.tar.gz", hash = "sha256:63c20e4bbaa51690dfd4c8d189521f6bf6bc9da9fcdb23d95d2ee8ee87f9ec62", size = 11490, upload-time = "2026-02-19T15:30:23.638Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/a9/57c66006373381f1d3e5bd94216f1d371228a89f443d3030e010f73dd198/dj_database_url-3.1.2-py3-none-any.whl", hash = "sha256:544e015fee3efa5127a1eb1cca465f4ace578265b3671fe61d0ed7dbafb5ec8a", size = 8953, upload-time = "2026-02-19T15:30:39.37Z" },
+]
+
+[[package]]
+name = "django"
+version = "6.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "asgiref" },
+ { name = "sqlparse" },
+ { name = "tzdata", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5e/f1/bf85f0d29ef76abf901f193fe8fef4769d3da7794197832bc30151c071d8/django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269", size = 10924131, upload-time = "2026-05-05T13:54:39.329Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/5b/1328f8b84fce040c404f76822bf8c57d254e368e8cbd8bd67ec2b26d75f5/django-6.0.5-py3-none-any.whl", hash = "sha256:9d58a7cb49244e74c8e161d5e403a46d6209f1009ba40f5a66d6aa0d0786a8f0", size = 8368680, upload-time = "2026-05-05T13:54:33.532Z" },
+]
+
+[[package]]
+name = "djangorestframework"
+version = "3.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5a/e1/2c516bdc83652b1a60c6119366ac2c0607b479ed05cd6093f916ca8928f8/djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457", size = 898844, upload-time = "2026-03-24T16:58:31.845Z" },
+]
+
+[[package]]
+name = "drf-spectacular"
+version = "0.29.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+ { name = "djangorestframework" },
+ { name = "inflection" },
+ { name = "jsonschema" },
+ { name = "pyyaml" },
+ { name = "uritemplate" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5e/0e/a4f50d83e76cbe797eda88fc0083c8ca970cfa362b5586359ef06ec6f70a/drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc", size = 241722, upload-time = "2025-11-02T03:40:26.348Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/d9/502c56fc3ca960075d00956283f1c44e8cafe433dada03f9ed2821f3073b/drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a", size = 105433, upload-time = "2025-11-02T03:40:24.823Z" },
+]
+
+[[package]]
+name = "gunicorn"
+version = "26.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" },
+]
+
+[[package]]
+name = "inflection"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091, upload-time = "2020-08-22T08:16:29.139Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
+]
+
+[[package]]
+name = "psycopg"
+version = "3.3.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "tzdata", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
+]
+
+[package.optional-dependencies]
+binary = [
+ { name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
+]
+
+[[package]]
+name = "psycopg-binary"
+version = "3.3.4"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/09/43/13e9c406fbbf354580476e248a16b64802a376873ebe6339e30bb655572d/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7", size = 4590377, upload-time = "2026-05-01T23:29:18.782Z" },
+ { url = "https://files.pythonhosted.org/packages/22/be/2923cd7c3683e7afdecf4f10796a18de02f5c5ddc0969aa2ad0a8cdd3bbd/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31", size = 4669023, upload-time = "2026-05-01T23:29:25.884Z" },
+ { url = "https://files.pythonhosted.org/packages/96/a0/2c913d6fe13d6a8bd13597d36739bf47af063ad9399e402cfecab16f3c1e/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70", size = 5467423, upload-time = "2026-05-01T23:29:33.416Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/38/205d10bc1ad0df4a21c5c51659126bd3ea0ef98fcad1e852f78c249bb9c3/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3", size = 5151137, upload-time = "2026-05-01T23:29:42.013Z" },
+ { url = "https://files.pythonhosted.org/packages/36/fc/f0381ddcd45eff3bb70dbca6823a996048d7f507b2ec3fc92c6fabc0fe87/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c", size = 6736671, upload-time = "2026-05-01T23:29:51.626Z" },
+ { url = "https://files.pythonhosted.org/packages/95/40/fa545ae152c24327651e5624e4902121e808270be36c10b12e9939be09bc/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae", size = 4979601, upload-time = "2026-05-01T23:29:56.961Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e4/2f8a47ee97f90cd2b933d0463081d35631ff419de2b8c984a5f369857de0/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc", size = 4510513, upload-time = "2026-05-01T23:30:07.243Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/0e/94e842ff4a7f98ed162580ca2e8b8864b28c1e0350f2443f8ee47f821167/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf", size = 4187243, upload-time = "2026-05-01T23:30:15.352Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/83/fc6c174b672e29b7de996ea77b6cbddf46c891751c3355f6974292baa6b4/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260", size = 3927347, upload-time = "2026-05-01T23:30:21.186Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/65/768364d4a97a15b1a7f47ba52688c1686f22941d8332a8398cefc468e25f/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf", size = 4236393, upload-time = "2026-05-01T23:30:26.211Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/3b/218efbc9e645becd80cdf651acda05f85cfe546b7a9c0458c7cbc8fe1f74/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38", size = 3564592, upload-time = "2026-05-01T23:30:31.764Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "rest-framework-tutorial"
+version = "1.0.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "dj-database-url" },
+ { name = "django" },
+ { name = "djangorestframework" },
+ { name = "drf-spectacular" },
+ { name = "gunicorn" },
+ { name = "inflection" },
+ { name = "markdown" },
+ { name = "psycopg", extra = ["binary"] },
+ { name = "pygments" },
+ { name = "pyyaml" },
+ { name = "whitenoise" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "dj-database-url", specifier = "==3.1.2" },
+ { name = "django", specifier = "==6.0.5" },
+ { name = "djangorestframework", specifier = "==3.17.1" },
+ { name = "drf-spectacular", specifier = "==0.29.0" },
+ { name = "gunicorn", specifier = "==26.0.0" },
+ { name = "inflection", specifier = "==0.5.1" },
+ { name = "markdown", specifier = "==3.10.2" },
+ { name = "psycopg", extras = ["binary"], specifier = "==3.3.4" },
+ { name = "pygments", specifier = "==2.20.0" },
+ { name = "pyyaml", specifier = "==6.0.3" },
+ { name = "whitenoise", specifier = "==6.12.0" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.30.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
+ { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
+ { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
+ { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
+ { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
+ { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
+ { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
+ { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
+]
+
+[[package]]
+name = "sqlparse"
+version = "0.5.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
+]
+
+[[package]]
+name = "uritemplate"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" },
+]
+
+[[package]]
+name = "whitenoise"
+version = "6.12.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" },
+]