From: mar77i Date: Sat, 19 Oct 2024 14:54:57 +0000 (+0200) Subject: add pre-commit, set up ruff fixes X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=cda2b802aab8bbb5be8aa5286373113a325eabd2;p=chat add pre-commit, set up ruff fixes --- diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8bc47c1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.0 + hooks: + - id: ruff + args: [ --fix, --extend-select, I ] + - id: ruff-format diff --git a/chat/admin.py b/chat/admin.py index 8c38f3f..846f6b4 100644 --- a/chat/admin.py +++ b/chat/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - # Register your models here. diff --git a/chat/apps.py b/chat/apps.py index bebd81f..580e57a 100644 --- a/chat/apps.py +++ b/chat/apps.py @@ -5,12 +5,12 @@ from .utils import get_app_configs class ChatConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'chat' + default_auto_field = "django.db.models.BigAutoField" @staticmethod def autodiscover_models(): from django.contrib.admin import site + for app_config in get_app_configs(): for model in app_config.get_models(): site.register(model, ModelAdmin) diff --git a/chat/asgi.py b/chat/asgi.py index 27a53c7..1033432 100644 --- a/chat/asgi.py +++ b/chat/asgi.py @@ -1,5 +1,4 @@ -""" -ASGI config for chat project. +"""ASGI config for chat project. It exposes the ASGI callable as a module-level variable named ``application``. @@ -12,19 +11,19 @@ import os import django from django.core.handlers.asgi import ASGIHandler -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chat.settings") class ASGIHandlerWithWebsocket(ASGIHandler): async def __call__(self, scope, receive, send): from .urls import websocket_urls + if scope["type"] == "websocket": await websocket_urls.get(scope["path"])(scope, receive, send) else: await super().__call__(scope, receive, send) - def get_asgi_application(): django.setup(set_prefix=False) return ASGIHandlerWithWebsocket() diff --git a/chat/forms.py b/chat/forms.py index b2ed832..837950f 100644 --- a/chat/forms.py +++ b/chat/forms.py @@ -1,9 +1,8 @@ from urllib.parse import urlunsplit from django.conf import settings -from django.contrib.auth.forms import ( - AuthenticationForm as DjangoAuthenticationForm, BaseUserCreationForm -) +from django.contrib.auth.forms import AuthenticationForm as DjangoAuthenticationForm +from django.contrib.auth.forms import BaseUserCreationForm from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ValidationError @@ -29,7 +28,7 @@ class RegisterForm(BaseUserCreationForm): class Meta: model = User - fields = ['email', 'username', 'first_name', 'last_name'] + fields = ["email", "username", "first_name", "last_name"] class PasswordResetForm(Form): @@ -61,7 +60,7 @@ class PasswordResetForm(Form): ), None, None, - ) + ), ) user.email_user( f"Password reset on {site.name}", @@ -73,7 +72,7 @@ class PasswordResetForm(Form): f"Your username, in case you've forgotten: {user}\n\n" "Thanks for using our site!\n" f"The {site.name} team" - ) + ), ) diff --git a/chat/migrations/0001_initial.py b/chat/migrations/0001_initial.py index 73207ba..366219a 100644 --- a/chat/migrations/0001_initial.py +++ b/chat/migrations/0001_initial.py @@ -11,113 +11,388 @@ from django.db import migrations, models class Migration(migrations.Migration): - initial = True dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name='Channel', + name="Channel", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=256, unique=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=256, unique=True)), ], ), 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", + ), + ), ], managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ("objects", django.contrib.auth.models.UserManager()), ], ), migrations.CreateModel( - name='ChannelMessage', + name="ChannelMessage", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ts', models.DateTimeField(auto_now_add=True)), - ('text', models.TextField()), - ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chat.channel')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("ts", models.DateTimeField(auto_now_add=True)), + ("text", models.TextField()), + ( + "channel", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="chat.channel", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='ChannelUser', + name="ChannelUser", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chat.channel')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "channel", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="chat.channel", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AddField( - model_name='channel', - name='users', - field=models.ManyToManyField(related_name='channels', through='chat.ChannelUser', to=settings.AUTH_USER_MODEL), + model_name="channel", + name="users", + field=models.ManyToManyField( + related_name="channels", + through="chat.ChannelUser", + to=settings.AUTH_USER_MODEL, + ), ), migrations.CreateModel( - name='PrivateMessage', + name="PrivateMessage", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ts', models.DateTimeField(auto_now_add=True)), - ('text', models.TextField()), - ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pns_recieved', to=settings.AUTH_USER_MODEL)), - ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pms_sent', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("ts", models.DateTimeField(auto_now_add=True)), + ("text", models.TextField()), + ( + "recipient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="pns_recieved", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "sender", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="pms_sent", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), pgtrigger.migrations.AddTrigger( - model_name='user', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_user', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', hash='7f6e8eba01193825febb722ab5558a49773b1c1a', operation='DELETE OR INSERT OR UPDATE', pgid='pgtrigger_pg_notify_user_152e9', table='chat_user', when='AFTER')), + model_name="user", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_user", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', + hash="7f6e8eba01193825febb722ab5558a49773b1c1a", + operation="DELETE OR INSERT OR UPDATE", + pgid="pgtrigger_pg_notify_user_152e9", + table="chat_user", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='user', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_user_truncate', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME || \'"}\'\n );\n RETURN NULL;\n ', hash='58944d2a6ef65e1fe00c504ca2488854a9ed2989', level='STATEMENT', operation='TRUNCATE', pgid='pgtrigger_pg_notify_user_truncate_b8c18', table='chat_user', when='AFTER')), + model_name="user", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_user_truncate", + sql=pgtrigger.compiler.UpsertTriggerSql( + func="\n PERFORM pg_notify(\n 'pg_notify',\n '{\"op\":\"' || TG_OP || '\",\"table\":\"' || TG_TABLE_NAME || '\"}'\n );\n RETURN NULL;\n ", + hash="58944d2a6ef65e1fe00c504ca2488854a9ed2989", + level="STATEMENT", + operation="TRUNCATE", + pgid="pgtrigger_pg_notify_user_truncate_b8c18", + table="chat_user", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='channelmessage', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_channelmessage', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \',"user_id":\' || OLD."user_id" || \',"channel_id":\' || OLD."channel_id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \',"user_id":\' || NEW."user_id" || \',"channel_id":\' || NEW."channel_id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', hash='b6e672dd9e454c168ef544260779943b1e40fe6d', operation='DELETE OR INSERT OR UPDATE', pgid='pgtrigger_pg_notify_channelmessage_d2b2e', table='chat_channelmessage', when='AFTER')), + model_name="channelmessage", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_channelmessage", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \',"user_id":\' || OLD."user_id" || \',"channel_id":\' || OLD."channel_id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \',"user_id":\' || NEW."user_id" || \',"channel_id":\' || NEW."channel_id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', + hash="b6e672dd9e454c168ef544260779943b1e40fe6d", + operation="DELETE OR INSERT OR UPDATE", + pgid="pgtrigger_pg_notify_channelmessage_d2b2e", + table="chat_channelmessage", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='channelmessage', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_channelmessage_truncate', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME || \'"}\'\n );\n RETURN NULL;\n ', hash='8f1c4f12257239f00ee73bfd21b226a25a2c6a16', level='STATEMENT', operation='TRUNCATE', pgid='pgtrigger_pg_notify_channelmessage_truncate_02c45', table='chat_channelmessage', when='AFTER')), + model_name="channelmessage", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_channelmessage_truncate", + sql=pgtrigger.compiler.UpsertTriggerSql( + func="\n PERFORM pg_notify(\n 'pg_notify',\n '{\"op\":\"' || TG_OP || '\",\"table\":\"' || TG_TABLE_NAME || '\"}'\n );\n RETURN NULL;\n ", + hash="8f1c4f12257239f00ee73bfd21b226a25a2c6a16", + level="STATEMENT", + operation="TRUNCATE", + pgid="pgtrigger_pg_notify_channelmessage_truncate_02c45", + table="chat_channelmessage", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='channeluser', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_channeluser', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \',"user_id":\' || OLD."user_id" || \',"channel_id":\' || OLD."channel_id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \',"user_id":\' || NEW."user_id" || \',"channel_id":\' || NEW."channel_id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', hash='6d4422d5ba8d654603affe34f0cabebe53fe23b0', operation='DELETE OR INSERT OR UPDATE', pgid='pgtrigger_pg_notify_channeluser_f01cc', table='chat_channeluser', when='AFTER')), + model_name="channeluser", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_channeluser", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \',"user_id":\' || OLD."user_id" || \',"channel_id":\' || OLD."channel_id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \',"user_id":\' || NEW."user_id" || \',"channel_id":\' || NEW."channel_id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', + hash="6d4422d5ba8d654603affe34f0cabebe53fe23b0", + operation="DELETE OR INSERT OR UPDATE", + pgid="pgtrigger_pg_notify_channeluser_f01cc", + table="chat_channeluser", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='channeluser', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_channeluser_truncate', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME || \'"}\'\n );\n RETURN NULL;\n ', hash='7eee59ce12c198a1144e9bd89914eb0845bee5f5', level='STATEMENT', operation='TRUNCATE', pgid='pgtrigger_pg_notify_channeluser_truncate_5ae88', table='chat_channeluser', when='AFTER')), + model_name="channeluser", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_channeluser_truncate", + sql=pgtrigger.compiler.UpsertTriggerSql( + func="\n PERFORM pg_notify(\n 'pg_notify',\n '{\"op\":\"' || TG_OP || '\",\"table\":\"' || TG_TABLE_NAME || '\"}'\n );\n RETURN NULL;\n ", + hash="7eee59ce12c198a1144e9bd89914eb0845bee5f5", + level="STATEMENT", + operation="TRUNCATE", + pgid="pgtrigger_pg_notify_channeluser_truncate_5ae88", + table="chat_channeluser", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='channel', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_channel', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', hash='32baa6dfc09e87012fc270e79ff8a074b5c9ffcd', operation='DELETE OR INSERT OR UPDATE', pgid='pgtrigger_pg_notify_channel_fab0c', table='chat_channel', when='AFTER')), + model_name="channel", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_channel", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', + hash="32baa6dfc09e87012fc270e79ff8a074b5c9ffcd", + operation="DELETE OR INSERT OR UPDATE", + pgid="pgtrigger_pg_notify_channel_fab0c", + table="chat_channel", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='channel', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_channel_truncate', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME || \'"}\'\n );\n RETURN NULL;\n ', hash='0f2df778b97a70d4e3e883b0c80430e8d4ef194e', level='STATEMENT', operation='TRUNCATE', pgid='pgtrigger_pg_notify_channel_truncate_bcd8a', table='chat_channel', when='AFTER')), + model_name="channel", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_channel_truncate", + sql=pgtrigger.compiler.UpsertTriggerSql( + func="\n PERFORM pg_notify(\n 'pg_notify',\n '{\"op\":\"' || TG_OP || '\",\"table\":\"' || TG_TABLE_NAME || '\"}'\n );\n RETURN NULL;\n ", + hash="0f2df778b97a70d4e3e883b0c80430e8d4ef194e", + level="STATEMENT", + operation="TRUNCATE", + pgid="pgtrigger_pg_notify_channel_truncate_bcd8a", + table="chat_channel", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='privatemessage', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_privatemessage', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \',"sender_id":\' || OLD."sender_id" || \',"recipient_id":\' || OLD."recipient_id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \',"sender_id":\' || NEW."sender_id" || \',"recipient_id":\' || NEW."recipient_id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', hash='03ce1756ef8739ddbaac8c46c954bb7f29cfb352', operation='DELETE OR INSERT OR UPDATE', pgid='pgtrigger_pg_notify_privatemessage_92534', table='chat_privatemessage', when='AFTER')), + model_name="privatemessage", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_privatemessage", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='\n IF TG_OP = \'DELETE\' THEN\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || OLD."id" || \',"sender_id":\' || OLD."sender_id" || \',"recipient_id":\' || OLD."recipient_id" || \'}}\'\n );\n ELSE\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME ||\n \'","obj":{"id":\' || NEW."id" || \',"sender_id":\' || NEW."sender_id" || \',"recipient_id":\' || NEW."recipient_id" || \'}}\'\n );\n END IF;\n RETURN NULL;\n ', + hash="03ce1756ef8739ddbaac8c46c954bb7f29cfb352", + operation="DELETE OR INSERT OR UPDATE", + pgid="pgtrigger_pg_notify_privatemessage_92534", + table="chat_privatemessage", + when="AFTER", + ), + ), ), pgtrigger.migrations.AddTrigger( - model_name='privatemessage', - trigger=pgtrigger.compiler.Trigger(name='pg_notify_privatemessage_truncate', sql=pgtrigger.compiler.UpsertTriggerSql(func='\n PERFORM pg_notify(\n \'pg_notify\',\n \'{"op":"\' || TG_OP || \'","table":"\' || TG_TABLE_NAME || \'"}\'\n );\n RETURN NULL;\n ', hash='edd1292c2830959599e3bec829029206726381d6', level='STATEMENT', operation='TRUNCATE', pgid='pgtrigger_pg_notify_privatemessage_truncate_e9186', table='chat_privatemessage', when='AFTER')), + model_name="privatemessage", + trigger=pgtrigger.compiler.Trigger( + name="pg_notify_privatemessage_truncate", + sql=pgtrigger.compiler.UpsertTriggerSql( + func="\n PERFORM pg_notify(\n 'pg_notify',\n '{\"op\":\"' || TG_OP || '\",\"table\":\"' || TG_TABLE_NAME || '\"}'\n );\n RETURN NULL;\n ", + hash="edd1292c2830959599e3bec829029206726381d6", + level="STATEMENT", + operation="TRUNCATE", + pgid="pgtrigger_pg_notify_privatemessage_truncate_e9186", + table="chat_privatemessage", + when="AFTER", + ), + ), ), ] diff --git a/chat/models.py b/chat/models.py index 31ecc92..0ef74c4 100644 --- a/chat/models.py +++ b/chat/models.py @@ -1,8 +1,15 @@ -from django.db.models import ( - CASCADE, CharField, DateTimeField, ForeignKey, ManyToManyField, Model, TextField -) from django.conf import settings from django.contrib.auth.models import AbstractUser +from django.db.models import ( + CASCADE, + CharField, + DateTimeField, + ForeignKey, + ManyToManyField, + Model, + TextField, +) + from .triggers import triggers_for_table @@ -26,7 +33,7 @@ class PrivateMessage(Model): settings.PG_NOTIFY_CHANNEL, "PrivateMessage", ("id", "sender_id", "recipient_id"), - ) + ), ] @@ -35,9 +42,7 @@ class Channel(Model): users = ManyToManyField(User, related_name="channels", through="ChannelUser") class Meta: - triggers = [ - *triggers_for_table(settings.PG_NOTIFY_CHANNEL, "Channel", ("id",)) - ] + triggers = [*triggers_for_table(settings.PG_NOTIFY_CHANNEL, "Channel", ("id",))] class ChannelUser(Model): @@ -50,7 +55,7 @@ class ChannelUser(Model): settings.PG_NOTIFY_CHANNEL, "ChannelUser", ("id", "user_id", "channel_id"), - ) + ), ] @@ -66,5 +71,5 @@ class ChannelMessage(Model): settings.PG_NOTIFY_CHANNEL, "ChannelMessage", ("id", "user_id", "channel_id"), - ) + ), ] diff --git a/chat/rest_views.py b/chat/rest_views.py index 2586802..fc1f0cd 100644 --- a/chat/rest_views.py +++ b/chat/rest_views.py @@ -42,11 +42,11 @@ class ModelRestView(LoginRequiredMixin, View): if "before" in query: queryset = queryset.filter(pk__lt=int(query["before"])) count = queryset.count() - queryset = queryset[max(count - settings.DEFAULT_PAGE_SIZE, 0):] + queryset = queryset[max(count - settings.DEFAULT_PAGE_SIZE, 0) :] if count > settings.DEFAULT_PAGE_SIZE: query["before"] = queryset[0].pk prev_url = self.request.build_absolute_uri( - urlunsplit(("", "", full_path, query.urlencode(), "")) + urlunsplit(("", "", full_path, query.urlencode(), "")), ) else: prev_url = None @@ -72,7 +72,7 @@ class ModelRestView(LoginRequiredMixin, View): self.paginate(self.get_queryset(), serializer.to_json), **self.get_json_dump_kwargs(), ), - content_type="application/json" + content_type="application/json", ) def create(self): @@ -92,9 +92,7 @@ class ModelRestView(LoginRequiredMixin, View): def create_or_update(self, *args, **kwargs): serializer = self.serializer(self.request) - instance, m2m = serializer.from_json( - json.load(self.request), *args - ) + instance, m2m = serializer.from_json(json.load(self.request), *args) instance.save() for field_name, value in m2m.items(): getattr(instance, field_name).set(value) @@ -187,11 +185,11 @@ class PrivateMessageRestView(ModelRestView): if recipient_id is not None: queryset = queryset.filter( Q(sender=self.request.user, recipient_id=recipient_id) - | Q(sender_id=recipient_id, recipient=self.request.user) + | Q(sender_id=recipient_id, recipient=self.request.user), ) else: queryset = queryset.filter( - Q(sender=self.request.user) | Q(recipient=self.request.user) + Q(sender=self.request.user) | Q(recipient=self.request.user), ) return queryset.order_by("ts") @@ -203,7 +201,7 @@ class ChannelRestView(ListAllMixin, ModelRestView): return super().get_queryset().filter(users=self.request.user.pk) -#class ChannelUserRestView(ListAllMixin, ModelRestView): +# class ChannelUserRestView(ListAllMixin, ModelRestView): # serializer = ChannelUserSerializer # # def get_queryset(self): @@ -214,9 +212,7 @@ class ChannelMessageRestView(ModelRestView): serializer = ChannelMessageSerializer def get_queryset(self): - queryset = super().get_queryset().filter( - channel__users=self.request.user.pk - ) + queryset = super().get_queryset().filter(channel__users=self.request.user.pk) if "channel_id" in self.request.GET: queryset = queryset.filter(channel_id=self.request.GET["channel_id"]) return queryset.order_by("ts") diff --git a/chat/serializers.py b/chat/serializers.py index 744f279..11dddd5 100644 --- a/chat/serializers.py +++ b/chat/serializers.py @@ -2,12 +2,16 @@ from datetime import datetime from typing import Type from django.db.models import ( - DateTimeField, ForeignKey, ManyToManyField, ManyToManyRel, Model + DateTimeField, + ForeignKey, + ManyToManyField, + ManyToManyRel, + Model, ) from django.urls import reverse from django.utils.timezone import now -from .models import Channel, PrivateMessage, User, ChannelMessage # , ChannelUser +from .models import Channel, ChannelMessage, PrivateMessage, User # , ChannelUser class ModelSerializer: @@ -22,7 +26,7 @@ class ModelSerializer: def field_to_json(self, field_name, instance): if field_name == "url": - name = self.model._meta.verbose_name.lower().replace(' ', '') + name = self.model._meta.verbose_name.lower().replace(" ", "") value = reverse(f"chat-{name}-detail", args=[instance.pk]) if self.request: value = self.request.build_absolute_uri(value) @@ -95,12 +99,7 @@ class PrivateMessageSerializer(ModelSerializer): def from_json(self, data, instance=None): if instance is None: - data.update( - { - "sender_id": self.request.user.pk, - "ts": now().isoformat(" ") - } - ) + data.update({"sender_id": self.request.user.pk, "ts": now().isoformat(" ")}) else: data.pop("ts", None) return super().from_json(data, instance) @@ -120,7 +119,7 @@ class ChannelSerializer(ModelSerializer): return instance, m2m -#class ChannelUserSerializer(ModelSerializer): +# class ChannelUserSerializer(ModelSerializer): # model = ChannelUser # fields = ["id", "url", "channel", "user"] diff --git a/chat/settings.py b/chat/settings.py index d6afb4e..e024a6c 100644 --- a/chat/settings.py +++ b/chat/settings.py @@ -1,5 +1,4 @@ -""" -Django settings for chat project. +"""Django settings for chat project. Generated by 'django-admin startproject' using Django 5.1.1. @@ -9,6 +8,7 @@ https://docs.djangoproject.com/en/5.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.1/ref/settings/ """ + import os from pathlib import Path @@ -70,7 +70,7 @@ DATABASES = { "PASSWORD": os.environ.get("RDS_PASSWORD"), "HOST": os.environ.get("RDS_HOSTNAME"), "PORT": os.environ.get("RDS_PORT"), - } + }, } AUTH_PASSWORD_VALIDATORS = [ @@ -102,6 +102,6 @@ REST_CREATE_UPDATE_RETURN_RESULT = False SECRET_KEY = os.environ.get("SECRET_KEY") try: - from .settings_local import * + from .settings_local import * # noqa: F403 except ImportError: # pragma: no cover pass diff --git a/chat/tests.py b/chat/tests.py index bf73123..5d1ea20 100644 --- a/chat/tests.py +++ b/chat/tests.py @@ -2,12 +2,12 @@ import json import sys from html.parser import HTMLParser from importlib import import_module, reload -from string import hexdigits from secrets import token_urlsafe +from string import hexdigits from urllib.parse import urlencode from django.conf import settings -from django.contrib.auth.forms import SetPasswordForm, AuthenticationForm +from django.contrib.auth.forms import AuthenticationForm, SetPasswordForm from django.core import mail from django.db.models import Max from django.http import HttpResponse @@ -61,7 +61,7 @@ class FormExtractor(HTMLParser): self.errorlist_stack.append(tag) if tag in self.IGNORE_TAGS: return - elif tag == "form": + if tag == "form": self.forms.append((attrs, [])) elif tag == "input": input_name = self.get_attr("name", attrs) @@ -73,13 +73,13 @@ class FormExtractor(HTMLParser): pass else: raise ValueError( - f"unhandled input type: {repr(input_type)}" + f"unhandled input type: {input_type!r}", ) # pragma: no cover elif tag == "ul": if "errorlist" in (self.get_attr("class", attrs) or "").split(): self.errorlist_stack = ["ul"] else: - raise ValueError(f"unhandled tag: {repr(tag)}") # pragma: no cover + raise ValueError(f"unhandled tag: {tag!r}") # pragma: no cover def handle_data(self, data): if self.errorlist_stack is None: @@ -137,7 +137,7 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): register_url = reverse("chat-register") response = self.client.get(register_url) fields, csrf_token = self.get_form_fields_and_csrf_token( - response.content.decode() + response.content.decode(), ) payload = { "csrfmiddlewaretoken": csrf_token, @@ -185,7 +185,7 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): "attachments": [], "extra_headers": {}, "alternatives": [], - } + }, ) confirm_url = confirm_email.body pos = confirm_url.find(self.get_empty_confirm_url()) @@ -202,13 +202,14 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): assert pos >= 0 end_pos = content.find(f"") assert end_pos >= 0 - return content[pos + 1:end_pos] + return content[pos + 1 : end_pos] def reload_urls(self): if settings.ROOT_URLCONF in sys.modules: reload(sys.modules[settings.ROOT_URLCONF]) import_module(settings.ROOT_URLCONF) from django.urls import resolvers + resolvers._get_cached_resolver.cache_clear() @override_settings(TRUST_USER_REGISTRATIONS=True) @@ -225,7 +226,7 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): content = self.client.get(confirm_url).content.decode() self.assertEqual( self.extract_tag_data("p", content), - "Email has already been confirmed!" + "Email has already been confirmed!", ) @override_settings( @@ -246,18 +247,19 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): content = self.client.get(confirm_url).content.decode() self.assertEqual( self.extract_tag_data("p", content), - f"Email has already been confirmed!
{msg}" + f"Email has already been confirmed!
{msg}", ) user.is_active = True user.save(update_fields=["is_active"]) content = self.client.get(confirm_url).content.decode() self.assertEqual( self.extract_tag_data("p", content), - f"Email has already been confirmed!
You are ready to go!" + "Email has already been confirmed!
You are ready to go!", ) admin_msg = mail.outbox[1] self.assertEqual( - admin_msg.subject, f"[ChatApp] User registered: {payload['username']}" + admin_msg.subject, + f"[ChatApp] User registered: {payload['username']}", ) self.assertEqual( admin_msg.body, @@ -268,7 +270,7 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): f"email: '{payload['email']}'\n\n" "The user is currently inactive and needs 'is_active' set to true " "to do anything with the account. Do we trust that?" - ) + ), ) def test_reset_password(self): @@ -283,14 +285,15 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): reset_password_url = reverse("chat-reset-password") response = self.client.get(reset_password_url) fields, csrf_token = self.get_form_fields_and_csrf_token( - response.content.decode() + response.content.decode(), ) payload = {"csrfmiddlewaretoken": csrf_token, "email": "User@example.com"} self.assertEqual(set(payload), set(field[0] for field in fields)) response = self.client.post(reset_password_url, payload) self.assertEqual(response.status_code, 302) self.assertEqual( - response.headers["Location"], reverse("chat-reset-password-success") + response.headers["Location"], + reverse("chat-reset-password-success"), ) content = self.client.get(response.headers["Location"]).content.decode() self.assertEqual(self.extract_tag_data("p", content), "Password reset sent") @@ -306,7 +309,7 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): "You're receiving this email because you requested a password reset " "for your user account at testserver.\n\nPlease go to the following " "page and choose a new password:\n\n" - ) + ), ) self.assertEqual( reset_email.body[after_url_pos:], @@ -314,11 +317,11 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): "\nYour username, in case you've forgotten: example_user\n\n" "Thanks for using our site!\n" "The testserver team" - ) + ), ) url = reset_email.body[url_pos:after_url_pos] fields, csrf_token = self.get_form_fields_and_csrf_token( - self.client.get(url).content.decode() + self.client.get(url).content.decode(), ) payload = { "csrfmiddlewaretoken": csrf_token, @@ -330,16 +333,19 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): fe = FormExtractor() fe.feed(response.content.decode()) self.assertEqual( - fe.errorlist, [SetPasswordForm.error_messages["password_mismatch"]] + fe.errorlist, + [SetPasswordForm.error_messages["password_mismatch"]], ) response = self.client.post(url, payload) self.assertEqual(response.status_code, 302) self.assertEqual( - response.headers["Location"], reverse("chat-reset-password-token-success") + response.headers["Location"], + reverse("chat-reset-password-token-success"), ) content = self.client.get(response.headers["Location"]).content.decode() self.assertEqual( - self.extract_tag_data("p", content), "Password reset successful" + self.extract_tag_data("p", content), + "Password reset successful", ) user.refresh_from_db() user.check_password("SillyNewPw1234!") @@ -351,11 +357,11 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): other_slash_pos = url.rfind("/", 0, slash_pos) self.assertGreaterEqual(other_slash_pos, 0) fake_uid = urlsafe_base64_encode( - force_bytes(User.objects.aggregate(Max('pk'))["pk__max"] + 1) + force_bytes(User.objects.aggregate(Max("pk"))["pk__max"] + 1), ) url_modifieds = ( f"{url[:dash_pos + 1]}{c}{url[dash_pos + 2:]}", - f"{url[:other_slash_pos + 1]}{fake_uid}{url[slash_pos:]}" + f"{url[:other_slash_pos + 1]}{fake_uid}{url[slash_pos:]}", ) for url_modified in url_modifieds: response = self.client.get(url_modified) @@ -375,10 +381,9 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): ( {"is_active": False}, [ - AuthenticationForm.error_messages["invalid_login"] % { - "username": "username" - } - ] + AuthenticationForm.error_messages["invalid_login"] + % {"username": "username"}, + ], ), ({"is_active": True}, ["This account's email address is not confirmed."]), ({"last_login": now()}, None), @@ -393,7 +398,7 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): user.__dict__.update(update) user.save(update_fields=update.keys()) fields, csrf_token = self.get_form_fields_and_csrf_token( - self.client.get(login_url).content.decode() + self.client.get(login_url).content.decode(), ) payload["csrfmiddlewaretoken"] = csrf_token self.assertEqual(set(payload), set(field[0] for field in fields)) @@ -424,12 +429,18 @@ class ChatSignUpLoginTest(ChatTestMixin, TestCase): class ChatTest(ChatTestMixin, TestCase): def setup_users(self): user1 = User( - email="u1@example.com", username="u1", is_active=True, last_login=now() + email="u1@example.com", + username="u1", + is_active=True, + last_login=now(), ) user1.set_password("UserOnePassword123") user1.save() user2 = User( - email="u2@example.com", username="u2", is_active=True, last_login=now() + email="u2@example.com", + username="u2", + is_active=True, + last_login=now(), ) user2_password = "UserTwoPassword123" user2.set_password(user2_password) @@ -443,7 +454,7 @@ class ChatTest(ChatTestMixin, TestCase): channel.users.remove(user2) user2.channels.add(channel) - user1_url = reverse('chat-user-detail', args=[user1.pk]) + user1_url = reverse("chat-user-detail", args=[user1.pk]) user2_url = reverse("chat-user-detail", args=[user2.pk]) response = self.client.get(user2_url) @@ -464,14 +475,16 @@ class ChatTest(ChatTestMixin, TestCase): "date_joined": user2.date_joined.isoformat(" "), "channels": [channel.pk], "is_authenticated": True, - } + }, ) response = self.client.get( - reverse("chat-user-current-detail"), headers={"cookie": cookies} + reverse("chat-user-current-detail"), + headers={"cookie": cookies}, ) self.assertEqual(data, json.loads(response.content)) response = self.client.get( - reverse("chat-user-list"), headers={"cookie": cookies} + reverse("chat-user-list"), + headers={"cookie": cookies}, ) self.assertEqual( json.loads(response.content), @@ -499,8 +512,8 @@ class ChatTest(ChatTestMixin, TestCase): "channels": [channel.pk], "is_authenticated": True, }, - ] - } + ], + }, ) self.client.get(reverse("chat-logout"), headers={"cookie": cookies}) @@ -523,8 +536,8 @@ class ChatTest(ChatTestMixin, TestCase): "email": "u4@example.com", "is_active": True, "last_login": now().isoformat(" "), - "channels": [channel.pk] - } + "channels": [channel.pk], + }, ).encode(), content_type="application/json", headers={"cookie": cookies}, @@ -555,13 +568,13 @@ class ChatTest(ChatTestMixin, TestCase): "username": "user4", "is_active": False, "last_name": "Nichols", - } + }, ).encode(), content_type="application/json", headers={"cookie": cookies}, ) user4_data = json.loads(response.content) - user4_url = reverse('chat-user-detail', args=[user4_data['id']]) + user4_url = reverse("chat-user-detail", args=[user4_data["id"]]) self.assertEqual( user4_data, { @@ -618,7 +631,7 @@ class ChatTest(ChatTestMixin, TestCase): "url": f"http://testserver{channel_url}", "name": "test channel", "users": [user1.pk, user2.pk], - } + }, ) response = self.client.post( reverse("chat-channel-list"), @@ -650,7 +663,7 @@ class ChatTest(ChatTestMixin, TestCase): "id": None, "recipient_id": user1.pk, "text": "hello pms world", - } + }, ), content_type="application/json", headers={"cookie": cookies}, @@ -666,9 +679,9 @@ class ChatTest(ChatTestMixin, TestCase): "sender_id": user2.pk, "recipient_id": user1.pk, "text": "hello pms world", - } + }, ) - user3 = User.objects.create( + User.objects.create( username="additional user", email="additional@user.com", last_login=now(), @@ -678,11 +691,11 @@ class ChatTest(ChatTestMixin, TestCase): data["url"], json.dumps( { - #"sender_id": user3.pk, - #"recipient_id": user3.pk, + # "sender_id": user3.pk, + # "recipient_id": user3.pk, "ts": "2022-02-22 22:22:22.363636+00:00", "text": "hello updated pms world", - } + }, ), content_type="application/json", headers={"cookie": cookies}, @@ -697,7 +710,7 @@ class ChatTest(ChatTestMixin, TestCase): "sender_id": user2.pk, "recipient_id": user1.pk, "text": "hello updated pms world", - } + }, ) response = self.client.get( f"{reverse('chat-privatemessage-list')}?other={user1.pk}", @@ -711,7 +724,7 @@ class ChatTest(ChatTestMixin, TestCase): or (m["sender_id"] == user1.pk and m["recipient_id"] == user2.pk) ) for m in data["result"] - ) + ), ) self.assertEqual( data, @@ -726,11 +739,10 @@ class ChatTest(ChatTestMixin, TestCase): "recipient_id": user1.pk, "text": "hello updated pms world", }, - ] - } + ], + }, ) - def test_chat_channelmessage_api(self): user1, user2, user2_password, channel = self.setup_users() cookies = self.login_user(user2, user2_password) @@ -740,7 +752,7 @@ class ChatTest(ChatTestMixin, TestCase): { "channel_id": channel.pk, "text": "hello world", - } + }, ), content_type="application/json", headers={"cookie": cookies}, @@ -756,7 +768,7 @@ class ChatTest(ChatTestMixin, TestCase): "channel_id": channel.pk, "text": "hello world", "user_id": user2.pk, - } + }, ) for x in range(10): ChannelMessage.objects.create( diff --git a/chat/triggers.py b/chat/triggers.py index ed56437..22e05a1 100644 --- a/chat/triggers.py +++ b/chat/triggers.py @@ -2,7 +2,7 @@ from pgtrigger import After, Delete, Insert, Row, Statement, Trigger, Truncate, def fields_to_json(t, fields): - return ",".join(f"\"{field}\":' || {t}.\"{field}\" || '" for field in fields) + return ",".join(f'"{field}":\' || {t}."{field}" || \'' for field in fields) def triggers_for_table(channel_name, model_name, fields): diff --git a/chat/urls.py b/chat/urls.py index 1bc2cdd..050ed2f 100644 --- a/chat/urls.py +++ b/chat/urls.py @@ -1,8 +1,8 @@ -""" -URL configuration for chat project. +"""URL configuration for chat project. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/5.1/topics/http/urls/ + Examples: Function views 1. Add an import: from my_app import views @@ -13,25 +13,30 @@ Class-based views Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) + """ + from django.conf import settings -from django.contrib.staticfiles.views import serve as serve_staticfiles from django.contrib import admin +from django.contrib.staticfiles.views import serve as serve_staticfiles from django.urls import path, re_path from django.utils.safestring import mark_safe from .rest_views import ( - ChannelMessageRestView, ChannelRestView, PrivateMessageRestView, UserRestView + ChannelMessageRestView, + ChannelRestView, + PrivateMessageRestView, + UserRestView, ) from .views import ( ChatView, ConfirmEmailView, LoginView, LogoutView, - SuccessView, PasswordResetTokenView, PasswordResetView, RegisterView, + SuccessView, ) from .websocket import handle_websocket @@ -47,10 +52,10 @@ urlpatterns = [ if settings.TRUST_USER_REGISTRATIONS else mark_safe( "Registration complete!
" - "Your user account will be activated shortly!" + "Your user account will be activated shortly!", ) - ) - } + ), + }, ), name="chat-register-success", ), @@ -76,7 +81,6 @@ urlpatterns = [ ), name="chat-reset-password-token-success", ), - path("", ChatView.as_view(), name="chat-main"), path("login/", LoginView.as_view(), name="chat-login"), path( @@ -85,7 +89,6 @@ urlpatterns = [ name="chat-confirm-email", ), path("logout/", LogoutView.as_view(), name="chat-logout"), - path("admin/", admin.site.urls), *UserRestView.get_urls("user"), *PrivateMessageRestView.get_urls("privatemessage"), @@ -95,7 +98,7 @@ urlpatterns = [ if settings.DEBUG: # pragma: no cover urlpatterns.append( - re_path(f"{settings.STATIC_URL.lstrip('/')}(?P.*)$", serve_staticfiles) + re_path(f"{settings.STATIC_URL.lstrip('/')}(?P.*)$", serve_staticfiles), ) websocket_urls = {"/": handle_websocket} diff --git a/chat/utils.py b/chat/utils.py index 5de7285..a6c6cbe 100644 --- a/chat/utils.py +++ b/chat/utils.py @@ -3,6 +3,7 @@ from django.utils.module_loading import module_has_submodule def get_app_configs(required_submodule=None): from django.apps import apps + for app_config in apps.get_app_configs(): if "/site-packages/" not in app_config.path and ( required_submodule is None diff --git a/chat/views.py b/chat/views.py index a31fb11..ee67f03 100644 --- a/chat/views.py +++ b/chat/views.py @@ -1,17 +1,17 @@ -from asgiref.sync import sync_to_async - from django.conf import settings from django.contrib.auth.forms import SetPasswordForm from django.contrib.auth.models import update_last_login from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.views import ( LoginView as DjangoLoginView, +) +from django.contrib.auth.views import ( LogoutView as DjangoLogoutView, ) from django.contrib.staticfiles import finders from django.core.mail import mail_admins from django.core.signing import Signer -from django.http import Http404, FileResponse +from django.http import FileResponse, Http404 from django.urls import reverse, reverse_lazy from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.safestring import mark_safe @@ -19,7 +19,7 @@ from django.views.generic import TemplateView, View from django.views.generic.detail import SingleObjectTemplateResponseMixin from django.views.generic.edit import BaseCreateView, FormView, UpdateView -from .forms import AuthenticationForm, RegisterForm, PasswordResetForm +from .forms import AuthenticationForm, PasswordResetForm, RegisterForm from .models import User @@ -86,7 +86,7 @@ class PasswordResetTokenView(UpdateView): return user raise Http404( "No %(verbose_name)s found matching the query" - % {"verbose_name": User._meta.verbose_name} + % {"verbose_name": User._meta.verbose_name}, ) def get_form_kwargs(self): @@ -107,15 +107,15 @@ class RegisterView(SingleObjectTemplateResponseMixin, BaseCreateView): # email confirmation can not expire, since it cannot be re-requested token = urlsafe_base64_encode(Signer().sign(user.pk).encode()) url = self.request.build_absolute_uri( - reverse("chat-confirm-email", args=[token]) + reverse("chat-confirm-email", args=[token]), ) user.email_user( f"{settings.EMAIL_SUBJECT_PREFIX}Confirm your email address!", - f"Hello {str(user)}\nClick here to confirm your email address:\n{url}" + f"Hello {user!s}\nClick here to confirm your email address:\n{url}", ) if not settings.TRUST_USER_REGISTRATIONS: mail_admins( - f"User registered: {str(user)}", + f"User registered: {user!s}", ( f"Hello admin,\n" f"User '{user.username}' just registered.\n" @@ -150,7 +150,7 @@ class ConfirmEmailView(TemplateView): token = f"{self.kwargs['token']}{'=' * (-len(self.kwargs['token']) & 3)}" context = super().get_context_data(**kwargs) user = User.objects.get( - pk=Signer().unsign(urlsafe_base64_decode(token).decode()) + pk=Signer().unsign(urlsafe_base64_decode(token).decode()), ) context["msg"] = self.get_msg(user) update_last_login(None, user) diff --git a/chat/websocket.py b/chat/websocket.py index 23b0c15..9fb95a8 100644 --- a/chat/websocket.py +++ b/chat/websocket.py @@ -1,7 +1,10 @@ import json import sys from asyncio import ( - CancelledError, ensure_future, get_running_loop, run_coroutine_threadsafe + CancelledError, + ensure_future, + get_running_loop, + run_coroutine_threadsafe, ) from contextlib import contextmanager from functools import partial @@ -19,17 +22,19 @@ from django.db import connection async def process_ws(receive, send): while True: event = await receive() - if event['type'] == 'websocket.connect': - await send({'type': 'websocket.accept'}) - elif event['type'] == 'websocket.disconnect': + if event["type"] == "websocket.connect": + await send({"type": "websocket.accept"}) + elif event["type"] == "websocket.disconnect": return - elif event['type'] == 'websocket.receive': + elif event["type"] == "websocket.receive": # ...maybe make it possible to request data through the ws? - if event['text'] == 'ping': - await send({ - 'type': 'websocket.send', - 'text': "pong", - }) + if event["text"] == "ping": + await send( + { + "type": "websocket.send", + "text": "pong", + }, + ) @contextmanager @@ -88,7 +93,8 @@ def filter_trigger_channeluser(coro, data, user, user_channels): return loop = get_running_loop() run_coroutine_threadsafe( - sync_to_async(update_user_channels)(coro, loop, user, user_channels), loop + sync_to_async(update_user_channels)(coro, loop, user, user_channels), + loop, ) @@ -119,14 +125,14 @@ def process_triggers(send, user, user_channels, notification): async def handle_websocket(scope, receive, send): - request = ASGIRequest({**scope, "method": f"_ws"}, BytesIO()) + request = ASGIRequest({**scope, "method": "_ws"}, BytesIO()) SessionMiddleware(lambda x: None).process_request(request) request.user = await aget_user(request) if not request.user.is_authenticated: event = await receive() - if event['type'] == 'websocket.connect': - await send({'type': 'websocket.accept'}) + if event["type"] == "websocket.connect": + await send({"type": "websocket.accept"}) await send({"type": "websocket.close"}) return diff --git a/manage.py b/manage.py index 61e7f93..ecd372e 100755 --- a/manage.py +++ b/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env venv/bin/python """Django's command-line utility for administrative tasks.""" + import os import subprocess import sys @@ -8,13 +9,14 @@ from pathlib import Path def setup_virtual_env(): # pragma: no cover venv_dir = Path(__file__).parent / "venv" - subprocess.run(["bash", str(venv_dir.parent / "setup_venv.sh")]) + subprocess.run(["bash", str(venv_dir.parent / "setup_venv.sh")], check=False) os.environ.setdefault("VIRTUAL_ENV", str(venv_dir)) venv_bin = str(venv_dir / "bin") if venv_bin not in os.environ["PATH"].split(":"): os.environ["PATH"] = f"{venv_bin}:{os.environ['PATH']}" os.environ.pop("PYTHONHOME", None) + def main(): """Run administrative tasks.""" if "VIRTUAL_ENV" not in os.environ: @@ -26,10 +28,10 @@ def main(): raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" + "forget to activate a virtual environment?", ) from exc execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/setup_venv.sh b/setup_venv.sh index 7e7cbce..8327f26 100644 --- a/setup_venv.sh +++ b/setup_venv.sh @@ -5,4 +5,5 @@ export PYTHON="${PYTHON:-python}" "${PYTHON}" -m venv venv . venv/bin/activate "${PYTHON}" -m pip install -qU pip -"${PYTHON}" -m pip install -qU django 'uvicorn[standard]' 'psycopg[c]' django-pgtrigger +"${PYTHON}" -m pip install -qU \ + django 'uvicorn[standard]' 'psycopg[c]' django-pgtrigger pre-commit ruff