Coverage for app/venv/lib/python3.14/site-packages/weblate/trans/models/comment.py: 68%
59 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7from typing import TYPE_CHECKING
9from django.conf import settings
10from django.db import models
11from django.utils import timezone
12from weblate_language_data.utils import gettext_noop
14from weblate.trans.actions import ActionEvents
15from weblate.trans.mixins import UserDisplayMixin
16from weblate.utils.antispam import report_spam
17from weblate.utils.request import get_ip_address, get_user_agent_raw
18from weblate.utils.state import STATE_FUZZY
20if TYPE_CHECKING: 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true
21 from datetime import datetime
23 from weblate.auth.models import AuthenticatedHttpRequest, Unit, User
26class CommentManager(models.Manager):
27 def add(
28 self,
29 request: AuthenticatedHttpRequest,
30 unit: Unit,
31 text: str,
32 scope: str,
33 *,
34 user: User | None = None,
35 timestamp: datetime | None = None,
36 ) -> Comment:
37 """Add comment to this unit."""
38 # Is this source or target comment?
39 unit_scope = unit.source_unit if scope in {"global", "report"} else unit
41 if user is None: 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true
42 user = request.user
43 kwargs = {
44 "user": user,
45 "unit": unit_scope,
46 "comment": text,
47 "userdetails": {
48 "address": get_ip_address(request),
49 "agent": get_user_agent_raw(request),
50 },
51 }
52 if timestamp is not None:
53 kwargs["timestamp"] = timestamp
55 new_comment = self.create(**kwargs)
56 user.profile.increase_count("commented")
57 unit_scope.change_set.create(
58 comment=new_comment,
59 action=ActionEvents.COMMENT,
60 user=user,
61 author=user,
62 details={"comment": text},
63 )
65 component = unit_scope.translation.component
67 # Add review label/flag
68 if scope == "report": 68 ↛ 69line 68 didn't jump to line 69 because the condition on line 68 was never true
69 if component.has_template():
70 if unit_scope.translated and not unit_scope.readonly:
71 unit_scope.translate(
72 user,
73 unit_scope.target,
74 STATE_FUZZY,
75 change_action=ActionEvents.MARKED_EDIT,
76 )
77 else:
78 label = component.project.label_set.get_or_create(
79 name=gettext_noop("Source needs review"), defaults={"color": "red"}
80 )[0]
81 unit_scope.labels.add(label)
83 return new_comment
86class CommentQuerySet(models.QuerySet):
87 def order(self):
88 return self.order_by("timestamp")
91class Comment(models.Model, UserDisplayMixin):
92 unit = models.ForeignKey("trans.Unit", on_delete=models.deletion.CASCADE)
93 comment = models.TextField()
94 user = models.ForeignKey(
95 settings.AUTH_USER_MODEL,
96 null=True,
97 blank=True,
98 on_delete=models.deletion.CASCADE,
99 )
100 timestamp = models.DateTimeField(default=timezone.now, db_index=True)
101 resolved = models.BooleanField(default=False, db_index=True)
102 userdetails = models.JSONField(default=dict)
104 objects = CommentManager.from_queryset(CommentQuerySet)()
106 class Meta:
107 app_label = "trans"
108 verbose_name = "string comment"
109 verbose_name_plural = "string comments"
111 def __str__(self) -> str:
112 return "comment for {} by {}".format(
113 self.unit, self.user.username if self.user else "unknown"
114 )
116 def report_spam(self) -> None:
117 report_spam(
118 self.userdetails["address"], self.userdetails["agent"], self.comment
119 )
121 def resolve(self, user: User) -> None:
122 self.unit.change_set.create(
123 comment=self,
124 action=ActionEvents.COMMENT_RESOLVE,
125 user=user,
126 author=self.user,
127 details={"comment": self.comment},
128 )
129 self.resolved = True
130 self.save(update_fields=["resolved"])
132 def delete(self, user=None, using=None, keep_parents=False) -> None:
133 self.unit.change_set.create(
134 action=ActionEvents.COMMENT_DELETE,
135 user=user,
136 author=self.user,
137 details={"comment": self.comment},
138 )
139 super().delete(using=using, keep_parents=keep_parents)