Chrissy001 2026-03-06 11:07 采纳率: 0%
浏览 5

使用了自定义装饰器修饰视图函数,一直跳转到登录页面

有个评论按钮,点击之后跳转到了登录页面,与预算中的不符合
使用了自定义的装饰器修饰视图函数

def login_required(f):
    @wraps(f)
    def decorated_function(request: HttpRequest, *args, **kwargs):
        print("\n" + "🔐" * 30)
        print("【装饰器】开始检查")
        print(f"请求路径: {request.path}")
        print(f"Session中是否有username: {'username' in request.session}")
        # if 'username' in request.session:
        #     print(f"Session中的username: {request.session['username']}")
        # print("🔐" * 30 + "\n")
        # # 判断 session 中是否存在 'username'
        # if 'username' not in request.session:
        #     # 如果用户未登录,则重定向到登录页面,并将原始URL作为 `next` 参数传递
        #     return redirect(f"{reverse('login')}?next={request.get_full_path()}")
        # return f(request, *args, **kwargs)

        if not request.session.get('username'):
            print("❌ 未登录,重定向")
            return redirect(f"{reverse('login')}?next={request.path}")

        print("✅ 已登录,执行视图")
        return f(request, *args, **kwargs)

    return decorated_function

@login_required
def add_comments(request, movie_id):
    """
    添加电影评论
    """
    # 获取当前登录用户
    username = request.session.get('username')
    # userInfo = User.objects.get(username=username)
    # userInfo = request.user
    if not username:
        # 未登录,重定向到登录页
        return redirect(f'{settings.LOGIN_URL}?next={request.path}')

    try:
        userInfo = User.objects.get(username=username)
    except User.DoesNotExist:
        # 用户不存在,清除 session 并重定向
        request.session.flush()
        return redirect(f'{settings.LOGIN_URL}?next={request.path}')

    # 获取电影信息
    movie = get_object_or_404(MovieInfo, movie_id=movie_id)

    # 获取该电影的现有评论
    existing_comments = getCommentData.get_comments_by_movie(movie.title)

    # 检查用户是否已经评论过
    already_commented = getCommentData.check_user_commented(movie.title, userInfo.id)

    if request.method == 'POST':
        if already_commented:
            messages.error(request, '您已经评论过这部电影了!')
        else:
            # 获取表单数据
            star = request.POST.get('star')
            content = request.POST.get('content')

            # 验证数据
            if not star or not content:
                messages.error(request, '评分和评论内容不能为空!')
            else:
                # 准备评论数据
                comment_data = {
                    'movie_id': movie_id,
                    'movie_name': movie.title,
                    'user_id': userInfo.id,
                    'user_name': userInfo.username,
                    'user_url': getattr(userInfo, 'avatar', ''),
                    'star': star,
                    'content': content
                }

                # 保存评论
                result = getCommentData.add_comment(comment_data)

                if result:
                    messages.success(request, '评论发布成功!')
                    return redirect('data_operation')
                else:
                    messages.error(request, '评论发布失败,请重试!')

    return render(request, 'add_comments.html', {
        'userInfo': userInfo,
        'movie': movie,
        'existing_comments': existing_comments,
        'already_commented': already_commented,
        'movie_id': movie_id,
    })



def movie_comments(request, movie_id):
    """
    电影评论列表页面
    """
    username = request.session.get('username')
    userInfo = User.objects.get(username=username)

    movie = get_object_or_404(MovieInfo, movie_id=movie_id)
    comments = getCommentData.get_comments_by_movie(movie.title)

    return render(request, 'add_comments.html', {
        'userInfo': userInfo,
        'movie': movie,
        'comments': comments,
    })
def get_movie_by_id(movie_id):
    """
    根据电影ID获取电影信息
    """
    try:
        return MovieInfo.objects.get(movie_id=movie_id)
    except MovieInfo.DoesNotExist:
        return None

def get_comments_by_movie(movie_name):
    """
    获取电影的所有评论
    """
    return Comment.objects.filter(movie_name=movie_name).order_by('-created_at')

def add_comment(comment_data):
    """
    添加评论
    comment_data: {
        'movie_id': 电影ID,
        'movie_name': 电影名称,
        'user_id': 用户ID,
        'user_name': 用户名,
        'user_url': 用户头像,
        'star': 评分,
        'content': 评论内容
    }
    """
    try:
        comment = Comment.objects.create(
            movie_name=comment_data.get('movie_name'),
            user_name=comment_data.get('user_name'),
            user_url=comment_data.get('user_url', ''),
            user_id=comment_data.get('user_id'),
            star=float(comment_data.get('star')),
            review_date=timezone.now(),
            content=comment_data.get('content'),
            useful=0
        )
        return comment
    except Exception as e:
        print(f"添加评论失败: {e}")
        return None

