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

Django中如何在单个HTML页面按外键ID查看关联对象?

问题:Django按外键ID过滤通知失败,返回所有结果

我使用Django框架开发通知功能,希望实现按外键ID过滤通知,从而查看每个事件的处理流程。但尝试后返回了所有通知结果,不清楚如何正确按外键ID进行过滤。


相关代码实现

View代码

def user_reports(request):
    profile = get_object_or_404(UserProfile, user=request.user)
    incidentReports = IncidentGeneral.objects.all().order_by('-updated_at')
    # incident_general = IncidentGeneral.objects.filter(pk=request.id)
    notifications = Notification.objects.all().order_by('-date')
    paginator = Paginator(incidentReports, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    if request.method == 'POST':
        for i in incidentReports:
            x = request.POST.get(str(i.id))
            print(x)
            if str(x) == 'on':
                b = IncidentGeneral.objects.get(id=i.id)
                b.soft_delete()
                # b.is_deleted = True
                # b.deleted_at = timezone.now()
                messages.success(request, 'User Report successfully deleted')
    context = {
        'profile': profile,
        'incidentReports': page_obj,
        'notifications': notifications,
        # 'IncidentGeneral': IncidentGeneral
    }
    return render(request, 'pages/user_report.html', context)

通知模型(Notification)

class Notification(models.Model):
    TYPES = ((1, 'Reports'), (2, 'User Accounts'), (3, 'Inbox'), (4, 'Attributes Builder'))
    incident_report = models.ForeignKey('incidentreport.IncidentGeneral', on_delete=models.CASCADE, blank=True, null=True)
    sender = models.ForeignKey('accounts.User', on_delete=models.CASCADE, blank=True, null=True, related_name="noti_from_user")
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="noti_user")
    notification_type = models.IntegerField(choices=TYPES)
    remarks = models.CharField(max_length=90, blank=True)
    text_preview = models.CharField(max_length=90, blank=True)
    date = models.DateTimeField(auto_now_add=True)
    is_seen = models.BooleanField(default=False)

事件模型(IncidentGeneral)

