feat: 多用户系统改造(数据模型 + 认证 API)

This commit is contained in:
maoshen
2026-04-15 02:59:36 +00:00
parent 75423d4e0e
commit e6aecd2752
13 changed files with 283 additions and 13 deletions

View File

@@ -0,0 +1,34 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'date_joined']
read_only_fields = ['date_joined']
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, min_length=6)
class Meta:
model = User
fields = ['username', 'email', 'password']
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
email=validated_data.get('email', ''),
password=validated_data['password']
)
return user
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(write_only=True)
def validate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("用户名或密码错误")

View File

@@ -0,0 +1,9 @@
from django.urls import path
from .views import RegisterView, LoginView, LogoutView, CurrentUserView
urlpatterns = [
path('register/', RegisterView.as_view(), name='register'),
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('me/', CurrentUserView.as_view(), name='current-user'),
]

View File

@@ -0,0 +1,54 @@
from rest_framework import generics, permissions, status
from rest_framework.response import Response
from rest_framework.authtoken.serializers import AuthTokenSerializer
from django.contrib.auth import login, logout
from django.contrib.auth.models import User
from .serializers import UserSerializer, RegisterSerializer, LoginSerializer
class RegisterView(generics.CreateAPIView):
"""用户注册"""
serializer_class = RegisterSerializer
permission_classes = [permissions.AllowAny]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
'user': UserSerializer(user).data,
'message': '注册成功'
}, status=status.HTTP_201_CREATED)
class LoginView(generics.GenericAPIView):
"""用户登录"""
serializer_class = LoginSerializer
permission_classes = [permissions.AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
login(request, user)
return Response({
'user': UserSerializer(user).data,
'message': '登录成功'
})
class LogoutView(generics.GenericAPIView):
"""用户登出"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request, *args, **kwargs):
logout(request)
return Response({'message': '登出成功'})
class CurrentUserView(generics.RetrieveAPIView):
"""当前用户信息"""
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
def get_object(self):
return self.request.user

View File

@@ -0,0 +1,54 @@
# Generated by Django 4.2.11 on 2026-04-15 02:59
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('diary', '0007_comment_creativity_comment_efficiency_and_more'),
]
operations = [
migrations.RemoveField(
model_name='comment',
name='created_by',
),
migrations.AddField(
model_name='comment',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL, verbose_name='创建者'),
),
migrations.AddField(
model_name='diaryentry',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='diary_entries', to=settings.AUTH_USER_MODEL, verbose_name='用户'),
),
migrations.AddField(
model_name='experience',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='experiences', to=settings.AUTH_USER_MODEL, verbose_name='用户'),
),
migrations.AddField(
model_name='task',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to=settings.AUTH_USER_MODEL, verbose_name='创建者'),
),
migrations.AlterField(
model_name='diaryentry',
name='date',
field=models.DateField(verbose_name='日期'),
),
migrations.AlterField(
model_name='task',
name='assigned_to',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tasks', to=settings.AUTH_USER_MODEL, verbose_name='负责人'),
),
migrations.AlterUniqueTogether(
name='diaryentry',
unique_together={('user', 'date')},
),
]

View File

@@ -1,5 +1,6 @@
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class DiaryEntry(models.Model):
"""
@@ -16,7 +17,8 @@ class DiaryEntry(models.Model):
2. 确认不影响日历显示
3. 运行 test_frontend.py diary 验证
"""
date = models.DateField('日期', unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户', related_name='diary_entries', null=True, blank=True)
date = models.DateField('日期')
title = models.CharField('标题', max_length=200, default='每日日记')
content = models.TextField('日记内容', blank=True, default='')
completed_tasks = models.TextField('完成的任务', blank=True, default='')
@@ -35,6 +37,7 @@ class DiaryEntry(models.Model):
ordering = ['-date']
verbose_name = '日记'
verbose_name_plural = '日记'
unique_together = ['user', 'date'] # 每个用户每天一条日记
def __str__(self):
return f"{self.date} - {self.title}"
@@ -70,6 +73,7 @@ class Experience(models.Model):
修改前阅读 docs/EXPERIENCE.md
"""
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户', related_name='experiences', null=True, blank=True)
title = models.CharField('标题', max_length=200)
category = models.CharField('类别', max_length=50, choices=[
('deployment', '📦 部署'),
@@ -134,7 +138,7 @@ class Comment(models.Model):
creativity = models.IntegerField('创新性', null=True, blank=True, help_text='1-10 分')
learning = models.IntegerField('学习价值', null=True, blank=True, help_text='1-10 分')
created_by = models.CharField('创建者', max_length=100, default='北极星')
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='创建者', related_name='comments', null=True, blank=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
@@ -169,7 +173,8 @@ class Task(models.Model):
priority = models.CharField('优先级', max_length=20, choices=PRIORITY_CHOICES, default='medium')
progress_percent = models.IntegerField('进展百分比', default=0)
progress_notes = models.TextField('进展记录', blank=True, default='')
assigned_to = models.CharField('负责人', max_length=100, blank=True, default='码神')
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='创建者', related_name='tasks', null=True, blank=True)
assigned_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='负责人', related_name='assigned_tasks')
due_date = models.DateField('截止日期', null=True, blank=True)
completed_at = models.DateTimeField('完成时间', null=True, blank=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)

View File

@@ -3,11 +3,11 @@ from rest_framework.routers import DefaultRouter
from .views import DiaryEntryViewSet, DailyProgressViewSet, ExperienceViewSet, TaskViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'entries', DiaryEntryViewSet)
router.register(r'progress', DailyProgressViewSet)
router.register(r'experiences', ExperienceViewSet)
router.register(r'tasks', TaskViewSet)
router.register(r'comments', CommentViewSet)
router.register(r'entries', DiaryEntryViewSet, basename='diaryentry')
router.register(r'progress', DailyProgressViewSet, basename='dailyprogress')
router.register(r'experiences', ExperienceViewSet, basename='experience')
router.register(r'tasks', TaskViewSet, basename='task')
router.register(r'comments', CommentViewSet, basename='comment')
urlpatterns = [
path('', include(router.urls)),

View File

@@ -1,6 +1,7 @@
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import permissions
from django.utils import timezone
from .models import DiaryEntry, DailyProgress, Experience, Task, Comment
from .serializers import (
@@ -9,21 +10,24 @@ from .serializers import (
)
class DiaryEntryViewSet(viewsets.ModelViewSet):
queryset = DiaryEntry.objects.all()
serializer_class = DiaryEntrySerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return DiaryEntry.objects.filter(user=self.request.user)
@action(detail=False, methods=['get'])
def today(self, request):
"""获取今天的日记"""
today = timezone.now().date()
entry, created = DiaryEntry.objects.get_or_create(date=today)
entry, created = DiaryEntry.objects.get_or_create(user=request.user, date=today)
serializer = self.get_serializer(entry)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def recent(self, request):
"""获取最近 7 天的日记"""
entries = DiaryEntry.objects.order_by('-date')[:7]
entries = DiaryEntry.objects.filter(user=request.user).order_by('-date')[:7]
serializer = self.get_serializer(entries, many=True)
return Response(serializer.data)
@@ -73,8 +77,11 @@ class DailyProgressViewSet(viewsets.ModelViewSet):
class ExperienceViewSet(viewsets.ModelViewSet):
queryset = Experience.objects.all()
serializer_class = ExperienceSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Experience.objects.filter(user=self.request.user)
@action(detail=False, methods=['get'])
def by_category(self, request):
@@ -96,8 +103,11 @@ class ExperienceViewSet(viewsets.ModelViewSet):
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Task.objects.filter(user=self.request.user)
@action(detail=False, methods=['get'])
def by_status(self, request):

View File

@@ -21,8 +21,10 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
'diary',
'authentication',
]
MIDDLEWARE = [

View File

@@ -6,5 +6,6 @@ from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/auth/', include('authentication.urls')),
path('api/', include('diary.urls')),
]