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><{{ notification.user.username }}></small> <br> <p>{{ notification.sender.first_name }} {{ notification.sender.last_name }} <{{ notification.sender.username }}> {{ 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><{{ notification.user.username }}></small> <br> <p>{{ notification.user.first_name }} {{ notification.user.last_name }} <{{ notification.user.username }}> <strong> {{ notification.text_preview }} </strong> with {{ notification.incident_report.address }} from {{ notification.sender.first_name }} {{ notification.sender.last_name }} <{{ notification.sender.username }}> 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
相关产品推荐
相关产品推荐