def check_user_commented(movie_name, user_id):
    """
    检查用户是否已经评论过该电影
    """
    return Comment.objects.filter(
        movie_name=movie_name,
        user_id=user_id
    ).exists()

def increment_useful(comment_id):
    """
    增加评论的有用数
    """
    try:
        comment = Comment.objects.get(id=comment_id)
        comment.useful += 1
        comment.save()
        return comment.useful
    except Comment.DoesNotExist:
        return None

```html

<!-- templates/add_comments.html -->
{% extends 'base.html' %}

{% block title %}添加电影评论{% endblock %}

{% block content %}
<div class="pagetitle" style="display: flex; align-items: center">
    <div style="margin-right: auto">
        <h1>添加电影评论</h1>
        <nav>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="{% url 'home' %}">主页</a></li>
                <li class="breadcrumb-item"><a href="{% url 'data_operation' %}">数据操作</a></li>
                <li class="breadcrumb-item active">添加评论</li>
            </ol>
        </nav>
    </div>
</div>

<section class="section">
    <div class="row">
        <div class="col-lg-8">
            <!-- 电影信息卡片 -->
            <div class="card mb-3">
                <div class="card-body">
                    <div class="d-flex">
                        <div style="width: 120px; margin-right: 20px;">
                            <img src="{{ movie.poster_url }}" alt="{{ movie.title }}"
                                 style="width: 100%; height: 160px; object-fit: cover; border-radius: 8px;">
                        </div>
                        <div>
                            <h4>{{ movie.title }}</h4>
                            <p class="text-muted mb-2">导演:{{ movie.directors|default:"未知" }}</p>
                            <p class="text-muted mb-2">类型:{{ movie.genres|default:"未知" }}</p>
                            <p class="text-muted mb-2">上映年份:{{ movie.release_year|default:"未知" }}</p>
                            <p class="text-warning mb-0">
                                <i class="bi bi-star-fill"></i> {{ movie.rating|default:"暂无评分" }}
                            </p>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 评论表单卡片 -->
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">撰写评论</h5>

                    {% if messages %}
                        {% for message in messages %}
                            <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                                {{ message }}
                                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                            </div>
                        {% endfor %}
                    {% endif %}

                    {% if already_commented %}
                        <div class="alert alert-warning">
                            <i class="bi bi-exclamation-triangle-fill me-2"></i>
                            您已经评论过这部电影了,不能重复评论。
                        </div>
                        <a href="{% url 'data_operation' %}" class="btn btn-primary">
                            <i class="bi bi-arrow-left"></i> 返回数据页面
                        </a>
                    {% else %}
                        <form method="post">
                            {% csrf_token %}

                            <div class="mb-3">
                                <label class="form-label fw-bold">评分 <span class="text-danger">*</span></label>
                                <div class="rating-stars">
                                    <div class="btn-group" role="group">
                                        <input type="radio" class="btn-check" name="star" id="star5" value="5" autocomplete="off">
                                        <label class="btn btn-outline-warning" for="star5">⭐⭐⭐⭐⭐ 非常好</label>

                                        <input type="radio" class="btn-check" name="star" id="star4" value="4" autocomplete="off">
                                        <label class="btn btn-outline-warning" for="star4">⭐⭐⭐⭐ 很好</label>

                                        <input type="radio" class="btn-check" name="star" id="star3" value="3" autocomplete="off">
                                        <label class="btn btn-outline-warning" for="star3">⭐⭐⭐ 一般</label>

                                        <input type="radio" class="btn-check" name="star" id="star2" value="2" autocomplete="off">
                                        <label class="btn btn-outline-warning" for="star2">⭐⭐ 较差</label>

                                        <input type="radio" class="btn-check" name="star" id="star1" value="1" autocomplete="off">
                                        <label class="btn btn-outline-warning" for="star1">⭐ 很差</label>
                                    </div>
                                </div>
                            </div>

                            <div class="mb-3">
                                <label for="content" class="form-label fw-bold">评论内容 <span class="text-danger">*</span></label>
                                <textarea class="form-control" id="content" name="content" rows="5"
                                          placeholder="写下你对这部电影的看法..." required></textarea>
                                <div class="form-text">至少10个字,最多500字</div>
                            </div>

                            <div class="mb-3">
                                <button type="submit" class="btn btn-primary">
                                    <i class="bi bi-check-circle"></i> 发布评论
                                </button>
                                <a href="{% url 'data_operation' %}" class="btn btn-secondary">
                                    <i class="bi bi-x-circle"></i> 取消
                                </a>
                            </div>
                        </form>
                    {% endif %}
                </div>
            </div>
        </div>

        <!-- 右侧:已有评论列表 -->
        <div class="col-lg-4">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">
                        <i class="bi bi-chat-dots-fill me-2"></i>
                        已有评论 ({{ existing_comments.count }})
                    </h5>

                    {% if existing_comments %}
                        <div class="comments-list" style="max-height: 600px; overflow-y: auto;">
                            {% for comment in existing_comments %}
                                <div class="comment-item mb-3 p-3 border-bottom">
                                    <div class="d-flex justify-content-between align-items-center mb-2">
                                        <div class="d-flex align-items-center">
                                            {% if comment.user_url %}
                                                <img src="{{ comment.user_url }}" alt="avatar"
                                                     style="width: 30px; height: 30px; border-radius: 50%; margin-right: 10px;">
                                            {% else %}
                                                <div class="bg-secondary text-white rounded-circle d-flex align-items-center justify-content-center"
                                                     style="width: 30px; height: 30px; margin-right: 10px;">
                                                    <span>{{ comment.user_name|first|upper }}</span>
                                                </div>
                                            {% endif %}
                                            <strong>{{ comment.user_name }}</strong>
                                        </div>
                                        <span class="text-warning">
                                            {% for i in "12345"|make_list %}
                                                {% if forloop.counter <= comment.star %}
                                                    <i class="bi bi-star-fill"></i>
                                                {% else %}
                                                    <i class="bi bi-star"></i>
                                                {% endif %}
                                            {% endfor %}
                                        </span>
                                    </div>
                                    <p class="mb-2">{{ comment.content|truncatechars:100 }}</p>
                                    <div class="d-flex justify-content-between align-items-center">
                                        <small class="text-muted">{{ comment.review_date|date:"Y-m-d H:i" }}</small>
                                        <span class="text-muted">
                                            <i class="bi bi-hand-thumbs-up"></i> {{ comment.useful }}
                                        </span>
                                    </div>
                                </div>
                            {% endfor %}
                        </div>
                    {% else %}
                        <p class="text-muted text-center py-4">暂无评论,快来第一个评论吧!</p>
                    {% endif %}
                </div>
            </div>
        </div>
    </div>
</section>

<style>
.rating-stars .btn-group {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}
.rating-stars .btn {
    border-radius: 20px !important;
    margin: 2px;
}
.comments-list::-webkit-scrollbar {
    width: 5px;
}
.comments-list::-webkit-scrollbar-track {
    background: #f1f1f1;
}
.comments-list::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 5px;
}
.comments-list::-webkit-scrollbar-thumb:hover {
    background: #555;
}
</style>
{% endblock %}

```
[06/Mar/2026 10:58:18] "GET /login/?next=/add-comments/35861791/ HTTP/1.1" 200 4210

  • 写回答

