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

Django评分评论平台提交时出现IntegrityError:concours_id不能为空

问题:提交评论时触发IntegrityError,concours_id为空

我开发了一个允许用户提交星级评分和评论的意见平台,但用户提交操作时始终触发错误。具体错误信息为:

IntegrityError at /save-comment/,(1048, "Le champ 'concours_id' ne peut être vide (null)")

涉及视图concours.views.save_comment,需排查concours_id无法正确传递导致为空的问题。


模型代码

class AvisConcours(models.Model):
    concours = models.ForeignKey('concours.Concours', on_delete=models.CASCADE, related_name='avis_concours')
    note = models.ManyToManyField('concours.Note', related_name='avis_note', blank=True,)
    commentaire = models.TextField(blank=True)
    utilisateur = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = "Avis de concours"
        verbose_name_plural = "Avis de concours"

    def __str__(self):
        return f"{self.concours}" + " - " + f"{self.utilisateur}"

视图代码

def save_comment(request):
    if request.method == 'POST':
        commentaire = request.POST.get('commentaire')
        concours_id = request.POST.get('concours')
        utilisateur = request.user

        notes_to_create = []
        for note_type in NoteType.objects.all():
            rating_value = request.POST.get(f'rating_{note_type.id}')
            if rating_value is not None:  # Only create notes for non-empty ratings
                notes_to_create.append(Note(note_type=note_type, valeur=rating_value))

        # Save bulk Note
        created_notes = Note.objects.bulk_create(notes_to_create)

        # Save AvisConcours and link notes
        avis_concours = AvisConcours.objects.create(
            concours_id=concours_id,
            commentaire=commentaire,
            utilisateur=utilisateur
        )

        avis_concours.note.set(created_notes)  # Use set() to set ManyToManyField directly

        return redirect('merci_commentaire')

    return render(request, "page_commentaire.html")

模板代码

<div class="col-lg-8 ps-xxl-5 mt-5">
    {% if user.is_authenticated %}
    <div class="give-rating-area mb-5">
        <form method="post" action="/save-comment/"> {% csrf_token %}
            <input type="hidden" name="concours" value="{{concours.id}}">
            <!-- Début:: Utilisateur connecté -->
            <div class="give-rating-person">
                {% if user.is_authenticated %}
                <div class="thumb" style="width: 80px; height: 80px; border-radius: 50%; overflow: hidden;">
                    {% if user.profile_image %}
                    <img src="{{ user.profile_image.url }}" alt="{{ user.full_name }}'s profile image" style="width: 80%; height: 80%; object-fit: cover; border-radius: 80%;">
                    {% else %}
                    <img src="{% static 'images/avatar.jpg' %}" alt="Default profile image" style="width: 80%; height: 80%; object-fit: cover; border-radius: 80%;">
                    {% endif %}
                </div>
                
                <div class="content">
                    <h6>{{ user.full_name }}</h6>
                </div>
                {% endif %}
            </div>
            
            <!-- Début:: Utilisateur connecté -->

            <br><br>
            {% for note_type in note_types %}

            <div class="give-rating-person">
          
              <div class="content">
                <h6>{{ note_type.nom }}</h6>
              </div>
          
              <div class='give-rating'>
          
                {% for i in "12345" reversed %}
                  <span>
                    <input id='str{{ i }}_{{ note_type.id }}' 
                           name='rating_{{ note_type.id }}'
                           type='radio' value='{{ i }}'>
                    <label for='str{{ i }}_{{ note_type.id }}'>
                      <i class="la la-star fa-sm"></i>
                    </label>
                  </span>
                {% endfor %}
                
              </div>
          
            </div>
          
          {% endfor %}


            <div class="mt-4">
                    <textarea name="commentaire" class="form--control"
                              placeholder="Ecrivez votre commentaire" required>
                    </textarea>

                <div class="text-end">
                    <button type="submit" class="btn btn--base submitBtn">Soumettre
                    </button>
                </div>
            </div>
        </form>
    </div>
    {% else %}
    <div class="give-rating-area mb-5">
        <p class="text-center">Veuillez vous <a href="{% url 'connexion' %}"
            class=" text--base">connecter</a> d'abord pour soumettre votre avis. </p>                            </div>
    <div class="mt-3">
        <ul class="pagination justify-content-end">
        </ul>
    </div>
    {% endif %}
    
    <div class="customer-review-wrapper">
        <div>
            <ul class="pagination justify-content-end me-3">
            </ul>
        </div>
        <div class="review-block">
            {% if avis_concours.exists %}
            {% for avis in avis_concours %}
            <div class="col-lg-12">
                <div>
                    <div class="single-slide" style="width: 100%; display: inline-block;">
                        <div class="review-card">
                            <div class="give-rating-person">
                                {% if user.is_authenticated %}
                                    <div class="thumb" style="width: 50px; height: 50px; overflow: hidden; border-radius: 50%; margin-right: 10px;">
                                        <img src="{% if user.profile_image %}{{ user.profile_image.url }}{% else %}{% static 'images/avatar.jpg' %}{% endif %}" alt="Image de l'utilisateur" style="width: 100%; height: auto;">
                                    </div>
                                    <div class="content">
                                        <h6>{{ user.full_name }}</h6>
                                        <h6>  <p class="fs--14px mt-1">Concours commenté : <span class="text--base"><strong>{{ avis.concours.nom }}</strong></span></p>
                                        </h6>
                                    </div>
                                {% endif %}
                            </div>
                            <hr>

                            <div class="col-md-12">
                                <div class="review-card__ratings text--base">
                                    <div class="review-card__ratings-text">
                                        <h6>Note globlale de l'utilisateur</h6>
                                    </div>

                                    <div class="review-card__ratings-stars">
                                        <!-- Utilisez le filtre de modèle "rating_stars" pour afficher la moyenne des notes sous forme d'étoiles -->
                                        <p class="fs--30px text-muted"> <span
                                                style="font-weight: bold; font-size:30px"> {{ avis.average_rating }}/5</span>
                                            Basé sur les notes detaillés

                                        </p>
                                        {{ avis.average_rating|rating_stars|safe }}
                                    </div>
                                </div>
                            </div>
                            <hr>

                            <h6>Notes detaillés de l'utilisateur</h6>

                            <div class="row">
                                {% if avis.note.exists %}
                                {% for note in avis.note.all %}
                                <div class="col-md-4">
                                    <div class="review-card__ratings text--base">
                                        <div class="review-card__ratings-text">
                                            <p>{{note.note_type.nom}}</p>
                                        </div>
                                        {{ note.valeur|rating_stars|safe }}
                                    </div>
                                </div>
                                {% endfor %}
                                {% else %}
                                <div class="customer-review d-flex justify-content-center">
                                    <h5>Aucun avis pour le moment !</h5>
                                </div>
                                {% endif %}
                            </div>
                            <hr>
                            <h5>Commentaire de l'utilisateur</h5>
                            <p class="review-card__des">
                                {{avis.commentaire}}
                            </p>

                            <p class="review-card__date">{{ avis.date }}</p>
                        </div>
                    </div>
                </div>
            </div>
            <br>
            {% endfor %}
            {% else %}
            <div class="customer-review d-flex justify-content-center">
                <h5>Aucun avis pour le moment !</h5>
            </div>
            {% endif %}
        </div>
    </div>
    
