You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于DRF+React实现单篇博客多类别选择与存储的技术问询

实现Django+React博客文章多选类别提交方案

一、前端React修改(多选组件替换+提交数据处理)

将原单选下拉框改为多选组件,调整状态管理和提交逻辑,以下是完整修改示例:

原单选版本参考(用户现有代码)

import { useState, useEffect } from 'react';

const CreatePost = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [categories, setCategories] = useState([]);
  const [selectedCategory, setSelectedCategory] = useState('');

  useEffect(() => {
    fetch('/api/categories/')
      .then(res => res.json())
      .then(data => setCategories(data));
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const postData = {
      title,
      content,
      category: selectedCategory
    };
    await fetch('/api/posts/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': getCookie('csrftoken')
      },
      body: JSON.stringify(postData)
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="文章标题"
      />
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="文章内容"
      />
      <select
        value={selectedCategory}
        onChange={(e) => setSelectedCategory(e.target.value)}
      >
        <option value="">选择类别</option>
        {categories.map(category => (
          <option key={category.id} value={category.id}>
            {category.name}
          </option>
        ))}
      </select>
      <button type="submit">发布文章</button>
    </form>
  );
};

export default CreatePost;

修改后的多选版本代码

import { useState, useEffect } from 'react';

const CreatePost = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [categories, setCategories] = useState([]);
  const [selectedCategories, setSelectedCategories] = useState([]); // 改为数组存储多选ID

  // 获取类别逻辑不变
  useEffect(() => {
    fetch('/api/categories/')
      .then(res => res.json())
      .then(data => setCategories(data));
  }, []);

  // 处理多选状态变化
  const handleCategoryChange = (e) => {
    const selectedIds = Array.from(e.target.options)
      .filter(option => option.selected)
      .map(option => option.value);
    setSelectedCategories(selectedIds);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    const postData = {
      title,
      content,
      categories: selectedCategories // 提交类别ID数组
    };
    await fetch('/api/posts/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': getCookie('csrftoken')
      },
      body: JSON.stringify(postData)
    });
    // 提交后重置表单
    setTitle('');
    setContent('');
    setSelectedCategories([]);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="文章标题"
        required
      />
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="文章内容"
        required
      />
      {/* 添加multiple属性启用多选,调整高度提升体验 */}
      <select
        multiple
        value={selectedCategories}
        onChange={handleCategoryChange}
        style={{ height: '120px', width: '100%' }}
      >
        {categories.map(category => (
          <option key={category.id} value={category.id}>
            {category.name}
          </option>
        ))}
      </select>
      <p style={{ fontSize: '14px', color: '#666' }}>提示:按住Ctrl(Windows)/Command(Mac)可选择多个类别</p>
      <button type="submit" style={{ marginTop: '10px' }}>发布文章</button>
    </form>
  );
};

// 辅助函数:获取CSRF Token
function getCookie(name) {
  let cookieValue = null;
  if (document.cookie && document.cookie !== '') {
    const cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i].trim();
      if (cookie.substring(0, name.length + 1) === (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

export default CreatePost;

二、后端Django调整(支持多对多字段接收)

1. 模型配置(确保多对多关系)

# models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    categories = models.ManyToManyField(Category, related_name='posts') # 多对多关联字段
    created_at = models.DateTimeField(auto_now_add=True)

2. Serializer修改(支持接收ID数组)

# serializers.py
from rest_framework import serializers
from .models import Post, Category

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name']

class PostSerializer(serializers.ModelSerializer):
    categories = serializers.PrimaryKeyRelatedField(
        many=True,
        queryset=Category.objects.all()
    )

    class Meta:
        model = Post
        fields = ['id', 'title', 'content', 'categories', 'created_at']

    def create(self, validated_data):
        # 先创建文章,再关联多对多类别
        categories_data = validated_data.pop('categories')
        post = Post.objects.create(**validated_data)
        post.categories.add(*categories_data)
        return post

3. View配置(无需额外修改,依赖Serializer处理)

# views.py
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

关键注意事项

  • 原生多选select需要按住快捷键批量选择,若要更友好的交互,可替换为React-Select等第三方多选组件,核心逻辑保持一致。
  • 后端必须先创建Post实例,再通过add(*categories_data)关联多对多关系,不能直接在创建时传递多对多数据。
  • 确保CSRF Token正确传递,避免403权限错误。

内容的提问来源于stack exchange,提问作者Abbasali Rezaei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 15:17:19