2条回答 默认 最新

  • 檀越@新空间 2026-03-06 11:08
    关注

    上午好☀️☀️☀️️
    本答案参考通义千问

    您提到的 “点击评论按钮后跳转到登录页面,与预算中的不符合” 问题,通常是因为在访问需要登录的视图函数(如 add_comments)时,系统检测到用户未登录,因此重定向到了登录页面。这可能是由于以下原因导致的:


    问题分析

    1. 装饰器逻辑问题

      • 您的 login_required 装饰器中使用了 request.session.get('username') 来判断是否登录。
      • 如果 session 中没有 username,则会重定向到登录页。
    2. 请求路径问题

      • 在装饰器中,您使用了 request.path 作为 next 参数,但有些情况下,这个路径可能不是您期望的 URL,例如 /movie/1/comments/movie/1 是不同的路径,可能导致重定向错误。
    3. 未正确处理 next 参数

      • 登录成功后,如果未正确处理 next 参数,用户可能会被重定向到默认页面,而不是原来的评论页面。
    4. Session 中缺少 username

      • 用户可能没有登录或 Session 已过期,导致装饰器无法识别用户身份。

    🔧 解决方案

    第一步:确保装饰器逻辑正确

    修改您的 login_required 装饰器,使其更清晰地判断是否登录,并记录日志以便调试。

    from functools import wraps
    from django.shortcuts import redirect
    from django.urls import reverse
    
    def login_required(f):
        @wraps(f)
        def decorated_function(request, *args, **kwargs):
            print("\n" + "🔐" * 30)
            print("【装饰器】开始检查")
            print(f"请求路径: {request.path}")
            print(f"Session中是否有username: {'username' in request.session}")
    
            if not request.session.get('username'):
                print("❌ 未登录,重定向")
                # 使用 get_full_path() 确保获取完整的请求路径
                next_url = request.get_full_path()
                return redirect(f"{reverse('login')}?next={next_url}")
            
            print("✅ 已登录,执行视图")
            return f(request, *args, **kwargs)
    
        return decorated_function
    

    重点说明

    • 使用 request.get_full_path() 更准确地获取完整路径,包括查询参数。
    • 使用 reverse('login') 获取登录页面的 URL,避免硬编码。

    第二步:确保登录页面正确处理 next 参数

    在您的登录视图中,确保在用户登录成功后,将用户重定向到 next 参数指定的页面。

    from django.contrib.auth import login as auth_login
    from django.shortcuts import redirect, render
    from django.urls import reverse
    
    def login_view(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
    
            user = authenticate(username=username, password=password)
            if user is not None:
                auth_login(request, user)
                next_url = request.GET.get('next')
                if next_url:
                    return redirect(next_url)
                else:
                    return redirect('home')  # 默认跳转页面
            else:
                return render(request, 'login.html', {'error': '用户名或密码错误'})
        
        return render(request, 'login.html')
    

    重点说明

    • 登录成功后,优先检查 next 参数,若存在则跳转至该页面,否则跳转至默认页面。

    第三步:确保 add_comments 视图中不重复检查登录

    您可以删除 add_comments 视图中的登录判断逻辑,因为装饰器已经处理了登录验证。

    @login_required
    def add_comments(request, movie_id):
        """
        添加电影评论
        """
        # 删除以下代码
        # username = request.session.get('username')
        # if not username:
        #     return redirect(f'{settings.LOGIN_URL}?next={request.path}')
    
        try:
            userInfo = User.objects.get(username=request.session['username'])
        except User.DoesNotExist:
            request.session.flush()
            return redirect(f'{settings.LOGIN_URL}?next={request.path}')
    
        movie = get_object_or_404(MovieInfo, movie_id=movie_id)
        existing_comments = getCommentData.get_comments_by_movie(movie.title)
        already_commented = getCommentData.check_user_commented(movie.title, userInfo.id)
    
        if request.method == 'POST':
            # 处理表单数据...
            pass
    
        return render(request, 'add_comments.html', {
            'userInfo': userInfo,
            'movie': movie,
            'existing_comments': existing_comments,
            'already_commented': already_commented,
            'movie_id': movie_id,
        })
    

    重点说明

    • 保留装饰器即可,无需在视图中重复判断登录状态。

    第四步:确保 Session 正确保存用户信息

    确保在用户登录时,将 username 存入 Session:

    def login_view(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
    
            user = authenticate(username=username, password=password)
            if user is not None:
                auth_login(request, user)
                request.session['username'] = username  # 保存用户名到 Session
                next_url = request.GET.get('next')
                if next_url:
                    return redirect(next_url)
                else:
                    return redirect('home')
            else:
                return render(request, 'login.html', {'error': '用户名或密码错误'})
    

    重点说明

    • 在登录成功后,务必将 username 存入 Session,否则装饰器无法识别用户身份。

    📌 总结:关键步骤

    1. 修改装饰器逻辑,使用 request.get_full_path() 获取完整路径。
    2. 确保登录视图处理 next 参数,实现正确的跳转。
    3. 删除视图中重复的登录判断逻辑,由装饰器统一管理。
    4. 确保登录成功后将用户名存入 Session,保证装饰器能正确识别用户。

    最终效果

    • 用户点击评论按钮时,若已登录,直接进入评论页面;
    • 若未登录,跳转到登录页面,并在登录成功后自动跳转回原评论页面;
    • 避免了不必要的跳转和功能异常。

    如有更多细节或代码片段,欢迎继续提问!

    评论

报告相同问题?

问题事件

  • 创建了问题 3月6日