class IncidentGeneral(SoftDeleteModel):
    PENDING = 1
    APPROVED = 2
    REJECTED = 3
    STATUS = (
        (PENDING, 'Pending'),
        (APPROVED, 'Approved'),
        (REJECTED, 'Rejected')
    )

    IF_DUPLICATE = (
        ('Duplicate', 'Duplicate'),
        ('Possible Duplicate', 'Possible Duplicate'),
        ('Not Duplicate', 'Not Duplicate')
    )

    WEATHER = (
        ('Clear Night', 'Clear Night'),
        ('Cloudy', 'Cloudy'),
        ('Day', 'Day'),
        ('Fog', 'Fog'),
        ('Hail', 'Hail'),
        ('Partially cloudy day', 'Partially cloudy day'),
        ('Partially cloudy night', 'Partially cloudy night'),
        ('Rain', 'Rain'),
        ('Rain', 'Rain'),
        ('Wind', 'Wind'),
    )

    LIGHT = (
        ('Dawn', 'Dawn'),
        ('Day', 'Day'),
        ('Dusk', 'Dusk'),
        ('Night', 'Night'),
    )

    SEVERITY = (
        ('Damage to Property', 'Damage to Property'),
        ('Fatal', 'Fatal'),
        ('Non-Fatal', 'Non-Fatal'),
    )

    user = models.ForeignKey(User, on_delete=models.CASCADE, editable=False, null=True, blank=True)
    generalid = models.CharField(max_length=250, unique=True, null=True, blank=True)
    description = models.TextField(max_length=250, blank=True)
    address = models.CharField(max_length=250)
    country = models.CharField(max_length=50, blank=True, null=True)
    state = models.CharField(max_length=250, blank=True, null=True)
    city = models.CharField(max_length=50, blank=True, null=True)
    pin_code = models.CharField(max_length=6, blank=True, null=True)
    latitude = models.FloatField(max_length=20, blank=True, null=True)
    longitude = models.FloatField(max_length=20, blank=True, null=True)
    geo_location = gismodels.PointField(blank=True, null=True, srid=4326) # New field
    upload_photovideo = models.FileField(upload_to=incident_image_upload_path, blank=True, null=True)
    date = models.DateField(auto_now_add=False, auto_now=False, blank=True, null=True)
    time = models.TimeField(auto_now_add=False, auto_now=False, blank=True, null=True)
    status = models.PositiveSmallIntegerField(choices=STATUS, blank=True, null=True)
    duplicate =  models.CharField(choices=IF_DUPLICATE,max_length=250, blank=True, null=True)
    duplicate_general =  models.ForeignKey('incidentreport.IncidentGeneral', on_delete=models.CASCADE, null=True, blank=True)
    accident_factor = models.ForeignKey(AccidentCausation, on_delete=models.SET_NULL, blank=True, null=True)
    collision_type = models.ForeignKey(CollisionType, on_delete=models.SET_NULL, blank=True, null=True)
    crash_type = models.ForeignKey(CrashType, on_delete=models.SET_NULL, blank=True, null=True)
    weather =  models.CharField(choices=WEATHER, max_length=250,blank=True, null=True)
    light =  models.CharField(choices=LIGHT,max_length=250, blank=True, null=True)
    severity = models.CharField(choices=SEVERITY, max_length=250, blank=True, null=True)
    movement_code = models.CharField(max_length=250, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def get_status(self):
        if self.status == 1:
            incident_status = 'Pending'
        elif self.status == 2:
            incident_status = 'Approved'
        elif self.status == 3:
            incident_status = 'Rejected'
        return incident_status


    def save(self, *args, **kwargs):
        # super(IncidentGeneral, self).save(*args, **kwargs)
        if self.latitude and self.longitude:
            self.geo_location = Point(float(self.longitude), float(self.latitude))
            super(IncidentGeneral, self).save(*args, **kwargs)

HTML模板片段

{% for incident in incidentReports %}
    {% if incident.user.username %}
        <tr>
            {% if incident.status == 2 %}
            <td>
                {% comment %} <input type="checkbox" name="{{incident.id}}" id="check" disabled> {% endcomment %}
            </td>
            {% else %}
            <td><input type="checkbox" name="{{incident.id}}" id="check"></td>
            {% endif %}
            <td>{{incident.user.username}}</td>
            <td>{% if incident.user.role %}{{incident.user.get_role}}{% endif %}</td>
            <td>{{incident.description}}</td>
            <td>{{incident.address}}</td>
            {% comment %} <td>{{incident__incident_general.accident_factor}}</td>
            <td>{{incident.collision_type}}</td> {% endcomment %}
            <td>{{incident.date}}</td>
            <td>{{incident.time}}</td>
            <td>
                {% if incident.status == 1 %}
                <span class="badge badge-warning">{{incident.get_status}}</span>
                {% elif incident.status == 2 %}
                <span class="badge badge-success">{{incident.get_status}}</span>
                {% elif incident.status == 3 %}
                <span class="badge badge-danger">{{incident.get_status}}</span>
                {% endif %}
            </td>
            <td>
                <a href="{% url 'incident_report_general_view' incident.id %}"><i class='bx bxs-folder-open' title="View"></i></a>

                {% if incident.status == 2 %} 
                {% else %}     
                <a href="#" data-toggle="modal" data-target="#deleteModal{{ incident.id }}"><i class='bx bx-trash' data-toggle="tooltip" title="Delete" ></i></a>
                {% endif %}
            </td>
            <td>
                <a href="#"  data-toggle="collapse" data-target="#collapse{{incident.id}}" onclick=""><i class="fa-sharp fa-solid fa-circle-arrow-right"></i></a>
            </td>
        </tr>

        <tr>
            <td id="collapse{{incident.id}}" class="collapse" colspan="10" >
                <div class="columns is-mobile is-centered">
                    <div class="column is-half">
                        {% for notification in notifications %}
                        {% if notification.incident_report.id ==  15 %}
                        {% if notification.remarks == "new incident" %}
                        <div class="notification">
                            <article class="media">
                                <figure class="media-left">
                                    <p class="image is-64x64">
                                        {% if notification.user.userprofile.profile_picture %}
                                            <img src="{{ notification.user.userprofile.profile_picture.url }}" alt="Placeholder image">
                                        {% else %}
                                            <img src="https://bulma.io/images/placeholders/96x96.png" alt="Placeholder image">
                                        {% endif %}
                                    </p>
                                </figure>
                                <div class="media-content">
                                    <div class="content">
                                        <strong>{{ notification.user.first_name }} {{ notification.user.last_name }}</strong> <small>&lt;{{ notification.user.username }}&gt;</small> 
                                        <br>
                                        <p>{{ notification.sender.first_name }} {{ notification.sender.last_name }} &lt;{{ notification.sender.username }}&gt; {{ notification.text_preview }}</p>
                                        <strong><small>{{ notification.date|naturaltime }}</small></strong>
                                    </div>
                                </div>
                                <div class="media-right">
                                    <figure class="image is-128x128">
                                        <img src="{{ notification.post.picture.url }}">
                                    </figure>
                                </div>
                            </article>
                        </div>
                        {% endif %}
                        {% endif %}

                        {% if notification.remarks == "update incident status" %}
                        <div class="notification">
                            <article class="media">
                                <figure class="media-left">
                                    <p class="image is-64x64">
                                        {% if notification.user.userprofile.profile_picture %}
                                            <img src="{{ notification.user.userprofile.profile_picture.url }}" alt="Placeholder image">
                                        {% else %}
                                            <img src="https://bulma.io/images/placeholders/96x96.png" alt="Placeholder image">
                                        {% endif %}
                                    </p>
                                </figure>
                                <div class="media-content">
                                    <div class="content">
                                        <strong>{{ notification.user.first_name }} {{ notification.user.last_name }}</strong> <small>&lt;{{ notification.user.username }}&gt;</small> 
                                        <br>
                                        <p>{{ notification.user.first_name }} {{ notification.user.last_name }} &lt;{{ notification.user.username }}&gt; <strong> {{ notification.text_preview }} </strong>
                                            with {{ notification.incident_report.address }} from {{ notification.sender.first_name }} {{ notification.sender.last_name }} &lt;{{ notification.sender.username }}&gt; on reported date {{ notification.incident_report.created_at }}
                                        </p>
                                        <strong><small>{{ notification.date|naturaltime }}</small></strong>
                                    </div>
                                </div>
                                <div class="media-right">
                                    <figure class="image is-128x128">
                                        <img src="{{ notification.post.picture.url }}">
                                    </figure>
                                </div>
                            </article>
                        </div>
                        {% endif %}
                        {% endfor %}
                    </div>
                </div>
            </td>
        </tr>
{% endif %}
{% endfor %}

解决方案

1. 修复模板中的硬编码过滤逻辑

当前模板中硬编码了{% if notification.incident_report.id == 15 %},这只会显示ID为15的事件的通知。要匹配当前循环的事件,需要修改为:

{% if notification.incident_report and notification.incident_report.id == incident.id %}

添加notification.incident_report判断是为了避免incident_report为null时触发报错。

2. 优化数据库查询(推荐)

直接遍历所有通知再过滤会产生多余的数据库查询,效率低下。可以通过以下方式优化:

步骤1:修改Notification模型的外键关联

给incident_report字段添加related_name,方便反向查询:

incident_report = models.ForeignKey(
    'incidentreport.IncidentGeneral', 
    on_delete=models.CASCADE, 
    blank=True, 
    null=True,
    related_name='notifications'  # 添加这一行
)

步骤2:在View中预取关联的通知

修改incidentReports的查询语句,使用prefetch_related预取每个事件对应的通知,减少数据库查询次数:

incidentReports = IncidentGeneral.objects.all().order_by('-updated_at').prefetch_related('notifications')

同时可以删除View中notifications = Notification.objects.all().order_by('-date')这一行,以及context中的'notifications': notifications,因为不需要再传递所有通知到模板。

步骤3:修改模板中的循环逻辑

直接遍历当前事件的关联通知,无需全局过滤:

{% for notification in incident.notifications.all|dictsortreversed:"date" %}
    {% if notification.remarks == "new incident" %}
    <!-- 原有通知内容 -->
    {% endif %}

    {% if notification.remarks == "update incident status" %}
    <!-- 原有通知内容 -->
    {% endif %}
{% endfor %}

使用dictsortreversed:"date"可以保持通知按时间倒序排列,和之前的逻辑一致。

3. 额外注意点

  • 确保Notification中的incident_report字段正确关联到IncidentGeneral模型,避免拼写错误。
  • 如果需要限制通知的可见范围(比如只显示当前用户相关的),可以在prefetch_related中添加过滤条件:
from django.db.models import Prefetch

incidentReports = IncidentGeneral.objects.all().order_by('-updated_at').prefetch_related(
    Prefetch('notifications', queryset=Notification.objects.filter(user=request.user))
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 04:35:20