</div>

排查分析与解决方案

核心问题点

  1. 模板上下文缺失concours对象:模板中使用{{concours.id}}填充隐藏字段,但如果渲染该模板的视图没有把对应的Concours实例传入上下文,这个字段值会为空,导致提交后concours_id无效。
  2. 视图未做参数校验:当前视图直接使用request.POST.get('concours'),未验证值是否存在、是否为有效竞赛ID,空值传入数据库触发完整性错误。

具体修复步骤

1. 确保模板渲染时传入concours对象

检查渲染page_commentaire.html的视图,必须将目标Concours实例添加到上下文:

def comment_page(request, concours_id):
    concours = get_object_or_404(Concours, id=concours_id)
    note_types = NoteType.objects.all()
    avis_concours = AvisConcours.objects.filter(concours=concours)
    return render(request, "page_commentaire.html", {
        "concours": concours,
        "note_types": note_types,
        "avis_concours": avis_concours
    })

2. 增强视图的参数校验与安全性

修改save_comment视图,提前验证concours_id的有效性,并用实例而非ID创建AvisConcours:

def save_comment(request):
    if request.method == 'POST':
        commentaire = request.POST.get('commentaire')
        concours_id = request.POST.get('concours')
        utilisateur = request.user

        # 校验concours_id非空且对应竞赛存在
        if not concours_id:
            messages.error(request, "无法识别对应的竞赛,请重试")
            return redirect(request.META.get('HTTP_REFERER', 'comment_page'))
        
        try:
            concours = Concours.objects.get(id=concours_id)
        except Concours.DoesNotExist:
            messages.error(request, "对应的竞赛不存在")
            return redirect(request.META.get('HTTP_REFERER', 'comment_page'))

        notes_to_create = []
        for note_type in NoteType.objects.all():
            rating_value = request.POST.get(f'rating_{note_type.id}')
            if rating_value is not None:
                notes_to_create.append(Note(note_type=note_type, valeur=rating_value))

        created_notes = Note.objects.bulk_create(notes_to_create)

        # 使用concours实例创建,避免ID传递的潜在问题
        avis_concours = AvisConcours.objects.create(
            concours=concours,
            commentaire=commentaire,
            utilisateur=utilisateur
        )

        avis_concours.note.set(created_notes)
        return redirect('merci_commentaire')

    return render(request, "page_commentaire.html")

3. 模板字段添加必填验证(可选)

给隐藏字段添加required属性,强制前端提交时携带该字段:

<input type="hidden" name="concours" value="{{concours.id}}" required>

内容的提问来源于stack exchange,提问作者oue serge Gedeon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 16:49:57