fix: just forgot something...
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
from django.contrib import admin
|
||||
from .models import Memo, MemoImage
|
||||
|
||||
|
||||
class MemoImageInline(admin.TabularInline):
|
||||
model = MemoImage
|
||||
extra = 0
|
||||
|
||||
|
||||
@admin.register(Memo)
|
||||
class MemoAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'created_by', 'created_at', 'updated_at', 'is_active']
|
||||
list_filter = ['is_active', 'created_at', 'updated_at']
|
||||
search_fields = ['title', 'content']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
inlines = [MemoImageInline]
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not change:
|
||||
obj.created_by = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(MemoImage)
|
||||
class MemoImageAdmin(admin.ModelAdmin):
|
||||
list_display = ['memo', 'alt_text', 'uploaded_at']
|
||||
list_filter = ['uploaded_at']
|
||||
search_fields = ['memo__title', 'alt_text']
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MemoConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "memo"
|
||||
@@ -0,0 +1,40 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Memo(models.Model):
|
||||
title = models.CharField(max_length=200, verbose_name="标题")
|
||||
content = models.TextField(verbose_name="内容", blank=True)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="创建者")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||||
is_active = models.BooleanField(default=True, verbose_name="是否启用")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "备忘录"
|
||||
verbose_name_plural = "备忘录"
|
||||
ordering = ['-updated_at']
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def content_preview(self):
|
||||
"""返回内容的缩略预览"""
|
||||
if len(self.content) > 100:
|
||||
return self.content[:100] + "..."
|
||||
return self.content
|
||||
|
||||
|
||||
class MemoImage(models.Model):
|
||||
memo = models.ForeignKey(Memo, on_delete=models.CASCADE, related_name='images', verbose_name="备忘录")
|
||||
image = models.ImageField(upload_to='memo_images/', verbose_name="图片")
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
|
||||
alt_text = models.CharField(max_length=200, blank=True, verbose_name="图片描述")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "备忘录图片"
|
||||
verbose_name_plural = "备忘录图片"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.memo.title} - 图片"
|
||||
@@ -0,0 +1,33 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Memo, MemoImage
|
||||
|
||||
|
||||
class MemoImageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = MemoImage
|
||||
fields = ['id', 'image', 'uploaded_at', 'alt_text']
|
||||
|
||||
|
||||
class MemoSerializer(serializers.ModelSerializer):
|
||||
images = MemoImageSerializer(many=True, read_only=True)
|
||||
content_preview = serializers.ReadOnlyField()
|
||||
created_by_name = serializers.CharField(source='created_by.username', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Memo
|
||||
fields = ['id', 'title', 'content', 'content_preview', 'created_by', 'created_by_name',
|
||||
'created_at', 'updated_at', 'is_active', 'images']
|
||||
read_only_fields = ['created_by', 'created_at', 'updated_at']
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['created_by'] = self.context['request'].user
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class MemoListSerializer(serializers.ModelSerializer):
|
||||
content_preview = serializers.ReadOnlyField()
|
||||
created_by_name = serializers.CharField(source='created_by.username', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Memo
|
||||
fields = ['id', 'title', 'content_preview', 'created_by_name', 'updated_at']
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import MemoViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'memos', MemoViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('api/', include(router.urls)),
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
from rest_framework import viewsets, filters, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.parsers import MultiPartParser, FormParser
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django.db.models import Q
|
||||
from .models import Memo, MemoImage
|
||||
from .serializers import MemoSerializer, MemoListSerializer, MemoImageSerializer
|
||||
|
||||
|
||||
class MemoViewSet(viewsets.ModelViewSet):
|
||||
queryset = Memo.objects.filter(is_active=True)
|
||||
serializer_class = MemoSerializer
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
search_fields = ['title', 'content']
|
||||
ordering_fields = ['created_at', 'updated_at']
|
||||
ordering = ['-updated_at']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return MemoListSerializer
|
||||
return MemoSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
search = self.request.query_params.get('search', None)
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(title__icontains=search) | Q(content__icontains=search)
|
||||
)
|
||||
return queryset
|
||||
|
||||
@action(detail=True, methods=['post'], parser_classes=[MultiPartParser, FormParser])
|
||||
def upload_image(self, request, pk=None):
|
||||
memo = self.get_object()
|
||||
image = request.FILES.get('image')
|
||||
alt_text = request.data.get('alt_text', '')
|
||||
|
||||
if not image:
|
||||
return Response({'error': '请选择图片文件'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
memo_image = MemoImage.objects.create(
|
||||
memo=memo,
|
||||
image=image,
|
||||
alt_text=alt_text
|
||||
)
|
||||
|
||||
serializer = MemoImageSerializer(memo_image)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@action(detail=True, methods=['delete'])
|
||||
def delete_image(self, request, pk=None):
|
||||
memo = self.get_object()
|
||||
image_id = request.data.get('image_id')
|
||||
|
||||
try:
|
||||
memo_image = MemoImage.objects.get(id=image_id, memo=memo)
|
||||
memo_image.delete()
|
||||
return Response({'message': '图片删除成功'}, status=status.HTTP_200_OK)
|
||||
except MemoImage.DoesNotExist:
|
||||
return Response({'error': '图片不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
Reference in New Issue
Block a user