From c95dafe14d2cc4536c682d277594022123afdef6 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Fri, 7 Mar 2025 00:06:56 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- account/migrations/0001_initial.py | 20 +++++-- account/migrations/0002_user_role.py | 18 ------ account/migrations/0003_user_raw_password.py | 18 ------ ...alter_user_raw_password_alter_user_role.py | 23 -------- .../0005_alter_user_raw_password.py | 18 ------ account/migrations/0006_alter_user_options.py | 17 ------ account/migrations/0007_alter_user_options.py | 17 ------ account/models.py | 28 ++++++++- api/settings.py | 4 +- api/urls.py | 2 +- challenge/models.py | 3 - challenge/views.py | 3 - main.py | 6 -- {challenge => submission}/__init__.py | 0 {challenge => submission}/admin.py | 0 {challenge => submission}/apps.py | 4 +- .../migrations/0001_initial.py | 18 ++++-- .../migrations/__init__.py | 0 submission/models.py | 47 +++++++++++++++ {tutorial => task}/__init__.py | 0 task/admin.py | 5 ++ {tutorial => task}/apps.py | 6 +- task/migrations/0001_initial.py | 58 +++++++++++++++++++ {tutorial => task}/migrations/__init__.py | 0 task/models.py | 46 +++++++++++++++ {tutorial => task}/schemas.py | 0 tutorial/api.py => task/tutorial.py | 0 tutorial/admin.py | 4 -- .../migrations/0002_alter_tutorial_display.py | 18 ------ .../migrations/0003_alter_tutorial_options.py | 17 ------ .../migrations/0004_alter_tutorial_options.py | 17 ------ tutorial/models.py | 17 ------ 32 files changed, 219 insertions(+), 215 deletions(-) delete mode 100644 account/migrations/0002_user_role.py delete mode 100644 account/migrations/0003_user_raw_password.py delete mode 100644 account/migrations/0004_alter_user_raw_password_alter_user_role.py delete mode 100644 account/migrations/0005_alter_user_raw_password.py delete mode 100644 account/migrations/0006_alter_user_options.py delete mode 100644 account/migrations/0007_alter_user_options.py delete mode 100644 challenge/models.py delete mode 100644 challenge/views.py delete mode 100644 main.py rename {challenge => submission}/__init__.py (100%) rename {challenge => submission}/admin.py (100%) rename {challenge => submission}/apps.py (61%) rename {tutorial => submission}/migrations/0001_initial.py (51%) rename {challenge => submission}/migrations/__init__.py (100%) create mode 100644 submission/models.py rename {tutorial => task}/__init__.py (100%) create mode 100644 task/admin.py rename {tutorial => task}/apps.py (52%) create mode 100644 task/migrations/0001_initial.py rename {tutorial => task}/migrations/__init__.py (100%) create mode 100644 task/models.py rename {tutorial => task}/schemas.py (100%) rename tutorial/api.py => task/tutorial.py (100%) delete mode 100644 tutorial/admin.py delete mode 100644 tutorial/migrations/0002_alter_tutorial_display.py delete mode 100644 tutorial/migrations/0003_alter_tutorial_options.py delete mode 100644 tutorial/migrations/0004_alter_tutorial_options.py delete mode 100644 tutorial/models.py diff --git a/account/migrations/0001_initial.py b/account/migrations/0001_initial.py index 4f2caa8..84c3c77 100644 --- a/account/migrations/0001_initial.py +++ b/account/migrations/0001_initial.py @@ -1,8 +1,10 @@ -# Generated by Django 5.1.6 on 2025-02-27 13:22 +# Generated by Django 5.1.6 on 2025-03-06 16:03 import django.contrib.auth.models import django.contrib.auth.validators +import django.db.models.deletion import django.utils.timezone +from django.conf import settings from django.db import migrations, models @@ -29,16 +31,26 @@ class Migration(migrations.Migration): ('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')), + ('role', models.CharField(choices=[('super', '超级管理员'), ('admin', '管理员'), ('normal', '普通')], default='normal', max_length=20, verbose_name='权限')), + ('raw_password', models.CharField(blank=True, max_length=20, null=True, verbose_name='明文密码')), ('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', - 'abstract': False, + 'verbose_name': '用户', + 'verbose_name_plural': '用户', + 'ordering': ('-id',), }, managers=[ ('objects', django.contrib.auth.models.UserManager()), ], ), + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('total_score', models.IntegerField(default=0)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), ] diff --git a/account/migrations/0002_user_role.py b/account/migrations/0002_user_role.py deleted file mode 100644 index 43eec53..0000000 --- a/account/migrations/0002_user_role.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-03 02:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='role', - field=models.CharField(choices=[('super', '超级管理员'), ('admin', '管理员'), ('normal', '普通')], default='normal', max_length=20), - ), - ] diff --git a/account/migrations/0003_user_raw_password.py b/account/migrations/0003_user_raw_password.py deleted file mode 100644 index 33e3642..0000000 --- a/account/migrations/0003_user_raw_password.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-04 02:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0002_user_role'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='raw_password', - field=models.CharField(blank=True, max_length=20, null=True), - ), - ] diff --git a/account/migrations/0004_alter_user_raw_password_alter_user_role.py b/account/migrations/0004_alter_user_raw_password_alter_user_role.py deleted file mode 100644 index 3879e7e..0000000 --- a/account/migrations/0004_alter_user_raw_password_alter_user_role.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-04 02:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0003_user_raw_password'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='raw_password', - field=models.CharField(max_length=20, null=True, verbose_name='明文密码'), - ), - migrations.AlterField( - model_name='user', - name='role', - field=models.CharField(choices=[('super', '超级管理员'), ('admin', '管理员'), ('normal', '普通')], default='normal', max_length=20, verbose_name='权限'), - ), - ] diff --git a/account/migrations/0005_alter_user_raw_password.py b/account/migrations/0005_alter_user_raw_password.py deleted file mode 100644 index e709da6..0000000 --- a/account/migrations/0005_alter_user_raw_password.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-04 02:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0004_alter_user_raw_password_alter_user_role'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='raw_password', - field=models.CharField(blank=True, max_length=20, null=True, verbose_name='明文密码'), - ), - ] diff --git a/account/migrations/0006_alter_user_options.py b/account/migrations/0006_alter_user_options.py deleted file mode 100644 index a429d71..0000000 --- a/account/migrations/0006_alter_user_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-06 07:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0005_alter_user_raw_password'), - ] - - operations = [ - migrations.AlterModelOptions( - name='user', - options={'ordering': ('-id',)}, - ), - ] diff --git a/account/migrations/0007_alter_user_options.py b/account/migrations/0007_alter_user_options.py deleted file mode 100644 index 980099d..0000000 --- a/account/migrations/0007_alter_user_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-06 09:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0006_alter_user_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='user', - options={'ordering': ('-id',), 'verbose_name': '用户', 'verbose_name_plural': '用户'}, - ), - ] diff --git a/account/models.py b/account/models.py index 2daa419..48d5bc2 100644 --- a/account/models.py +++ b/account/models.py @@ -1,7 +1,9 @@ from django.db import models - from django.contrib.auth.models import AbstractUser +from django.db.models.signals import post_save +from django.dispatch import receiver + class RoleChoices(models.TextChoices): SUPER = "super", "超级管理员" @@ -23,6 +25,11 @@ class User(AbstractUser): verbose_name="明文密码", ) + def save(self, *args, **kwargs): + if self.is_superuser: + self.role = RoleChoices.SUPER + super().save(*args, **kwargs) + def set_password(self, raw_password): super().set_password(raw_password) self.raw_password = raw_password @@ -32,3 +39,22 @@ class User(AbstractUser): ordering = ("-id",) verbose_name = "用户" verbose_name_plural = verbose_name + + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + total_score = models.IntegerField(default=0) + + def __str__(self): + return self.user.username + + +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + instance.profile.save() diff --git a/api/settings.py b/api/settings.py index fe147b6..310ca45 100644 --- a/api/settings.py +++ b/api/settings.py @@ -49,8 +49,8 @@ INSTALLED_APPS = [ "corsheaders", "django_extensions", "account", - "tutorial", - "challenge", + "task", + "submission", ] MIDDLEWARE = [ diff --git a/api/urls.py b/api/urls.py index 6d081b5..85c0337 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,7 +22,7 @@ from ninja import NinjaAPI api = NinjaAPI() api.add_router("account/", "account.api.router") -api.add_router("tutorial/", "tutorial.api.router") +api.add_router("tutorial/", "task.tutorial.router") urlpatterns = [ path("admin/", admin.site.urls), diff --git a/challenge/models.py b/challenge/models.py deleted file mode 100644 index 71a8362..0000000 --- a/challenge/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/challenge/views.py b/challenge/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/challenge/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/main.py b/main.py deleted file mode 100644 index f5421a1..0000000 --- a/main.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(): - print("Hello from webapi!") - - -if __name__ == "__main__": - main() diff --git a/challenge/__init__.py b/submission/__init__.py similarity index 100% rename from challenge/__init__.py rename to submission/__init__.py diff --git a/challenge/admin.py b/submission/admin.py similarity index 100% rename from challenge/admin.py rename to submission/admin.py diff --git a/challenge/apps.py b/submission/apps.py similarity index 61% rename from challenge/apps.py rename to submission/apps.py index ad63d66..ebc1803 100644 --- a/challenge/apps.py +++ b/submission/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class ChallengeConfig(AppConfig): +class SubmissionConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'challenge' + name = 'submission' diff --git a/tutorial/migrations/0001_initial.py b/submission/migrations/0001_initial.py similarity index 51% rename from tutorial/migrations/0001_initial.py rename to submission/migrations/0001_initial.py index 604aa94..f7fff1f 100644 --- a/tutorial/migrations/0001_initial.py +++ b/submission/migrations/0001_initial.py @@ -1,6 +1,8 @@ -# Generated by Django 5.1.6 on 2025-03-03 11:38 +# Generated by Django 5.1.6 on 2025-03-06 16:03 +import django.db.models.deletion import django_extensions.db.fields +from django.conf import settings from django.db import migrations, models @@ -9,19 +11,23 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('task', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Tutorial', + name='Submission', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), - ('display', models.CharField(max_length=10, unique=True)), - ('title', models.CharField(max_length=100)), - ('content', models.TextField()), - ('is_public', models.BooleanField(default=False)), + ('score', models.IntegerField(default=0)), + ('html', models.TextField(blank=True, null=True)), + ('css', models.TextField(blank=True, null=True)), + ('js', models.TextField(blank=True, null=True)), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='task.task')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'get_latest_by': 'modified', diff --git a/challenge/migrations/__init__.py b/submission/migrations/__init__.py similarity index 100% rename from challenge/migrations/__init__.py rename to submission/migrations/__init__.py diff --git a/submission/models.py b/submission/models.py new file mode 100644 index 0000000..1c2b943 --- /dev/null +++ b/submission/models.py @@ -0,0 +1,47 @@ +from django.db import models +from django.db.models import Sum +from django.db.models.signals import post_save +from django.dispatch import receiver +from django_extensions.db.models import TimeStampedModel + +from account.models import Profile, User +from task.models import Task + + +class Submission(TimeStampedModel): + user = models.ForeignKey(User, on_delete=models.CASCADE) + task = models.ForeignKey(Task, on_delete=models.CASCADE) + score = models.IntegerField(default=0) + html = models.TextField(null=True, blank=True) + css = models.TextField(null=True, blank=True) + js = models.TextField(null=True, blank=True) + + def __str__(self): + return f"{self.user.username} - {self.task.title}" + + def get_task_type(self): + """ + 返回任务的具体类型(Challenge 或 Tutorial) + """ + return self.task.task_type + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.user.profile.update_total_score() + + +# 信号处理函数 +@receiver(post_save, sender=Submission) +def update_user_score(sender, instance, **kwargs): + """ + 当 Submission 保存后,自动更新用户的总分 + """ + total_score = ( + Submission.objects.filter(user=instance.user).aggregate( + total_score=Sum("score") + )["total_score"] + or 0 + ) + profile, created = Profile.objects.get_or_create(user=instance.user) + profile.total_score = total_score + profile.save() diff --git a/tutorial/__init__.py b/task/__init__.py similarity index 100% rename from tutorial/__init__.py rename to task/__init__.py diff --git a/task/admin.py b/task/admin.py new file mode 100644 index 0000000..fdbd8b2 --- /dev/null +++ b/task/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import Tutorial, Challenge + +admin.site.register(Tutorial) +admin.site.register(Challenge) diff --git a/tutorial/apps.py b/task/apps.py similarity index 52% rename from tutorial/apps.py rename to task/apps.py index 4d0bd4d..25edd55 100644 --- a/tutorial/apps.py +++ b/task/apps.py @@ -1,7 +1,7 @@ from django.apps import AppConfig -class TutorialConfig(AppConfig): +class TaskConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "tutorial" - verbose_name = "教程" + name = "task" + verbose_name = "任务" diff --git a/task/migrations/0001_initial.py b/task/migrations/0001_initial.py new file mode 100644 index 0000000..e38277e --- /dev/null +++ b/task/migrations/0001_initial.py @@ -0,0 +1,58 @@ +# Generated by Django 5.1.6 on 2025-03-06 16:03 + +import django.db.models.deletion +import django_extensions.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Task', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), + ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), + ('display', models.IntegerField(unique=True)), + ('title', models.CharField(max_length=100)), + ('content', models.TextField()), + ('task_type', models.CharField(choices=[('challenge', 'Challenge'), ('tutorial', 'Tutorial')], editable=False, max_length=20)), + ('is_public', models.BooleanField(default=False)), + ], + options={ + 'get_latest_by': 'modified', + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Challenge', + fields=[ + ('task_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='task.task')), + ('score', models.IntegerField(default=0)), + ], + options={ + 'verbose_name': '挑战', + 'verbose_name_plural': '挑战', + 'ordering': ('display',), + }, + bases=('task.task',), + ), + migrations.CreateModel( + name='Tutorial', + fields=[ + ('task_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='task.task')), + ], + options={ + 'verbose_name': '教程', + 'verbose_name_plural': '教程', + 'ordering': ('display',), + }, + bases=('task.task',), + ), + ] diff --git a/tutorial/migrations/__init__.py b/task/migrations/__init__.py similarity index 100% rename from tutorial/migrations/__init__.py rename to task/migrations/__init__.py diff --git a/task/models.py b/task/models.py new file mode 100644 index 0000000..cbf64aa --- /dev/null +++ b/task/models.py @@ -0,0 +1,46 @@ +from django.db import models +from django_extensions.db.models import TimeStampedModel + + +class Task(TimeStampedModel): + TASK_TYPE_CHOICES = [ + ("challenge", "Challenge"), + ("tutorial", "Tutorial"), + ] + + display = models.IntegerField(unique=True) + title = models.CharField(max_length=100) + content = models.TextField() + task_type = models.CharField( + max_length=20, + choices=TASK_TYPE_CHOICES, + editable=False, + ) + is_public = models.BooleanField(default=False) + + def save(self, *args, **kwargs): + if not self.task_type: + self.task_type = self.__class__.__name__.lower() + super().save(*args, **kwargs) + + +class Tutorial(Task): + def __str__(self): + return self.title + + class Meta: + ordering = ("display",) + verbose_name = "教程" + verbose_name_plural = verbose_name + + +class Challenge(Task): + score = models.IntegerField(default=0) + + def __str__(self): + return self.title + + class Meta: + ordering = ("display",) + verbose_name = "挑战" + verbose_name_plural = verbose_name diff --git a/tutorial/schemas.py b/task/schemas.py similarity index 100% rename from tutorial/schemas.py rename to task/schemas.py diff --git a/tutorial/api.py b/task/tutorial.py similarity index 100% rename from tutorial/api.py rename to task/tutorial.py diff --git a/tutorial/admin.py b/tutorial/admin.py deleted file mode 100644 index 87a26f2..0000000 --- a/tutorial/admin.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.contrib import admin -from .models import Tutorial - -admin.site.register(Tutorial) diff --git a/tutorial/migrations/0002_alter_tutorial_display.py b/tutorial/migrations/0002_alter_tutorial_display.py deleted file mode 100644 index f018b71..0000000 --- a/tutorial/migrations/0002_alter_tutorial_display.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-03 14:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tutorial', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='tutorial', - name='display', - field=models.IntegerField(unique=True), - ), - ] diff --git a/tutorial/migrations/0003_alter_tutorial_options.py b/tutorial/migrations/0003_alter_tutorial_options.py deleted file mode 100644 index 41cd717..0000000 --- a/tutorial/migrations/0003_alter_tutorial_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-04 13:45 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tutorial', '0002_alter_tutorial_display'), - ] - - operations = [ - migrations.AlterModelOptions( - name='tutorial', - options={'verbose_name': '教程', 'verbose_name_plural': '教程'}, - ), - ] diff --git a/tutorial/migrations/0004_alter_tutorial_options.py b/tutorial/migrations/0004_alter_tutorial_options.py deleted file mode 100644 index 29ed7ba..0000000 --- a/tutorial/migrations/0004_alter_tutorial_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-06 07:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tutorial', '0003_alter_tutorial_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='tutorial', - options={'ordering': ('display',), 'verbose_name': '教程', 'verbose_name_plural': '教程'}, - ), - ] diff --git a/tutorial/models.py b/tutorial/models.py deleted file mode 100644 index 00d7d00..0000000 --- a/tutorial/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import models -from django_extensions.db.models import TimeStampedModel - - -class Tutorial(TimeStampedModel): - display = models.IntegerField(unique=True) - title = models.CharField(max_length=100) - content = models.TextField() - is_public = models.BooleanField(default=False) - - def __str__(self): - return self.title - - class Meta: - ordering = ("display",) - verbose_name = "教程" - verbose_name_plural = verbose_name