diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7ba63df --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Django", + "type": "debugpy", + "request": "launch", + "args": [ + "runserver" + ], + "django": true, + "autoStartBrowser": false, + "program": "${workspaceFolder}\\manage.py" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 91869f2..a344182 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,8 @@ "appauthor", "appname", "ASGI", + "collectstatic", + "createsuperuser", "docstrings", "dotenv", "Hellsén", @@ -13,12 +15,14 @@ "Joakim", "lovinator", "Mailgun", + "makemigrations", "platformdirs", "psutil", "pydocstyle", "pyright", "pytest", "regularuser", + "runserver", "sendgrid", "testpass", "ttvdrops", diff --git a/README.md b/README.md index fd32907..19aa674 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # ttvdrops + Get notified when a new drop is available on Twitch + +## Development + +```bash +uv run python manage.py createsuperuser +uv run python manage.py makemigrations +uv run python manage.py migrate +uv run python manage.py collectstatic +uv run python manage.py runserver +uv run pytest +``` diff --git a/config/settings.py b/config/settings.py index 5b65046..892ce36 100644 --- a/config/settings.py +++ b/config/settings.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import os import sys from pathlib import Path @@ -8,6 +9,8 @@ from typing import Any from dotenv import load_dotenv from platformdirs import user_data_dir +logger: logging.Logger = logging.getLogger("ttvdrops.settings") + load_dotenv(verbose=True) DEBUG: bool = os.getenv(key="DEBUG", default="True").lower() == "true" @@ -43,6 +46,9 @@ BASE_DIR: Path = Path(__file__).resolve().parent.parent DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" ROOT_URLCONF = "config.urls" SECRET_KEY: str = os.getenv("DJANGO_SECRET_KEY", default="") +if not SECRET_KEY: + logger.error("DJANGO_SECRET_KEY environment variable is not set.") + sys.exit(1) DEFAULT_FROM_EMAIL: str | None = os.getenv(key="EMAIL_HOST_USER", default=None) EMAIL_HOST: str = os.getenv(key="EMAIL_HOST", default="smtp.gmail.com") @@ -108,8 +114,8 @@ INSTALLED_APPS: list[str] = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "django.contrib.sites", - "core", + "core.apps.CoreConfig", + "twitch.apps.TwitchConfig", ] MIDDLEWARE: list[str] = [ diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 408d22c..9713b97 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,103 +1,44 @@ -# Generated by Django 5.2.4 on 2025-07-08 00:33 -from __future__ import annotations - -from typing import TYPE_CHECKING +# Generated by Django 5.2.4 on 2025-07-08 15:01 import django.contrib.auth.models import django.contrib.auth.validators import django.utils.timezone from django.db import migrations, models -if TYPE_CHECKING: - from django.db.migrations.operations.base import Operation - class Migration(migrations.Migration): - """Initial migration for the custom User model. - - This migration creates the User model based on Django's AbstractUser. - """ initial = True - dependencies: list[tuple[str, str]] = [ - ("auth", "0012_alter_user_first_name_max_length"), + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), ] - operations: list[Operation] = [ + operations = [ migrations.CreateModel( - name="User", + name='User', fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("password", models.CharField(max_length=128, verbose_name="password")), - ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")), - ( - "is_superuser", - models.BooleanField( - default=False, - help_text="Designates that this user has all permissions without explicitly assigning them.", - verbose_name="superuser status", - ), - ), - ( - "username", - models.CharField( - error_messages={"unique": "A user with that username already exists."}, - help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", - max_length=150, - unique=True, - validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], - verbose_name="username", - ), - ), - ("first_name", models.CharField(blank=True, max_length=150, verbose_name="first name")), - ("last_name", models.CharField(blank=True, max_length=150, verbose_name="last name")), - ("email", models.EmailField(blank=True, max_length=254, verbose_name="email address")), - ( - "is_staff", - models.BooleanField( - default=False, help_text="Designates whether the user can log into this admin site.", verbose_name="staff status" - ), - ), - ( - "is_active", - models.BooleanField( - default=True, - help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", - verbose_name="active", - ), - ), - ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")), - ( - "groups", - models.ManyToManyField( - blank=True, - help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", - related_name="user_set", - related_query_name="user", - to="auth.group", - verbose_name="groups", - ), - ), - ( - "user_permissions", - models.ManyToManyField( - blank=True, - help_text="Specific permissions for this user.", - related_name="user_set", - related_query_name="user", - to="auth.permission", - verbose_name="user permissions", - ), - ), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], options={ - "verbose_name": "User", - "verbose_name_plural": "Users", - "db_table": "auth_user", + 'verbose_name': 'User', + 'verbose_name_plural': 'Users', + 'db_table': 'auth_user', }, managers=[ - ("objects", django.contrib.auth.models.UserManager()), + ('objects', django.contrib.auth.models.UserManager()), ], ), ] diff --git a/twitch/__init__.py b/twitch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/twitch/admin.py b/twitch/admin.py new file mode 100644 index 0000000..846f6b4 --- /dev/null +++ b/twitch/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/twitch/apps.py b/twitch/apps.py new file mode 100644 index 0000000..c481710 --- /dev/null +++ b/twitch/apps.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from django.apps import AppConfig + + +class TwitchConfig(AppConfig): + """AppConfig subclass for the 'twitch' application.""" + + default_auto_field: str = "django.db.models.BigAutoField" + name = "twitch" diff --git a/twitch/migrations/__init__.py b/twitch/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/twitch/models.py b/twitch/models.py new file mode 100644 index 0000000..6b20219 --- /dev/null +++ b/twitch/models.py @@ -0,0 +1 @@ +# Create your models here. diff --git a/twitch/tests.py b/twitch/tests.py new file mode 100644 index 0000000..a39b155 --- /dev/null +++ b/twitch/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/twitch/views.py b/twitch/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/twitch/views.py @@ -0,0 +1 @@ +# Create your views here.