├── .env.example ├── .github └── workflows │ └── code-quality-checks.yml ├── .gitignore ├── .python-version ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docker-compose.yml ├── justfile ├── manage.py ├── modd.conf ├── pyproject.toml ├── starminder ├── __init__.py ├── asgi.py ├── core │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_userprofile.py │ │ ├── 0003_default_site.py │ │ ├── 0004_github_allauth.py │ │ ├── 0005_userprofile_created_at_userprofile_modified_at.py │ │ ├── 0006_configuration.py │ │ ├── 0007_userprofile_active_userprofile_day_userprofile_hour_and_more.py │ │ ├── 0008_remove_userprofile_active.py │ │ ├── 0009_remove_userprofile_day_remove_userprofile_hour.py │ │ ├── 0010_userprofile_day_userprofile_hour.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── base.html │ │ ├── dashboard.html │ │ ├── home.html │ │ └── settings.html │ ├── test_temp.py │ └── views.py ├── settings.py ├── urls.py └── wsgi.py └── uv.lock /.env.example: -------------------------------------------------------------------------------- 1 | SITE_DOMAIN_NAME= 2 | SITE_DISPLAY_NAME= 3 | APP_NAME=DJANGO_SECRET_KEY= 4 | 5 | DB_PASSWORD= 6 | DB_USER= 7 | DB_NAME= 8 | 9 | GITHUB_CLIENT_ID= 10 | GITHUB_CLIENT_SECRET= 11 | 12 | ADMIN_PREFIX= -------------------------------------------------------------------------------- /.github/workflows/code-quality-checks.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | 13 | continue-on-error: true 14 | 15 | steps: 16 | - uses: taiki-e/install-action@just 17 | 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install the latest version of uv 21 | uses: astral-sh/setup-uv@v5 22 | 23 | - name: Install dependencies 24 | run: uv sync 25 | 26 | - name: Check formatting via ruff 27 | run: uv run just formatcheck 28 | 29 | - name: Check type hints via mypy 30 | run: uv run just typecheck 31 | 32 | - name: Run linter via ruff 33 | run: uv run just lint 34 | 35 | - name: Run tests via pytest 36 | run: uv run just test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python,git,macos,vim 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,git,macos,vim 4 | 5 | ### Git ### 6 | # Created by git for backups. To disable backups in Git: 7 | # $ git config --global mergetool.keepBackup false 8 | *.orig 9 | 10 | # Created by git when using merge tools for conflicts 11 | *.BACKUP.* 12 | *.BASE.* 13 | *.LOCAL.* 14 | *.REMOTE.* 15 | *_BACKUP_*.txt 16 | *_BASE_*.txt 17 | *_LOCAL_*.txt 18 | *_REMOTE_*.txt 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### Python ### 50 | # Byte-compiled / optimized / DLL files 51 | __pycache__/ 52 | *.py[cod] 53 | *$py.class 54 | 55 | # C extensions 56 | *.so 57 | 58 | # Distribution / packaging 59 | .Python 60 | build/ 61 | develop-eggs/ 62 | dist/ 63 | downloads/ 64 | eggs/ 65 | .eggs/ 66 | lib/ 67 | lib64/ 68 | parts/ 69 | sdist/ 70 | var/ 71 | wheels/ 72 | share/python-wheels/ 73 | *.egg-info/ 74 | .installed.cfg 75 | *.egg 76 | MANIFEST 77 | 78 | # PyInstaller 79 | # Usually these files are written by a python script from a template 80 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 81 | *.manifest 82 | *.spec 83 | 84 | # Installer logs 85 | pip-log.txt 86 | pip-delete-this-directory.txt 87 | 88 | # Unit test / coverage reports 89 | htmlcov/ 90 | .tox/ 91 | .nox/ 92 | .coverage 93 | .coverage.* 94 | .cache 95 | nosetests.xml 96 | coverage.xml 97 | *.cover 98 | *.py,cover 99 | .hypothesis/ 100 | .pytest_cache/ 101 | cover/ 102 | 103 | # Translations 104 | *.mo 105 | *.pot 106 | 107 | # Django stuff: 108 | *.log 109 | local_settings.py 110 | db.sqlite3 111 | db.sqlite3-journal 112 | 113 | # Flask stuff: 114 | instance/ 115 | .webassets-cache 116 | 117 | # Scrapy stuff: 118 | .scrapy 119 | 120 | # Sphinx documentation 121 | docs/_build/ 122 | 123 | # PyBuilder 124 | .pybuilder/ 125 | target/ 126 | 127 | # Jupyter Notebook 128 | .ipynb_checkpoints 129 | 130 | # IPython 131 | profile_default/ 132 | ipython_config.py 133 | 134 | # pyenv 135 | # For a library or package, you might want to ignore these files since the code is 136 | # intended to run in multiple environments; otherwise, check them in: 137 | # .python-version 138 | 139 | # pipenv 140 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 141 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 142 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 143 | # install all needed dependencies. 144 | #Pipfile.lock 145 | 146 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 147 | __pypackages__/ 148 | 149 | # Celery stuff 150 | celerybeat-schedule 151 | celerybeat.pid 152 | 153 | # SageMath parsed files 154 | *.sage.py 155 | 156 | # Environments 157 | .env 158 | .venv 159 | env/ 160 | venv/ 161 | ENV/ 162 | env.bak/ 163 | venv.bak/ 164 | 165 | # Spyder project settings 166 | .spyderproject 167 | .spyproject 168 | 169 | # Rope project settings 170 | .ropeproject 171 | 172 | # mkdocs documentation 173 | /site 174 | 175 | # mypy 176 | .mypy_cache/ 177 | .dmypy.json 178 | dmypy.json 179 | 180 | # Pyre type checker 181 | .pyre/ 182 | 183 | # pytype static type analyzer 184 | .pytype/ 185 | 186 | # Cython debug symbols 187 | cython_debug/ 188 | 189 | ### Vim ### 190 | # Swap 191 | [._]*.s[a-v][a-z] 192 | !*.svg # comment out if you don't need vector files 193 | [._]*.sw[a-p] 194 | [._]s[a-rt-v][a-z] 195 | [._]ss[a-gi-z] 196 | [._]sw[a-p] 197 | 198 | # Session 199 | Session.vim 200 | Sessionx.vim 201 | 202 | # Temporary 203 | .netrwhist 204 | *~ 205 | # Auto-generated tag files 206 | tags 207 | # Persistent undo 208 | [._]*.un~ 209 | 210 | # End of https://www.toptal.com/developers/gitignore/api/python,git,macos,vim 211 | 212 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13.3 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Starminder Changelog 2 | 3 | 4 | 12 | 13 | 14 | ## [Unreleased] 15 | 16 | ### Removed 17 | - Nearly everything, in preparation for v3. 18 | 19 | 20 | ## [2.0.0] - 2025-01-28 21 | 22 | ### Added 23 | - New script based version 24 | 25 | ### Removed 26 | - Delete _all_ the files in prep for v2! 27 | 28 | 29 | ## [1.0.8] - 2019-10-15 30 | 31 | ### Changed 32 | - Mailing address in the email templates 33 | 34 | 35 | ## [1.0.7] - 2018-01-31 36 | 37 | ### Added 38 | - Unsubscribe info and mailing address to the email templates 39 | 40 | ### Changed 41 | - Python runtime from 3.6.2 to 3.6.4 42 | 43 | 44 | ## [1.0.6] - 2017-08-21 45 | 46 | ### Added 47 | - Placeholders for number and email on the main form 48 | 49 | 50 | ## [1.0.5] - 2017-08-14 51 | 52 | ### Changed 53 | - Italicized testimonial authors. 54 | 55 | 56 | ## [1.0.4] - 2017-08-14 57 | 58 | ### Added 59 | - Release dates to the changelog 60 | - Fourth fake testimonial 61 | 62 | ### Removed 63 | - First release link from the changelog because it's silly 64 | 65 | 66 | ## [1.0.3] - 2017-08-01 67 | 68 | ### Fixed 69 | - Sentry client in the notifier script was misused, causing an error, which should no longer happen. 70 | 71 | 72 | ## [1.0.2] - 2017-08-01 73 | 74 | ### Changed 75 | - Fixed bad release linking in the changelog. 76 | - Fixed improper heading level for a section in the changelog. 77 | 78 | 79 | ## [1.0.1] - 2017-07-31 80 | 81 | ### Changed 82 | - Switched from `ConfigParser` to `SafeConfigParser`. 83 | 84 | 85 | ## [1.0.0] - 2017-07-31 86 | 87 | ### Added 88 | - Error tracking (for both the web app and notifier job) via Sentry 89 | - Constants are now tracked in `defaults.ini`. 90 | 91 | ### Fixed 92 | - Dead session 500s (user logs in, server gets restartet, uses saves/deletes) 93 | 94 | 95 | ## [0.3.0] - 2017-07-27 96 | 97 | ### Updated 98 | - Better `User.__repr__` (`` instead of ``) 99 | - User count in the footer is pluralized only when `!= 1`. 100 | - Email field on the form is now blank instead of showing "None" when there's nothing in the DB. 101 | 102 | 103 | ## [0.2.0] - 2017-07-27 104 | 105 | ### Added 106 | - Version output 107 | - `LICENSE` 108 | - `CHANGELOG.md` 109 | - `CODE_OF_CONDUCT.md` 110 | - `CONTRIBUTING.md` 111 | - `ISSUE_TEMPLATE.md` 112 | - `PULL_REQUEST_TEMPLATE.md` 113 | 114 | ### Updated 115 | - `README.rst` 116 | 117 | 118 | ## 0.1.0 - 2017-07-26 119 | 120 | ### Added 121 | - First version suitable for public consumption. 122 | 123 | 124 | [Unreleased]: https://github.com/nkantar/Starminder/compare/2.0.0...HEAD 125 | [2.0.0]: https://github.com/nkantar/Starminder/compare/1.0.8...2.0.0 126 | [1.0.8]: https://github.com/nkantar/Starminder/compare/1.0.7...1.0.8 127 | [1.0.7]: https://github.com/nkantar/Starminder/compare/1.0.6...1.0.7 128 | [1.0.6]: https://github.com/nkantar/Starminder/compare/1.0.5...1.0.6 129 | [1.0.5]: https://github.com/nkantar/Starminder/compare/1.0.4...1.0.5 130 | [1.0.4]: https://github.com/nkantar/Starminder/compare/1.0.3...1.0.4 131 | [1.0.3]: https://github.com/nkantar/Starminder/compare/1.0.2...1.0.3 132 | [1.0.2]: https://github.com/nkantar/Starminder/compare/1.0.1...1.0.2 133 | [1.0.1]: https://github.com/nkantar/Starminder/compare/1.0.0...1.0.1 134 | [1.0.0]: https://github.com/nkantar/Starminder/compare/0.3.0...1.0.0 135 | [0.3.0]: https://github.com/nkantar/Starminder/compare/0.2.0...0.3.0 136 | [0.2.0]: https://github.com/nkantar/Starminder/compare/0.1.0...0.2.0 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Nik Kantar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Do not use this at the moment. 2 | 3 | 4 | --- 5 | 6 | 7 | # Starminder 8 | 9 | _**Starminder**_ is a GitHub starred project reminder. ⭐ 10 | 11 | If you have any number of starred projects, you probably have very little idea what's in there. _Starminder_ aims to periodically remind you of some random ones, so you don't forget about them entirely. 12 | 13 | 14 | ## Contributing 15 | 16 | Unlike most of my projects, contributions are not explicitly encouraged, though they're not discouraged, either. 17 | 18 | 19 | ## License 20 | 21 | [MIT](https://choosealicense.com/licenses/mit/) 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | db: 5 | image: postgres 6 | restart: always 7 | environment: 8 | POSTGRES_PASSWORD: ${DB_PASSWORD} 9 | POSTGRES_USER: ${DB_USER} 10 | POSTGRES_DB: ${DB_NAME} 11 | ports: 12 | - "5432:5432" 13 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # NOTE Some of the commands assume uv is present. 2 | 3 | 4 | # ============================================================================== 5 | # general 6 | 7 | # run dev modd config 8 | dev: 9 | modd 10 | 11 | # run PostgreSQL via Docker compose 12 | docker-compose: 13 | docker compose up 14 | 15 | # run Django dev server 16 | serve: 17 | uv run python manage.py runserver 18 | 19 | 20 | # ============================================================================== 21 | # code quality 22 | 23 | # check formatting via ruff 24 | formatcheck: 25 | uv run ruff format --check . 26 | 27 | # check type hints via mypy 28 | typecheck: 29 | uv run mypy . 30 | 31 | # run linter via ruff 32 | lint: 33 | uv run ruff check . 34 | 35 | # run tests via pytest and coverage 36 | test: 37 | uv run pytest 38 | 39 | # run all checks 40 | checkall: 41 | just formatcheck 42 | just typecheck 43 | just lint 44 | just test 45 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | 4 | import os 5 | import sys 6 | 7 | 8 | def main() -> None: 9 | """Run administrative tasks.""" 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "starminder.settings") 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /modd.conf: -------------------------------------------------------------------------------- 1 | # PostgreSQL via Docker compose 2 | **/docker-compose.yml { 3 | daemon: docker compose up 4 | } 5 | 6 | # Django web server 7 | **/*.py { 8 | daemon: just serve 9 | } 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "Starminder" 3 | version = "3.0.0" 4 | dependencies = [ 5 | "django>=5.1.5", 6 | "django-allauth>=65.3.1", 7 | "psycopg[binary]>=3.2.4", 8 | "python-dotenv>=1.0.1", 9 | "requests>=2.32.3", 10 | ] 11 | requires-python = ">=3.13.1" 12 | authors = [ 13 | {name = "Nik Kantar", email = "nik@nkantar.com" }, 14 | ] 15 | description = "Remind yourself of your GitHub stars" 16 | readme = "README.md" 17 | license = {file = "LICENSE"} 18 | 19 | [dependency-groups] 20 | dev = [ 21 | "django-debug-toolbar>=5.0.1", 22 | "django-stubs>=5.1.2", 23 | "mypy>=1.14.1", 24 | "pdbpp>=0.10.3", 25 | "pydocstyle>=6.3.0", 26 | "pytest>=8.3.4", 27 | "pytest-django>=4.9.0", 28 | "python-lsp-server>=1.12.0", 29 | "ruff>=0.9.3", 30 | ] 31 | 32 | [tool.ruff] 33 | exclude = ["migrations"] 34 | 35 | [tool.ruff.lint] 36 | # select = ["ALL"] 37 | # ignore = [ 38 | # "ANN401", 39 | # "COM812", 40 | # "CPY001", 41 | # "D203", 42 | # "D212", 43 | # "DOC201", 44 | # "DOC501", 45 | # "E402", 46 | # "EM101", 47 | # "FBT001", 48 | # "FIX002", 49 | # "S101", 50 | # "TD002", 51 | # "TD003", 52 | # "TD004", 53 | # "TRY003", 54 | # "RUF012", 55 | # ] 56 | 57 | [tool.mypy] 58 | plugins = ["mypy_django_plugin.main"] 59 | exclude = "migrations" 60 | follow_untyped_imports = true 61 | 62 | [tool.django-stubs] 63 | django_settings_module = "starminder.settings" 64 | 65 | [tool.pytest.ini_options] 66 | DJANGO_SETTINGS_MODULE = "starminder.settings" 67 | python_files = ["test_*.py"] 68 | -------------------------------------------------------------------------------- /starminder/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /starminder/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for starminder project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "starminder.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /starminder/core/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /starminder/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .forms import CustomUserChangeForm, CustomUserCreationForm 5 | from .models import CustomUser, UserProfile 6 | 7 | 8 | class UserProfileInline(admin.StackedInline): 9 | model = UserProfile 10 | can_delete = False 11 | verbose_name_plural = "User Profile" # TODO is this wrong? 12 | 13 | 14 | class CustomUserAdmin(UserAdmin): 15 | add_form = CustomUserCreationForm 16 | form = CustomUserChangeForm 17 | model = CustomUser 18 | list_display = ["email", "username"] 19 | inlines = [UserProfileInline] 20 | 21 | 22 | admin.site.register(CustomUser, CustomUserAdmin) 23 | admin.site.register(UserProfile) 24 | -------------------------------------------------------------------------------- /starminder/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "starminder.core" 7 | -------------------------------------------------------------------------------- /starminder/core/forms.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 4 | from django.forms import ModelForm, Select 5 | 6 | from .models import CustomUser, UserProfile 7 | 8 | 9 | DAY_OPTIONS = [ 10 | "Monday", 11 | "Tuesday", 12 | "Wednesday", 13 | "Thursday", 14 | "Friday", 15 | "Saturday", 16 | "Sunday", 17 | "day", 18 | ] 19 | 20 | 21 | class CustomUserCreationForm(UserCreationForm): 22 | class Meta: 23 | model = CustomUser 24 | fields = ("username", "email") 25 | 26 | 27 | class CustomUserChangeForm(UserChangeForm): 28 | class Meta: 29 | model = CustomUser 30 | fields = ("username", "email") 31 | 32 | 33 | class SettingsForm(ModelForm): 34 | class Meta: 35 | model = UserProfile 36 | fields = ["day", "hour", "maximum"] 37 | widgets = { 38 | "day": Select( 39 | choices=tuple([(idx, day) for idx, day in enumerate(DAY_OPTIONS)]), 40 | ), 41 | "hour": Select(choices=tuple([(hour, f"{hour}:00") for hour in range(24)])), 42 | } 43 | -------------------------------------------------------------------------------- /starminder/core/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-29 03:43 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | import django.utils.timezone 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | initial = True 11 | 12 | dependencies = [ 13 | ("auth", "0012_alter_user_first_name_max_length"), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="CustomUser", 19 | fields=[ 20 | ( 21 | "id", 22 | models.BigAutoField( 23 | auto_created=True, 24 | primary_key=True, 25 | serialize=False, 26 | verbose_name="ID", 27 | ), 28 | ), 29 | ("password", models.CharField(max_length=128, verbose_name="password")), 30 | ( 31 | "last_login", 32 | models.DateTimeField( 33 | blank=True, null=True, verbose_name="last login" 34 | ), 35 | ), 36 | ( 37 | "is_superuser", 38 | models.BooleanField( 39 | default=False, 40 | help_text="Designates that this user has all permissions without explicitly assigning them.", 41 | verbose_name="superuser status", 42 | ), 43 | ), 44 | ( 45 | "username", 46 | models.CharField( 47 | error_messages={ 48 | "unique": "A user with that username already exists." 49 | }, 50 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 51 | max_length=150, 52 | unique=True, 53 | validators=[ 54 | django.contrib.auth.validators.UnicodeUsernameValidator() 55 | ], 56 | verbose_name="username", 57 | ), 58 | ), 59 | ( 60 | "first_name", 61 | models.CharField( 62 | blank=True, max_length=150, verbose_name="first name" 63 | ), 64 | ), 65 | ( 66 | "last_name", 67 | models.CharField( 68 | blank=True, max_length=150, verbose_name="last name" 69 | ), 70 | ), 71 | ( 72 | "email", 73 | models.EmailField( 74 | blank=True, max_length=254, verbose_name="email address" 75 | ), 76 | ), 77 | ( 78 | "is_staff", 79 | models.BooleanField( 80 | default=False, 81 | help_text="Designates whether the user can log into this admin site.", 82 | verbose_name="staff status", 83 | ), 84 | ), 85 | ( 86 | "is_active", 87 | models.BooleanField( 88 | default=True, 89 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 90 | verbose_name="active", 91 | ), 92 | ), 93 | ( 94 | "date_joined", 95 | models.DateTimeField( 96 | default=django.utils.timezone.now, verbose_name="date joined" 97 | ), 98 | ), 99 | ( 100 | "groups", 101 | models.ManyToManyField( 102 | blank=True, 103 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 104 | related_name="user_set", 105 | related_query_name="user", 106 | to="auth.group", 107 | verbose_name="groups", 108 | ), 109 | ), 110 | ( 111 | "user_permissions", 112 | models.ManyToManyField( 113 | blank=True, 114 | help_text="Specific permissions for this user.", 115 | related_name="user_set", 116 | related_query_name="user", 117 | to="auth.permission", 118 | verbose_name="user permissions", 119 | ), 120 | ), 121 | ], 122 | options={ 123 | "verbose_name": "user", 124 | "verbose_name_plural": "users", 125 | "abstract": False, 126 | }, 127 | managers=[ 128 | ("objects", django.contrib.auth.models.UserManager()), 129 | ], 130 | ), 131 | ] 132 | -------------------------------------------------------------------------------- /starminder/core/migrations/0002_userprofile.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-29 03:44 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 | dependencies = [ 10 | ("core", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="UserProfile", 16 | fields=[ 17 | ( 18 | "id", 19 | models.BigAutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ( 27 | "user", 28 | models.OneToOneField( 29 | on_delete=django.db.models.deletion.CASCADE, 30 | to=settings.AUTH_USER_MODEL, 31 | ), 32 | ), 33 | ], 34 | options={ 35 | "verbose_name": "User Profile", 36 | "verbose_name_plural": "User Profiles", 37 | }, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /starminder/core/migrations/0003_default_site.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import migrations 3 | 4 | 5 | DEFAULT_SITE_DOMAIN_NAME = "example.com" 6 | DEFAULT_SITE_DISPLAY_NAME = "example.com" 7 | 8 | 9 | def update_default_site(apps, site_domain_name, site_display_name): 10 | Site = apps.get_model("sites", "Site") 11 | site = Site.objects.get(pk=settings.SITE_ID) 12 | site.domain = site_domain_name 13 | site.name = site_display_name 14 | site.save() 15 | 16 | 17 | def update_default_site_forward(apps, schema_editor): 18 | update_default_site( 19 | apps=apps, 20 | site_domain_name=settings.SITE_DOMAIN_NAME, 21 | site_display_name=settings.SITE_DISPLAY_NAME, 22 | ) 23 | 24 | 25 | def reverse_update_default_site(apps, schema_editor): 26 | update_default_site( 27 | apps=apps, 28 | site_domain_name=DEFAULT_SITE_DOMAIN_NAME, 29 | site_display_name=DEFAULT_SITE_DISPLAY_NAME, 30 | ) 31 | 32 | 33 | class Migration(migrations.Migration): 34 | dependencies = [ 35 | ("core", "0002_userprofile"), 36 | ("sites", "0002_alter_domain_unique"), 37 | ] 38 | 39 | operations = [ 40 | migrations.RunPython( 41 | update_default_site_forward, 42 | reverse_code=reverse_update_default_site, 43 | ) 44 | ] 45 | -------------------------------------------------------------------------------- /starminder/core/migrations/0004_github_allauth.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import migrations 3 | 4 | 5 | PROVIDER_NAME = "github" 6 | 7 | 8 | def create_github_social_app(apps, schema_editor): 9 | SocialApp = apps.get_model("socialaccount", "SocialApp") 10 | app = SocialApp( 11 | provider=PROVIDER_NAME, 12 | name=settings.APP_NAME, 13 | client_id=settings.GITHUB_CLIENT_ID, 14 | secret=settings.GITHUB_CLIENT_SECRET, 15 | ) 16 | app.save() 17 | app.sites.set([settings.SITE_ID]) 18 | app.save() 19 | 20 | 21 | def delete_github_social_app(apps, schema_editor): 22 | # SocialApp = apps.get_model("socialaccount", "SocialApp") 23 | # app = SocialApp.objects.get(name=settings.APP_NAME) 24 | # app.delete() 25 | pass # TODO why isn't this working? 26 | 27 | 28 | class Migration(migrations.Migration): 29 | dependencies = [ 30 | ("core", "0003_default_site"), 31 | ] 32 | 33 | operations = [ 34 | migrations.RunPython( 35 | create_github_social_app, 36 | reverse_code=delete_github_social_app, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /starminder/core/migrations/0005_userprofile_created_at_userprofile_modified_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-30 04:05 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('core', '0004_github_allauth'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='userprofile', 16 | name='created_at', 17 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 18 | preserve_default=False, 19 | ), 20 | migrations.AddField( 21 | model_name='userprofile', 22 | name='modified_at', 23 | field=models.DateTimeField(auto_now=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /starminder/core/migrations/0006_configuration.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-30 04:30 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("core", "0005_userprofile_created_at_userprofile_modified_at"), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Configuration", 15 | fields=[ 16 | ( 17 | "id", 18 | models.BigAutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("created_at", models.DateTimeField(auto_now_add=True)), 26 | ("modified_at", models.DateTimeField(auto_now=True)), 27 | ( 28 | "day", 29 | models.CharField( 30 | choices=[ 31 | ("Monday", "Monday"), 32 | ("Tuesday", "Tuesday"), 33 | ("Wednesday", "Wednesday"), 34 | ("Thursday", "Thursday"), 35 | ("Friday", "Friday"), 36 | ("Saturday", "Saturday"), 37 | ("Sunday", "Sunday"), 38 | ("day", "Day"), 39 | ], 40 | default="day", 41 | max_length=9, 42 | ), 43 | ), 44 | ( 45 | "hour", 46 | models.IntegerField( 47 | choices=[ 48 | (0, "Zero"), 49 | (1, "One"), 50 | (2, "Two"), 51 | (3, "Three"), 52 | (4, "Four"), 53 | (5, "Five"), 54 | (6, "Six"), 55 | (7, "Seven"), 56 | (8, "Eight"), 57 | (9, "Nine"), 58 | (10, "Ten"), 59 | (11, "Eleven"), 60 | (12, "Twelve"), 61 | (13, "Thirteen"), 62 | (14, "Fourteen"), 63 | (15, "Fifteen"), 64 | (16, "Sixteen"), 65 | (17, "Seventeen"), 66 | (18, "Eighteen"), 67 | (19, "Nineteen"), 68 | (20, "Twenty"), 69 | (21, "Twentyone"), 70 | (22, "Twentytwo"), 71 | (23, "Twentythree"), 72 | ], 73 | default=0, 74 | ), 75 | ), 76 | ("maximum", models.PositiveIntegerField(default=5)), 77 | ("active", models.BooleanField(default=True)), 78 | ( 79 | "profile", 80 | models.ForeignKey( 81 | on_delete=django.db.models.deletion.CASCADE, 82 | to="core.userprofile", 83 | ), 84 | ), 85 | ], 86 | options={ 87 | "abstract": False, 88 | }, 89 | ), 90 | ] 91 | -------------------------------------------------------------------------------- /starminder/core/migrations/0007_userprofile_active_userprofile_day_userprofile_hour_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-09 19:41 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 | ('core', '0006_configuration'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='active', 18 | field=models.BooleanField(default=True), 19 | ), 20 | migrations.AddField( 21 | model_name='userprofile', 22 | name='day', 23 | field=models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday'), ('day', 'Day')], default='day', max_length=9), 24 | ), 25 | migrations.AddField( 26 | model_name='userprofile', 27 | name='hour', 28 | field=models.IntegerField(choices=[(0, 'Zero'), (1, 'One'), (2, 'Two'), (3, 'Three'), (4, 'Four'), (5, 'Five'), (6, 'Six'), (7, 'Seven'), (8, 'Eight'), (9, 'Nine'), (10, 'Ten'), (11, 'Eleven'), (12, 'Twelve'), (13, 'Thirteen'), (14, 'Fourteen'), (15, 'Fifteen'), (16, 'Sixteen'), (17, 'Seventeen'), (18, 'Eighteen'), (19, 'Nineteen'), (20, 'Twenty'), (21, 'Twentyone'), (22, 'Twentytwo'), (23, 'Twentythree')], default=0), 29 | ), 30 | migrations.AddField( 31 | model_name='userprofile', 32 | name='maximum', 33 | field=models.PositiveIntegerField(default=5), 34 | ), 35 | migrations.AlterField( 36 | model_name='userprofile', 37 | name='user', 38 | field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_profile', to=settings.AUTH_USER_MODEL), 39 | ), 40 | migrations.DeleteModel( 41 | name='Configuration', 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /starminder/core/migrations/0008_remove_userprofile_active.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-09 19:51 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('core', '0007_userprofile_active_userprofile_day_userprofile_hour_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='userprofile', 15 | name='active', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /starminder/core/migrations/0009_remove_userprofile_day_remove_userprofile_hour.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-09 21:36 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('core', '0008_remove_userprofile_active'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='userprofile', 15 | name='day', 16 | ), 17 | migrations.RemoveField( 18 | model_name='userprofile', 19 | name='hour', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /starminder/core/migrations/0010_userprofile_day_userprofile_hour.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-09 21:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('core', '0009_remove_userprofile_day_remove_userprofile_hour'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='userprofile', 15 | name='day', 16 | field=models.PositiveIntegerField(default=0), 17 | ), 18 | migrations.AddField( 19 | model_name='userprofile', 20 | name='hour', 21 | field=models.PositiveIntegerField(default=0), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /starminder/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkantar/Starminder/99394d62d0947a3c843cd3a5f84134d2f125d901/starminder/core/migrations/__init__.py -------------------------------------------------------------------------------- /starminder/core/models.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import logging 3 | 4 | from django.contrib.auth.models import AbstractUser 5 | from django.db.models import ( 6 | CASCADE, 7 | DateTimeField, 8 | Model, 9 | OneToOneField, 10 | PositiveIntegerField, 11 | ) 12 | from django.db.models.signals import post_save 13 | from django.dispatch import receiver 14 | 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class TimeStampedModel(Model): 20 | created_at = DateTimeField(auto_now_add=True) 21 | modified_at = DateTimeField(auto_now=True) 22 | 23 | class Meta: 24 | abstract = True 25 | 26 | 27 | class CustomUser(AbstractUser): 28 | user_profile: "UserProfile" 29 | 30 | def __str__(self) -> str: 31 | return self.email 32 | 33 | 34 | class UserProfile(TimeStampedModel): 35 | user = OneToOneField(CustomUser, on_delete=CASCADE, related_name="user_profile") 36 | 37 | day = PositiveIntegerField(default=0) 38 | hour = PositiveIntegerField(default=0) 39 | 40 | maximum = PositiveIntegerField(default=5) 41 | 42 | class Meta: 43 | verbose_name = "User Profile" 44 | verbose_name_plural = "User Profiles" 45 | 46 | def __str__(self) -> str: 47 | return f"Profile for {self.user}" 48 | 49 | 50 | @receiver(post_save, sender=CustomUser) 51 | def create_or_update_user_profile( 52 | sender: CustomUser, 53 | instance: CustomUser, 54 | created: bool, 55 | **kwargs: Any, 56 | ) -> None: 57 | UserProfile.objects.get_or_create(user=instance) 58 | -------------------------------------------------------------------------------- /starminder/core/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load socialaccount %} 2 |

