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

Django DRF歌曲收藏功能故障:重复收藏与状态判断失效求助

收藏夹功能修复方案:解决重复添加与按钮状态异常问题

核心问题梳理

  1. 模板调用song.is_favorite时未传入当前用户,导致收藏状态判断完全失效
  2. API的POST接口未做重复收藏校验,允许同一用户重复添加同一歌曲
  3. HTML按钮缺少data-song-id属性,导致JS无法准确定位元素更新状态
  4. API的GET接口逻辑错误,未正确返回歌曲的收藏状态

具体修复步骤

1. 修复模板中的收藏状态判断与按钮属性

修改song_list.html中的按钮代码,传入用户参数给is_favorite方法,并添加data-song-id属性:

{% if user.is_authenticated %}
  {% if song.is_favorite(user) %}
    <button data-song-id="{{ song.id }}" onclick="removeFavorite({{ song.id }})">Supprimer des favoris</button>
  {% else %}
    <button data-song-id="{{ song.id }}" onclick="addFavorite({{ song.id }})">Ajouter aux favoris</button>
  {% endif %}
{% else %}
  <a href="{% url 'login' %}?next={{ request.path }}">Connectez-vous pour ajouter aux favoris</a>
{% endif %}

2. 优化Song模型的is_favorite方法(可选,语义更清晰)

因为Favorite模型中song字段设置了related_name='favorites',可以把方法改成更直观的写法:

def is_favorite(self, user):
    # 用related_name替代favorite_set,语义更清晰
    return self.favorites.filter(user=user).exists()

3. 修复API视图逻辑,避免重复添加并正确返回收藏状态

更新FavoritesongView,修正GET方法逻辑,同时在POST方法中添加重复校验:

class FavoritesongView(APIView):
    def get(self, request, song_id):
        try:
            song = Song.objects.get(id=song_id)
            # 返回当前用户是否收藏该歌曲的状态
            is_favorited = song.is_favorite(request.user)
            return Response({"is_favorite": is_favorited}, status=status.HTTP_200_OK)
        except Song.DoesNotExist:
            return Response({"error": "Song not found"}, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            print(f"Error in get favorite: {e}")
            return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) 
            
    def post(self, request, song_id):
        try:
            song = Song.objects.get(id=song_id)
            # 检查是否已收藏,避免重复添加
            if Favorite.objects.filter(user=request.user, song=song).exists():
                return Response({"error": "Song already in favorites"}, status=status.HTTP_409_CONFLICT)
            favorite = Favorite(user=request.user, song=song)
            favorite.save()
            return Response({"is_favorite": True}, status=status.HTTP_201_CREATED)
        except Song.DoesNotExist:
            return Response({"error": "Song not found"}, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            print(f"ERROR in post request: {e}")
            return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, song_id):
        try:
            favorite = Favorite.objects.get(user=request.user, song_id=song_id)
            favorite.delete()
            return Response({"is_favorite": False}, status=status.HTTP_200_OK)
        except Favorite.DoesNotExist:
            return Response({"error": "Favorite not found"}, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            print(f"ERROR in delete request: {e}")
            return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)

4. 优化JavaScript逻辑,处理API返回状态并增强可靠性

更新JS代码,确保请求失败时能正确处理,同时添加页面加载时的按钮状态初始化:

function updateFavoriteButton(songId, isFavorite) {
  const button = document.querySelector(`button[data-song-id="${songId}"]`);
  if (!button) return; // 避免找不到元素报错
  if (isFavorite) {
    button.textContent = 'Supprimer des favoris';
    button.onclick = () => removeFavorite(songId);
  } else {
    button.textContent = 'Ajouter aux favoris';
    button.onclick = () => addFavorite(songId);
  }
}

async function addFavorite(songId) {
    try {
        const response = await fetch(`/api/songs/${songId}/favorite/`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCookie('csrftoken'),
            },
            body: JSON.stringify({ song_id: songId }),
        });
        const data = await response.json();
        if (response.ok) {
            updateFavoriteButton(songId, data.is_favorite);
        } else {
            console.error('添加失败:', data.error);
        }
    } catch (error) {
        console.error('请求错误:', error);
    }
}

async function removeFavorite(songId) {
  try {
      const response = await fetch(`/api/songs/${songId}/favorite/`, {
          method: 'DELETE',
          headers: {
              'X-CSRFToken': getCookie('csrftoken'),
          },
      });
      const data = await response.json();
      if (response.ok) {
          updateFavoriteButton(songId, data.is_favorite);
      } else {
          console.error('删除失败:', data.error);
      }
  } catch (error) {
      console.error('请求错误:', error);
  }
}

// 页面加载时初始化所有按钮状态
async function initFavoriteButtons() {
  const userIsAuthenticated = {{ user.is_authenticated|lower }};
  if (!userIsAuthenticated) return;
  const buttons = document.querySelectorAll('button[data-song-id]');
  for (const button of buttons) {
    const songId = button.dataset.songId;
    const response = await fetch(`/api/songs/${songId}/favorite/`);
    const data = await response.json();
    updateFavoriteButton(songId, data.is_favorite);
  }
}

// 页面加载完成后执行初始化
document.addEventListener('DOMContentLoaded', initFavoriteButtons);

function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

内容的提问来源于stack exchange,提问作者Roy Akany

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:28:04