重构数据

This commit is contained in:
2025-03-07 00:06:56 +08:00
parent 317f29b486
commit c95dafe14d
32 changed files with 219 additions and 215 deletions

View File

@@ -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)),
],
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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='权限'),
),
]

View File

@@ -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='明文密码'),
),
]

View File

@@ -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',)},
),
]

View File

@@ -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': '用户'},
),
]

View File

@@ -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()

View File

@@ -49,8 +49,8 @@ INSTALLED_APPS = [
"corsheaders",
"django_extensions",
"account",
"tutorial",
"challenge",
"task",
"submission",
]
MIDDLEWARE = [

View File

@@ -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),

View File

@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -1,6 +0,0 @@
def main():
print("Hello from webapi!")
if __name__ == "__main__":
main()

View File

@@ -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'

View File

@@ -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',

47
submission/models.py Normal file
View File

@@ -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()

5
task/admin.py Normal file
View File

@@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Tutorial, Challenge
admin.site.register(Tutorial)
admin.site.register(Challenge)

View File

@@ -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 = "任务"

View File

@@ -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',),
),
]

46
task/models.py Normal file
View File

@@ -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

View File

@@ -1,4 +0,0 @@
from django.contrib import admin
from .models import Tutorial
admin.site.register(Tutorial)

View File

@@ -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),
),
]

View File

@@ -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': '教程'},
),
]

View File

@@ -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': '教程'},
),
]

View File

@@ -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