Starminder

3 | 4 | {% if user.is_authenticated %} 5 | Dashboard 6 | Settings 7 | Log Out 8 | 9 |
10 | {% csrf_token %} 11 | 12 | 13 |
14 | {% else %} 15 |
16 | {% csrf_token %} 17 | 18 |
19 | {% endif %} 20 | 21 | {% if user.is_authenticated %} 22 |

Hello, {{ user.username }}.

23 | {% endif %} 24 | 25 | {% block content %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /starminder/core/templates/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 | dashboard 6 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /starminder/core/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 | home 6 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /starminder/core/templates/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |

Remind me every {{ form.day }} at {{ form.hour }} UTC about no more than {{ form.maximum }} projects.

7 | 8 | {% csrf_token %} 9 |
10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /starminder/core/test_temp.py: -------------------------------------------------------------------------------- 1 | def test_temp() -> None: 2 | assert True 3 | -------------------------------------------------------------------------------- /starminder/core/views.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import logging 3 | 4 | 5 | from django.contrib.auth.mixins import LoginRequiredMixin 6 | from django.http import HttpResponseServerError 7 | from django.urls import reverse_lazy 8 | from django.views.generic import FormView, TemplateView 9 | 10 | from .forms import SettingsForm 11 | 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class HomeView(TemplateView): 17 | template_name = "home.html" 18 | 19 | 20 | class DashboardView(LoginRequiredMixin, TemplateView): 21 | template_name = "dashboard.html" 22 | 23 | 24 | class SettingsView(LoginRequiredMixin, FormView): 25 | form_class = SettingsForm 26 | template_name = "settings.html" 27 | success_url = reverse_lazy("dashboard") 28 | 29 | def get_initial(self) -> dict[Any, Any]: 30 | super_data = super().get_initial() 31 | 32 | profile = self.request.user.user_profile # type: ignore[union-attr] 33 | 34 | profile_data = { 35 | "day": profile.day, 36 | "hour": profile.hour, 37 | "maximum": profile.maximum, 38 | } 39 | 40 | return {**super_data, **profile_data} 41 | 42 | def form_valid(self, form): 43 | form.instance.id = self.request.user.user_profile.id 44 | 45 | logger.info(form.is_valid()) 46 | 47 | return super().form_valid(form) 48 | -------------------------------------------------------------------------------- /starminder/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for starminder project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.1.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.1/ref/settings/ 11 | """ 12 | 13 | from os import getenv 14 | from pathlib import Path 15 | 16 | from dotenv import load_dotenv 17 | 18 | load_dotenv() 19 | 20 | 21 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 22 | BASE_DIR = Path(__file__).resolve().parent.parent 23 | 24 | 25 | # Quick-start development settings - unsuitable for production 26 | # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ 27 | 28 | # SECURITY WARNING: keep the secret key used in production secret! 29 | SECRET_KEY = getenv("DJANGO_SECRET_KEY") 30 | 31 | # SECURITY WARNING: don't run with debug turned on in production! 32 | DEBUG = True 33 | 34 | ALLOWED_HOSTS: list[str] = [] 35 | 36 | 37 | # Application definition 38 | 39 | INSTALLED_APPS = [ 40 | "django.contrib.admin", 41 | "django.contrib.auth", 42 | "django.contrib.contenttypes", 43 | "django.contrib.sessions", 44 | "django.contrib.messages", 45 | "django.contrib.staticfiles", 46 | "django.contrib.sites", 47 | # third party 48 | "allauth", 49 | "allauth.account", 50 | "allauth.socialaccount", 51 | "allauth.socialaccount.providers.github", 52 | # project 53 | "starminder.core", 54 | ] 55 | 56 | MIDDLEWARE = [ 57 | "django.middleware.security.SecurityMiddleware", 58 | "django.contrib.sessions.middleware.SessionMiddleware", 59 | "django.middleware.common.CommonMiddleware", 60 | "django.middleware.csrf.CsrfViewMiddleware", 61 | "django.contrib.auth.middleware.AuthenticationMiddleware", 62 | "django.contrib.messages.middleware.MessageMiddleware", 63 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 64 | "allauth.account.middleware.AccountMiddleware", 65 | ] 66 | 67 | ROOT_URLCONF = "starminder.urls" 68 | 69 | TEMPLATES = [ 70 | { 71 | "BACKEND": "django.template.backends.django.DjangoTemplates", 72 | "DIRS": [ 73 | BASE_DIR / "starminder" / "core" / "templates", 74 | ], 75 | "APP_DIRS": True, 76 | "OPTIONS": { 77 | "context_processors": [ 78 | "django.template.context_processors.debug", 79 | "django.template.context_processors.request", 80 | "django.contrib.auth.context_processors.auth", 81 | "django.contrib.messages.context_processors.messages", 82 | ], 83 | }, 84 | }, 85 | ] 86 | 87 | WSGI_APPLICATION = "starminder.wsgi.application" 88 | 89 | 90 | # Database 91 | # https://docs.djangoproject.com/en/5.1/ref/settings/#databases 92 | 93 | DATABASES = { 94 | "default": { 95 | "ENGINE": "django.db.backends.postgresql", 96 | "NAME": getenv("DB_NAME"), 97 | "USER": getenv("DB_USER"), 98 | "PASSWORD": getenv("DB_PASSWORD"), 99 | "HOST": "localhost", 100 | "PORT": 5432, 101 | }, 102 | } 103 | 104 | 105 | # Password validation 106 | # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators 107 | 108 | AUTH_PASSWORD_VALIDATORS = [ 109 | { 110 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 111 | }, 112 | { 113 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 114 | }, 115 | { 116 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 117 | }, 118 | { 119 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 120 | }, 121 | ] 122 | 123 | 124 | # Internationalization 125 | # https://docs.djangoproject.com/en/5.1/topics/i18n/ 126 | 127 | LANGUAGE_CODE = "en-us" 128 | 129 | TIME_ZONE = "UTC" 130 | 131 | USE_I18N = True 132 | 133 | USE_TZ = True 134 | 135 | 136 | # Static files (CSS, JavaScript, Images) 137 | # https://docs.djangoproject.com/en/5.1/howto/static-files/ 138 | 139 | STATIC_URL = "static/" 140 | 141 | # Default primary key field type 142 | # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field 143 | 144 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 145 | 146 | 147 | ########## 148 | 149 | 150 | AUTH_USER_MODEL = "core.CustomUser" 151 | 152 | AUTHENTICATION_BACKENDS = [ 153 | "django.contrib.auth.backends.ModelBackend", 154 | "allauth.account.auth_backends.AuthenticationBackend", 155 | ] 156 | 157 | ACCOUNT_EMAIL_VERIFICATION = "none" 158 | 159 | SITE_ID = 1 160 | SITE_DOMAIN_NAME = getenv("SITE_DOMAIN_NAME") 161 | SITE_DISPLAY_NAME = getenv("SITE_DISPLAY_NAME") 162 | 163 | LOGIN_REDIRECT_URL = "/dashboard" # new 164 | LOGOUT_REDIRECT_URL = "/" 165 | ACCOUNT_LOGOUT_ON_GET = True 166 | 167 | SOCIALACCOUNT_ONLY = True 168 | 169 | APP_NAME = getenv("APP_NAME") 170 | 171 | GITHUB_CLIENT_ID = getenv("GITHUB_CLIENT_ID") 172 | GITHUB_CLIENT_SECRET = getenv("GITHUB_CLIENT_SECRET") 173 | 174 | ADMIN_PREFIX = getenv("ADMIN_PREFIX") 175 | 176 | LOGGING = { 177 | "version": 1, 178 | "disable_existing_loggers": False, 179 | "handlers": { 180 | "console": { 181 | "class": "logging.StreamHandler", 182 | }, 183 | }, 184 | "root": { 185 | "handlers": ["console"], 186 | "level": "DEBUG", 187 | }, 188 | } 189 | -------------------------------------------------------------------------------- /starminder/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import admin 3 | from django.urls import include, path 4 | 5 | # from starminder.core.views import DashboardView, HomeView, SettingsView 6 | from starminder.core.views import DashboardView, HomeView 7 | 8 | urlpatterns = [ 9 | path(f"{settings.ADMIN_PREFIX}admin/", admin.site.urls), 10 | path("accounts/", include("allauth.urls")), 11 | path("dashboard/", DashboardView.as_view(), name="dashboard"), 12 | # path("settings/", SettingsView.as_view(), name="settings"), 13 | path("", HomeView.as_view(), name="home"), 14 | ] 15 | -------------------------------------------------------------------------------- /starminder/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for starminder project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "starminder.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.13.1" 3 | 4 | [[package]] 5 | name = "asgiref" 6 | version = "3.8.1" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, 11 | ] 12 | 13 | [[package]] 14 | name = "attrs" 15 | version = "25.1.0" 16 | source = { registry = "https://pypi.org/simple" } 17 | sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } 18 | wheels = [ 19 | { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, 20 | ] 21 | 22 | [[package]] 23 | name = "certifi" 24 | version = "2024.12.14" 25 | source = { registry = "https://pypi.org/simple" } 26 | sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, 29 | ] 30 | 31 | [[package]] 32 | name = "charset-normalizer" 33 | version = "3.4.1" 34 | source = { registry = "https://pypi.org/simple" } 35 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 36 | wheels = [ 37 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 38 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 39 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 40 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 41 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 42 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 43 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 44 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 45 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 46 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 47 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 48 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 49 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 50 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 51 | ] 52 | 53 | [[package]] 54 | name = "colorama" 55 | version = "0.4.6" 56 | source = { registry = "https://pypi.org/simple" } 57 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 58 | wheels = [ 59 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 60 | ] 61 | 62 | [[package]] 63 | name = "django" 64 | version = "5.1.5" 65 | source = { registry = "https://pypi.org/simple" } 66 | dependencies = [ 67 | { name = "asgiref" }, 68 | { name = "sqlparse" }, 69 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 70 | ] 71 | sdist = { url = "https://files.pythonhosted.org/packages/e4/17/834e3e08d590dcc27d4cc3c5cd4e2fb757b7a92bab9de8ee402455732952/Django-5.1.5.tar.gz", hash = "sha256:19bbca786df50b9eca23cee79d495facf55c8f5c54c529d9bf1fe7b5ea086af3", size = 10700031 } 72 | wheels = [ 73 | { url = "https://files.pythonhosted.org/packages/11/e6/e92c8c788b83d109f34d933c5e817095d85722719cb4483472abc135f44e/Django-5.1.5-py3-none-any.whl", hash = "sha256:c46eb936111fffe6ec4bc9930035524a8be98ec2f74d8a0ff351226a3e52f459", size = 8276957 }, 74 | ] 75 | 76 | [[package]] 77 | name = "django-allauth" 78 | version = "65.3.1" 79 | source = { registry = "https://pypi.org/simple" } 80 | dependencies = [ 81 | { name = "asgiref" }, 82 | { name = "django" }, 83 | ] 84 | sdist = { url = "https://files.pythonhosted.org/packages/20/73/3a24b32df7046c5106846c0cfb3309033e2ebe00b08dfbf64c883add253a/django_allauth-65.3.1.tar.gz", hash = "sha256:e02e951b71a2753a746459f2efa114c7c72bf2cef6887dbe8607a577c0350587", size = 1547530 } 85 | 86 | [[package]] 87 | name = "django-debug-toolbar" 88 | version = "5.0.1" 89 | source = { registry = "https://pypi.org/simple" } 90 | dependencies = [ 91 | { name = "django" }, 92 | { name = "sqlparse" }, 93 | ] 94 | sdist = { url = "https://files.pythonhosted.org/packages/93/f6/022cffde1ec6daf3d1418d03a69ddc4f760151f75664dcc9fd2167b2997f/django_debug_toolbar-5.0.1.tar.gz", hash = "sha256:296f6f18a80710e84fbb8361538ae5ec522a75ebe9ab67db34bcf1026cbeb420", size = 295982 } 95 | wheels = [ 96 | { url = "https://files.pythonhosted.org/packages/36/f7/3b406dc871d88c938d2bd967e65acf725b46c4e7c9add6a1f1bbaff9bdcf/django_debug_toolbar-5.0.1-py3-none-any.whl", hash = "sha256:7456cc2e951db37dab335686db7803c4a0ecb6736d120705f6668db9548bf49f", size = 260038 }, 97 | ] 98 | 99 | [[package]] 100 | name = "django-stubs" 101 | version = "5.1.2" 102 | source = { registry = "https://pypi.org/simple" } 103 | dependencies = [ 104 | { name = "asgiref" }, 105 | { name = "django" }, 106 | { name = "django-stubs-ext" }, 107 | { name = "types-pyyaml" }, 108 | { name = "typing-extensions" }, 109 | ] 110 | sdist = { url = "https://files.pythonhosted.org/packages/32/d6/b29debed5527ead981b69cef404f7589cca7b6e4aa65fe3e60a478b4588e/django_stubs-5.1.2.tar.gz", hash = "sha256:a0fcb3659bab46a6d835cc2d9bff3fc29c36ccea41a10e8b1930427bc0f9f0df", size = 267374 } 111 | wheels = [ 112 | { url = "https://files.pythonhosted.org/packages/77/28/137af496de2419ac521b4530f4f6340adbf709befd7d63ce590537c7432a/django_stubs-5.1.2-py3-none-any.whl", hash = "sha256:04ddc778faded6fb48468a8da9e98b8d12b9ba983faa648d37a73ebde0f024da", size = 472598 }, 113 | ] 114 | 115 | [[package]] 116 | name = "django-stubs-ext" 117 | version = "5.1.2" 118 | source = { registry = "https://pypi.org/simple" } 119 | dependencies = [ 120 | { name = "django" }, 121 | { name = "typing-extensions" }, 122 | ] 123 | sdist = { url = "https://files.pythonhosted.org/packages/ed/83/b673bf5131c61949f7840b70f9d25a52d90d27416fe2692f13ade14496f1/django_stubs_ext-5.1.2.tar.gz", hash = "sha256:421c0c3025a68e3ab8e16f065fad9ba93335ecefe2dd92a0cff97a665680266c", size = 9629 } 124 | wheels = [ 125 | { url = "https://files.pythonhosted.org/packages/73/c1/5df5231c5db00e3981e71f295c7e5269cbddb0a2c666b3a6f03831b24bd1/django_stubs_ext-5.1.2-py3-none-any.whl", hash = "sha256:6c559214538d6a26f631ca638ddc3251a0a891d607de8ce01d23d3201ad8ad6c", size = 9032 }, 126 | ] 127 | 128 | [[package]] 129 | name = "docstring-to-markdown" 130 | version = "0.15" 131 | source = { registry = "https://pypi.org/simple" } 132 | sdist = { url = "https://files.pythonhosted.org/packages/7a/ad/6a66abd14676619bd56f6b924c96321a2e2d7d86558841d94a30023eec53/docstring-to-markdown-0.15.tar.gz", hash = "sha256:e146114d9c50c181b1d25505054a8d0f7a476837f0da2c19f07e06eaed52b73d", size = 29246 } 133 | wheels = [ 134 | { url = "https://files.pythonhosted.org/packages/c1/cf/4eee59f6c4111b3e80cc32cf6bac483a90646f5c8693e84496c9855e8e38/docstring_to_markdown-0.15-py3-none-any.whl", hash = "sha256:27afb3faedba81e34c33521c32bbd258d7fbb79eedf7d29bc4e81080e854aec0", size = 21640 }, 135 | ] 136 | 137 | [[package]] 138 | name = "fancycompleter" 139 | version = "0.9.1" 140 | source = { registry = "https://pypi.org/simple" } 141 | dependencies = [ 142 | { name = "pyreadline", marker = "sys_platform == 'win32'" }, 143 | { name = "pyrepl" }, 144 | ] 145 | sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 } 146 | wheels = [ 147 | { url = "https://files.pythonhosted.org/packages/38/ef/c08926112034d017633f693d3afc8343393a035134a29dfc12dcd71b0375/fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080", size = 9681 }, 148 | ] 149 | 150 | [[package]] 151 | name = "idna" 152 | version = "3.10" 153 | source = { registry = "https://pypi.org/simple" } 154 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 155 | wheels = [ 156 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 157 | ] 158 | 159 | [[package]] 160 | name = "iniconfig" 161 | version = "2.0.0" 162 | source = { registry = "https://pypi.org/simple" } 163 | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 164 | wheels = [ 165 | { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, 166 | ] 167 | 168 | [[package]] 169 | name = "jedi" 170 | version = "0.19.2" 171 | source = { registry = "https://pypi.org/simple" } 172 | dependencies = [ 173 | { name = "parso" }, 174 | ] 175 | sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } 176 | wheels = [ 177 | { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, 178 | ] 179 | 180 | [[package]] 181 | name = "mypy" 182 | version = "1.14.1" 183 | source = { registry = "https://pypi.org/simple" } 184 | dependencies = [ 185 | { name = "mypy-extensions" }, 186 | { name = "typing-extensions" }, 187 | ] 188 | sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } 189 | wheels = [ 190 | { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, 191 | { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, 192 | { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, 193 | { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, 194 | { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, 195 | { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, 196 | { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, 197 | ] 198 | 199 | [[package]] 200 | name = "mypy-extensions" 201 | version = "1.0.0" 202 | source = { registry = "https://pypi.org/simple" } 203 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } 204 | wheels = [ 205 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, 206 | ] 207 | 208 | [[package]] 209 | name = "packaging" 210 | version = "24.2" 211 | source = { registry = "https://pypi.org/simple" } 212 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 213 | wheels = [ 214 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 215 | ] 216 | 217 | [[package]] 218 | name = "parso" 219 | version = "0.8.4" 220 | source = { registry = "https://pypi.org/simple" } 221 | sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } 222 | wheels = [ 223 | { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, 224 | ] 225 | 226 | [[package]] 227 | name = "pdbpp" 228 | version = "0.10.3" 229 | source = { registry = "https://pypi.org/simple" } 230 | dependencies = [ 231 | { name = "fancycompleter" }, 232 | { name = "pygments" }, 233 | { name = "wmctrl" }, 234 | ] 235 | sdist = { url = "https://files.pythonhosted.org/packages/1f/a3/c4bd048256fd4b7d28767ca669c505e156f24d16355505c62e6fce3314df/pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5", size = 68116 } 236 | wheels = [ 237 | { url = "https://files.pythonhosted.org/packages/93/ee/491e63a57fffa78b9de1c337b06c97d0cd0753e88c00571c7b011680332a/pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1", size = 23961 }, 238 | ] 239 | 240 | [[package]] 241 | name = "pluggy" 242 | version = "1.5.0" 243 | source = { registry = "https://pypi.org/simple" } 244 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 245 | wheels = [ 246 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 247 | ] 248 | 249 | [[package]] 250 | name = "psycopg" 251 | version = "3.2.4" 252 | source = { registry = "https://pypi.org/simple" } 253 | dependencies = [ 254 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 255 | ] 256 | sdist = { url = "https://files.pythonhosted.org/packages/e0/f2/954b1467b3e2ca5945b83b5e320268be1f4df486c3e8ffc90f4e4b707979/psycopg-3.2.4.tar.gz", hash = "sha256:f26f1346d6bf1ef5f5ef1714dd405c67fb365cfd1c6cea07de1792747b167b92", size = 156109 } 257 | wheels = [ 258 | { url = "https://files.pythonhosted.org/packages/40/49/15114d5f7ee68983f4e1a24d47e75334568960352a07c6f0e796e912685d/psycopg-3.2.4-py3-none-any.whl", hash = "sha256:43665368ccd48180744cab26b74332f46b63b7e06e8ce0775547a3533883d381", size = 198716 }, 259 | ] 260 | 261 | [package.optional-dependencies] 262 | binary = [ 263 | { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, 264 | ] 265 | 266 | [[package]] 267 | name = "psycopg-binary" 268 | version = "3.2.4" 269 | source = { registry = "https://pypi.org/simple" } 270 | wheels = [ 271 | { url = "https://files.pythonhosted.org/packages/25/e2/f56675aada063762f08559b6969e47e1313f269fc1682c16457c13da8186/psycopg_binary-3.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:81ab801c0d35830c876bf0d1edc8e7dd2f73aa2b04fe24eb812159c0b054d149", size = 3846854 }, 272 | { url = "https://files.pythonhosted.org/packages/7b/8b/8c4a66b2b3db494367df0299535b7d2df78f303334228c517b8d00c411d5/psycopg_binary-3.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c09e02ce1124eb6638b3381df050a8cf88aedfad4522f939945cda49050a990c", size = 3932292 }, 273 | { url = "https://files.pythonhosted.org/packages/84/e8/618d45f77cebce73d75497c95685a0902aea3783386d9335ce486c69e13a/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a249cdc6a5c2b5088a8677acba66b291e5237524739ab3d27498e1ef189312f5", size = 4493785 }, 274 | { url = "https://files.pythonhosted.org/packages/c4/87/fc30318e6b97e723e017e7dc88d0f721bbfb749de1a6e414e52d4ac54c9a/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2960ba8a5c0ad75e184f6d8bf76bdf023708999efe75fe4e13445136c1cd206", size = 4304874 }, 275 | { url = "https://files.pythonhosted.org/packages/91/30/1d127e651c21cd77befaf361c7c3b9001bfff51ac38027e8fce598ba0701/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dae2e50b0d3425c167eebbedc3553f7c811dbc0dbfc737b6877f68a03be7daf", size = 4541296 }, 276 | { url = "https://files.pythonhosted.org/packages/0d/5e/22c824cb38745c1c744cec85d227190727c564afb75960ce0057ca15fd84/psycopg_binary-3.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03bf7ee7e0002c2cce43ecb923ec510358056eb2e44a96afaeb0424518f35206", size = 4255756 }, 277 | { url = "https://files.pythonhosted.org/packages/b3/83/ae8783dec3f7e39df8a4056e4d383926ffec531970c0b415d48d9fd4a2c2/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f5c85eeb63b1a8a6b026eef57f5da36ff215ce9a6a3bb8e20a409670d6cfbda", size = 3845918 }, 278 | { url = "https://files.pythonhosted.org/packages/be/f7/fb7bffb0c4c45a5a82fe324e4f7b176075a4c5372e546a038858dd13c7ab/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8c7b95899d4d6d23c5cc46cb3419e8e6ca68d867509432ee1487042564a1ea55", size = 3315429 }, 279 | { url = "https://files.pythonhosted.org/packages/81/a3/29f4993a239d6a3fb18ef8681d9990c007f5f73bdd2e21f65f07ac55ad6f/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fa4acea9ca20a567c3872a5afab2084751530bb57b8fb6b52820d5c54e7c8c3b", size = 3399388 }, 280 | { url = "https://files.pythonhosted.org/packages/25/5b/925171cbfa2e3d1ccb7f4c005d0d5db609ba796c1d08a23c42825b09c554/psycopg_binary-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c487f35a1905bb15da927c1fc05f70f3d29f0e21fb4ba21d360a0da9c755f20", size = 3436702 }, 281 | { url = "https://files.pythonhosted.org/packages/b6/47/25b2b85b8fcabf99bfa92b4b0d587894c01576bf0b2bf137c243d1eb1070/psycopg_binary-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:80297c3a9f7b5a6afdb0d8f220661ccd796e5c9128c44b32c41267f7daefd37f", size = 2779196 }, 282 | ] 283 | 284 | [[package]] 285 | name = "pydocstyle" 286 | version = "6.3.0" 287 | source = { registry = "https://pypi.org/simple" } 288 | dependencies = [ 289 | { name = "snowballstemmer" }, 290 | ] 291 | sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796 } 292 | wheels = [ 293 | { url = "https://files.pythonhosted.org/packages/36/ea/99ddefac41971acad68f14114f38261c1f27dac0b3ec529824ebc739bdaa/pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", size = 38038 }, 294 | ] 295 | 296 | [[package]] 297 | name = "pygments" 298 | version = "2.19.1" 299 | source = { registry = "https://pypi.org/simple" } 300 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 303 | ] 304 | 305 | [[package]] 306 | name = "pyreadline" 307 | version = "2.1" 308 | source = { registry = "https://pypi.org/simple" } 309 | sdist = { url = "https://files.pythonhosted.org/packages/bc/7c/d724ef1ec3ab2125f38a1d53285745445ec4a8f19b9bb0761b4064316679/pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1", size = 109189 } 310 | 311 | [[package]] 312 | name = "pyrepl" 313 | version = "0.9.0" 314 | source = { registry = "https://pypi.org/simple" } 315 | sdist = { url = "https://files.pythonhosted.org/packages/05/1b/ea40363be0056080454cdbabe880773c3c5bd66d7b13f0c8b8b8c8da1e0c/pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775", size = 48744 } 316 | 317 | [[package]] 318 | name = "pytest" 319 | version = "8.3.4" 320 | source = { registry = "https://pypi.org/simple" } 321 | dependencies = [ 322 | { name = "colorama", marker = "sys_platform == 'win32'" }, 323 | { name = "iniconfig" }, 324 | { name = "packaging" }, 325 | { name = "pluggy" }, 326 | ] 327 | sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } 328 | wheels = [ 329 | { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, 330 | ] 331 | 332 | [[package]] 333 | name = "pytest-django" 334 | version = "4.9.0" 335 | source = { registry = "https://pypi.org/simple" } 336 | dependencies = [ 337 | { name = "pytest" }, 338 | ] 339 | sdist = { url = "https://files.pythonhosted.org/packages/02/c0/43c8b2528c24d7f1a48a47e3f7381f5ab2ae8c64634b0c3f4bd843063955/pytest_django-4.9.0.tar.gz", hash = "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314", size = 84067 } 340 | wheels = [ 341 | { url = "https://files.pythonhosted.org/packages/47/fe/54f387ee1b41c9ad59e48fb8368a361fad0600fe404315e31a12bacaea7d/pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99", size = 23723 }, 342 | ] 343 | 344 | [[package]] 345 | name = "python-dotenv" 346 | version = "1.0.1" 347 | source = { registry = "https://pypi.org/simple" } 348 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } 349 | wheels = [ 350 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, 351 | ] 352 | 353 | [[package]] 354 | name = "python-lsp-jsonrpc" 355 | version = "1.1.2" 356 | source = { registry = "https://pypi.org/simple" } 357 | dependencies = [ 358 | { name = "ujson" }, 359 | ] 360 | sdist = { url = "https://files.pythonhosted.org/packages/48/b6/fd92e2ea4635d88966bb42c20198df1a981340f07843b5e3c6694ba3557b/python-lsp-jsonrpc-1.1.2.tar.gz", hash = "sha256:4688e453eef55cd952bff762c705cedefa12055c0aec17a06f595bcc002cc912", size = 15298 } 361 | wheels = [ 362 | { url = "https://files.pythonhosted.org/packages/cb/d9/656659d5b5d5f402b2b174cd0ba9bc827e07ce3c0bf88da65424baf64af8/python_lsp_jsonrpc-1.1.2-py3-none-any.whl", hash = "sha256:7339c2e9630ae98903fdaea1ace8c47fba0484983794d6aafd0bd8989be2b03c", size = 8805 }, 363 | ] 364 | 365 | [[package]] 366 | name = "python-lsp-server" 367 | version = "1.12.0" 368 | source = { registry = "https://pypi.org/simple" } 369 | dependencies = [ 370 | { name = "docstring-to-markdown" }, 371 | { name = "jedi" }, 372 | { name = "pluggy" }, 373 | { name = "python-lsp-jsonrpc" }, 374 | { name = "ujson" }, 375 | ] 376 | sdist = { url = "https://files.pythonhosted.org/packages/2b/15/b7e1577b9ca358e008b06910bf23cfa0a8be130ee9f319a262a3c610ee8d/python_lsp_server-1.12.0.tar.gz", hash = "sha256:b6a336f128da03bd9bac1e61c3acca6e84242b8b31055a1ccf49d83df9dc053b", size = 114328 } 377 | wheels = [ 378 | { url = "https://files.pythonhosted.org/packages/1d/49/37c9659f76dbf1018d88892c14184db36ce9df09ea7d760162584aee8a58/python_lsp_server-1.12.0-py3-none-any.whl", hash = "sha256:2e912c661881d85f67f2076e4e66268b695b62bf127e07e81f58b187d4bb6eda", size = 74782 }, 379 | ] 380 | 381 | [[package]] 382 | name = "requests" 383 | version = "2.32.3" 384 | source = { registry = "https://pypi.org/simple" } 385 | dependencies = [ 386 | { name = "certifi" }, 387 | { name = "charset-normalizer" }, 388 | { name = "idna" }, 389 | { name = "urllib3" }, 390 | ] 391 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 392 | wheels = [ 393 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 394 | ] 395 | 396 | [[package]] 397 | name = "ruff" 398 | version = "0.9.3" 399 | source = { registry = "https://pypi.org/simple" } 400 | sdist = { url = "https://files.pythonhosted.org/packages/1e/7f/60fda2eec81f23f8aa7cbbfdf6ec2ca11eb11c273827933fb2541c2ce9d8/ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a", size = 3586740 } 401 | wheels = [ 402 | { url = "https://files.pythonhosted.org/packages/f9/77/4fb790596d5d52c87fd55b7160c557c400e90f6116a56d82d76e95d9374a/ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624", size = 11656815 }, 403 | { url = "https://files.pythonhosted.org/packages/a2/a8/3338ecb97573eafe74505f28431df3842c1933c5f8eae615427c1de32858/ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c", size = 11594821 }, 404 | { url = "https://files.pythonhosted.org/packages/8e/89/320223c3421962762531a6b2dd58579b858ca9916fb2674874df5e97d628/ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4", size = 11040475 }, 405 | { url = "https://files.pythonhosted.org/packages/b2/bd/1d775eac5e51409535804a3a888a9623e87a8f4b53e2491580858a083692/ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439", size = 11856207 }, 406 | { url = "https://files.pythonhosted.org/packages/7f/c6/3e14e09be29587393d188454064a4aa85174910d16644051a80444e4fd88/ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5", size = 11420460 }, 407 | { url = "https://files.pythonhosted.org/packages/ef/42/b7ca38ffd568ae9b128a2fa76353e9a9a3c80ef19746408d4ce99217ecc1/ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4", size = 12605472 }, 408 | { url = "https://files.pythonhosted.org/packages/a6/a1/3167023f23e3530fde899497ccfe239e4523854cb874458ac082992d206c/ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1", size = 13243123 }, 409 | { url = "https://files.pythonhosted.org/packages/d0/b4/3c600758e320f5bf7de16858502e849f4216cb0151f819fa0d1154874802/ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5", size = 12744650 }, 410 | { url = "https://files.pythonhosted.org/packages/be/38/266fbcbb3d0088862c9bafa8b1b99486691d2945a90b9a7316336a0d9a1b/ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4", size = 14458585 }, 411 | { url = "https://files.pythonhosted.org/packages/63/a6/47fd0e96990ee9b7a4abda62de26d291bd3f7647218d05b7d6d38af47c30/ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6", size = 12419624 }, 412 | { url = "https://files.pythonhosted.org/packages/84/5d/de0b7652e09f7dda49e1a3825a164a65f4998175b6486603c7601279baad/ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730", size = 11843238 }, 413 | { url = "https://files.pythonhosted.org/packages/9e/be/3f341ceb1c62b565ec1fb6fd2139cc40b60ae6eff4b6fb8f94b1bb37c7a9/ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2", size = 11484012 }, 414 | { url = "https://files.pythonhosted.org/packages/a3/c8/ff8acbd33addc7e797e702cf00bfde352ab469723720c5607b964491d5cf/ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519", size = 12038494 }, 415 | { url = "https://files.pythonhosted.org/packages/73/b1/8d9a2c0efbbabe848b55f877bc10c5001a37ab10aca13c711431673414e5/ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b", size = 12473639 }, 416 | { url = "https://files.pythonhosted.org/packages/cb/44/a673647105b1ba6da9824a928634fe23186ab19f9d526d7bdf278cd27bc3/ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c", size = 9834353 }, 417 | { url = "https://files.pythonhosted.org/packages/c3/01/65cadb59bf8d4fbe33d1a750103e6883d9ef302f60c28b73b773092fbde5/ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4", size = 10821444 }, 418 | { url = "https://files.pythonhosted.org/packages/69/cb/b3fe58a136a27d981911cba2f18e4b29f15010623b79f0f2510fd0d31fd3/ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b", size = 10038168 }, 419 | ] 420 | 421 | [[package]] 422 | name = "snowballstemmer" 423 | version = "2.2.0" 424 | source = { registry = "https://pypi.org/simple" } 425 | sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } 426 | wheels = [ 427 | { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, 428 | ] 429 | 430 | [[package]] 431 | name = "sqlparse" 432 | version = "0.5.3" 433 | source = { registry = "https://pypi.org/simple" } 434 | sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 } 435 | wheels = [ 436 | { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, 437 | ] 438 | 439 | [[package]] 440 | name = "starminder" 441 | version = "3.0.0" 442 | source = { virtual = "." } 443 | dependencies = [ 444 | { name = "django" }, 445 | { name = "django-allauth" }, 446 | { name = "psycopg", extra = ["binary"] }, 447 | { name = "python-dotenv" }, 448 | { name = "requests" }, 449 | ] 450 | 451 | [package.dev-dependencies] 452 | dev = [ 453 | { name = "django-debug-toolbar" }, 454 | { name = "django-stubs" }, 455 | { name = "mypy" }, 456 | { name = "pdbpp" }, 457 | { name = "pydocstyle" }, 458 | { name = "pytest" }, 459 | { name = "pytest-django" }, 460 | { name = "python-lsp-server" }, 461 | { name = "ruff" }, 462 | ] 463 | 464 | [package.metadata] 465 | requires-dist = [ 466 | { name = "django", specifier = ">=5.1.5" }, 467 | { name = "django-allauth", specifier = ">=65.3.1" }, 468 | { name = "psycopg", extras = ["binary"], specifier = ">=3.2.4" }, 469 | { name = "python-dotenv", specifier = ">=1.0.1" }, 470 | { name = "requests", specifier = ">=2.32.3" }, 471 | ] 472 | 473 | [package.metadata.requires-dev] 474 | dev = [ 475 | { name = "django-debug-toolbar", specifier = ">=5.0.1" }, 476 | { name = "django-stubs", specifier = ">=5.1.2" }, 477 | { name = "mypy", specifier = ">=1.14.1" }, 478 | { name = "pdbpp", specifier = ">=0.10.3" }, 479 | { name = "pydocstyle", specifier = ">=6.3.0" }, 480 | { name = "pytest", specifier = ">=8.3.4" }, 481 | { name = "pytest-django", specifier = ">=4.9.0" }, 482 | { name = "python-lsp-server", specifier = ">=1.12.0" }, 483 | { name = "ruff", specifier = ">=0.9.3" }, 484 | ] 485 | 486 | [[package]] 487 | name = "types-pyyaml" 488 | version = "6.0.12.20241230" 489 | source = { registry = "https://pypi.org/simple" } 490 | sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 } 491 | wheels = [ 492 | { url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 }, 493 | ] 494 | 495 | [[package]] 496 | name = "typing-extensions" 497 | version = "4.12.2" 498 | source = { registry = "https://pypi.org/simple" } 499 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 500 | wheels = [ 501 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 502 | ] 503 | 504 | [[package]] 505 | name = "tzdata" 506 | version = "2025.1" 507 | source = { registry = "https://pypi.org/simple" } 508 | sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 } 509 | wheels = [ 510 | { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 }, 511 | ] 512 | 513 | [[package]] 514 | name = "ujson" 515 | version = "5.10.0" 516 | source = { registry = "https://pypi.org/simple" } 517 | sdist = { url = "https://files.pythonhosted.org/packages/f0/00/3110fd566786bfa542adb7932d62035e0c0ef662a8ff6544b6643b3d6fd7/ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1", size = 7154885 } 518 | wheels = [ 519 | { url = "https://files.pythonhosted.org/packages/0d/69/b3e3f924bb0e8820bb46671979770c5be6a7d51c77a66324cdb09f1acddb/ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287", size = 55646 }, 520 | { url = "https://files.pythonhosted.org/packages/32/8a/9b748eb543c6cabc54ebeaa1f28035b1bd09c0800235b08e85990734c41e/ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e", size = 51806 }, 521 | { url = "https://files.pythonhosted.org/packages/39/50/4b53ea234413b710a18b305f465b328e306ba9592e13a791a6a6b378869b/ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557", size = 51975 }, 522 | { url = "https://files.pythonhosted.org/packages/b4/9d/8061934f960cdb6dd55f0b3ceeff207fcc48c64f58b43403777ad5623d9e/ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988", size = 53693 }, 523 | { url = "https://files.pythonhosted.org/packages/f5/be/7bfa84b28519ddbb67efc8410765ca7da55e6b93aba84d97764cd5794dbc/ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816", size = 58594 }, 524 | { url = "https://files.pythonhosted.org/packages/48/eb/85d465abafb2c69d9699cfa5520e6e96561db787d36c677370e066c7e2e7/ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20", size = 997853 }, 525 | { url = "https://files.pythonhosted.org/packages/9f/76/2a63409fc05d34dd7d929357b7a45e3a2c96f22b4225cd74becd2ba6c4cb/ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0", size = 1140694 }, 526 | { url = "https://files.pythonhosted.org/packages/45/ed/582c4daba0f3e1688d923b5cb914ada1f9defa702df38a1916c899f7c4d1/ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f", size = 1043580 }, 527 | { url = "https://files.pythonhosted.org/packages/d7/0c/9837fece153051e19c7bade9f88f9b409e026b9525927824cdf16293b43b/ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165", size = 38766 }, 528 | { url = "https://files.pythonhosted.org/packages/d7/72/6cb6728e2738c05bbe9bd522d6fc79f86b9a28402f38663e85a28fddd4a0/ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539", size = 42212 }, 529 | ] 530 | 531 | [[package]] 532 | name = "urllib3" 533 | version = "2.3.0" 534 | source = { registry = "https://pypi.org/simple" } 535 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } 536 | wheels = [ 537 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, 538 | ] 539 | 540 | [[package]] 541 | name = "wmctrl" 542 | version = "0.5" 543 | source = { registry = "https://pypi.org/simple" } 544 | dependencies = [ 545 | { name = "attrs" }, 546 | ] 547 | sdist = { url = "https://files.pythonhosted.org/packages/60/d9/6625ead93412c5ce86db1f8b4f2a70b8043e0a7c1d30099ba3c6a81641ff/wmctrl-0.5.tar.gz", hash = "sha256:7839a36b6fe9e2d6fd22304e5dc372dbced2116ba41283ea938b2da57f53e962", size = 5202 } 548 | wheels = [ 549 | { url = "https://files.pythonhosted.org/packages/13/ca/723e3f8185738d7947f14ee7dc663b59415c6dee43bd71575f8c7f5cd6be/wmctrl-0.5-py2.py3-none-any.whl", hash = "sha256:ae695c1863a314c899e7cf113f07c0da02a394b968c4772e1936219d9234ddd7", size = 4268 }, 550 | ] 551 | --------------------------------------------------------------------------------