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