大量修改UI

This commit is contained in:
Azmat@qq.com
2026-08-19 17:44:56 +08:00
parent 18b3927817
commit a343f47f0c
76 changed files with 11655 additions and 6104 deletions
@@ -21,6 +21,15 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
fields = ["notify", "two_factor_enabled", "creation_defaults", "display", "updated_at"]
read_only_fields = ["updated_at"]
def update(self, instance, validated_data):
# JSON 字段按 key 合并,避免消息中心写入的 mute-* 被设置页整包覆盖丢掉
for field in ("notify", "creation_defaults", "display"):
incoming = validated_data.get(field)
current = getattr(instance, field)
if isinstance(incoming, dict) and isinstance(current, dict):
validated_data[field] = {**current, **incoming}
return super().update(instance, validated_data)
class LoginSessionSerializer(serializers.ModelSerializer):
is_current = serializers.SerializerMethodField()
+38 -1
View File
@@ -1,13 +1,15 @@
from decimal import Decimal
from unittest.mock import patch
from django.test import TestCase
from rest_framework.test import APIClient
from apps.accounts.models import Team, TeamMember, User
from apps.ai.models import AITask, ModelConfig, ModelProvider
from apps.assets.models import Asset
from apps.billing.models import CreditAccount, CreditLedger
from apps.ops.models import Notification
from apps.ops.views import ensure_team_notifications
from apps.ops.views import NotificationViewSet, ensure_team_notifications
from apps.products.models import Product
from apps.projects.models import Project
@@ -84,3 +86,38 @@ class BillingNotificationTests(TestCase):
ensure_team_notifications(self.team, self.user)
note.refresh_from_db()
self.assertEqual(note.cost_label, "¥0.32")
class NotificationMuteFilterTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(username="owner", password="pass")
self.team = Team.objects.create(name="T", owner=self.user)
TeamMember.objects.create(team=self.team, user=self.user, role=TeamMember.Role.OWNER)
self.client = APIClient()
self.client.force_authenticate(self.user)
for kind, title in (
(Notification.Type.BILLING, "计费 1"),
(Notification.Type.BILLING, "计费 2"),
(Notification.Type.TASK, "任务 1"),
(Notification.Type.SYSTEM, "系统 1"),
):
Notification.objects.create(
team=self.team,
recipient=self.user,
notification_type=kind,
title=title,
dedupe_key=f"mute-test:{title}",
)
@patch.object(NotificationViewSet, "_refresh_notifications")
def test_exclude_type_hides_muted_category(self, _refresh):
res = self.client.get("/api/ops/notifications/", {"exclude_type": "billing", "page_size": 50})
self.assertEqual(res.status_code, 200)
titles = [row["title"] for row in res.data["results"]]
self.assertNotIn("计费 1", titles)
self.assertNotIn("计费 2", titles)
self.assertIn("任务 1", titles)
self.assertIn("系统 1", titles)
# chip 计数仍是全量,不被静音筛掉
self.assertEqual(res.data["type_counts"]["billing"], 2)
self.assertEqual(res.data["type_counts"]["task"], 1)
+12
View File
@@ -257,6 +257,15 @@ class NotificationViewSet(TeamScopedViewSetMixin, ModelViewSet):
user = self.request.user
return queryset.filter(Q(recipient=user) | Q(recipient__isnull=True))
def _excluded_types(self):
"""静音同类: ?exclude_type=billing&exclude_type=system 或逗号分隔。非法值丢弃。"""
allowed = {choice[0] for choice in Notification.Type.choices}
raw = self.request.query_params.getlist("exclude_type")
types: list[str] = []
for item in raw:
types.extend(part.strip() for part in item.split(",") if part.strip())
return [item for item in types if item in allowed]
def get_queryset(self):
queryset = self._recipient_scope()
notification_type = self.request.query_params.get("type")
@@ -264,6 +273,9 @@ class NotificationViewSet(TeamScopedViewSetMixin, ModelViewSet):
queryset = queryset.filter(notification_type=notification_type)
if self.request.query_params.get("unread") in {"1", "true", "yes"}:
queryset = queryset.filter(is_read=False)
excluded = self._excluded_types()
if excluded:
queryset = queryset.exclude(notification_type__in=excluded)
return queryset
def _refresh_notifications(self, request):