Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Weblate 5.15
* :ref:`mt-modernmt` better supports language variants.
* :ref:`addon-weblate.discovery.discovery` preserves :ref:`file_format_params`.
* :ref:`mt-cyrtranslit` now supports Belarusian and Greek.
* :ref:`block-user` now accepts internal notes that are not visible to the user.
* :ref:`addon-weblate.webhook.slack` and :ref:`addon-weblate.webhook.webhook` can be installed multiple times.
* Compatibility with Git SHA256 repositories.

Expand Down
25 changes: 25 additions & 0 deletions weblate/auth/migrations/0008_userblock_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright © Michal Čihař <[email protected]>
#
# SPDX-License-Identifier: GPL-3.0-or-later

# Generated by Django 5.2.8 on 2025-11-27 18:16

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("weblate_auth", "0007_alter_user_date_expires"),
]

operations = [
migrations.AddField(
model_name="userblock",
name="note",
field=models.TextField(
blank=True,
help_text="Internal notes regarding blocking the user that are not visible to the user.",
verbose_name="Block note",
),
),
]
7 changes: 7 additions & 0 deletions weblate/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@
return self._create_user(username, email, password, **extra_fields)

def get_or_create_bot(self, *, scope: str, name: str, verbose: str) -> User:
cached = bot_cache.get({})

Check failure on line 272 in weblate/auth/models.py

View workflow job for this annotation

GitHub Actions / mypy

Need type annotation for "cached"
username = f"{scope}:{name}"
try:
return cached[username]

Check failure on line 275 in weblate/auth/models.py

View workflow job for this annotation

GitHub Actions / mypy

Incompatible return value type (got "Any | GenericAlias", expected "User")
except KeyError:
user = self.get_or_create(
username=username,
Expand All @@ -284,7 +284,7 @@
"password": make_password(None),
},
)[0]
cached[username] = user

Check failure on line 287 in weblate/auth/models.py

View workflow job for this annotation

GitHub Actions / mypy

Invalid index type "str" for "dict"; expected type "dict[Any, Any]"

Check failure on line 287 in weblate/auth/models.py

View workflow job for this annotation

GitHub Actions / mypy

Too few arguments for "__setitem__" of "dict"
return user


Expand Down Expand Up @@ -1016,6 +1016,13 @@
Project, verbose_name=gettext_lazy("Project"), on_delete=models.deletion.CASCADE
)
expiry = models.DateTimeField(gettext_lazy("Block expiry"), null=True)
note = models.TextField(
verbose_name=gettext_lazy("Block note"),
blank=True,
help_text=gettext_lazy(
"Internal notes regarding blocking the user that are not visible to the user."
),
)

class Meta:
verbose_name = "Blocked user"
Expand Down Expand Up @@ -1261,7 +1268,7 @@
raise ValueError(msg)

send_notification_email(
None,

Check failure on line 1271 in weblate/auth/models.py

View workflow job for this annotation

GitHub Actions / mypy

Argument 1 to "send_notification_email" has incompatible type "None"; expected "str"
[email],
"invite",
info=f"{self}",
Expand Down
8 changes: 7 additions & 1 deletion weblate/templates/accounts/user.html
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,20 @@ <h3>{{ page_user }}</h3>
{% for userblock in page_user.userblock_set.all %}
<tr>
<th>{{ userblock.project.name }}</th>
<td></td>
<td>
{% if userblock.expiry %}
{% blocktranslate with expiry=userblock.expiry|date:"DATE_FORMAT" %}Blocked until {{ expiry }}{% endblocktranslate %}
{% else %}
{% translate "Blocked permanently" %}
{% endif %}
</td>
{% if userblock.note %}
<td class="number">
<span title="{{ userblock.note }}">{% icon 'info.svg' %}</span>
</td>
{% else %}
<td></td>
{% endif %}
<td>
<a href=""
class="btn btn-link btn-xs link-post"
Expand Down
3 changes: 3 additions & 0 deletions weblate/templates/trans/project-access.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ <h4 class="panel-title">
</strong>
</td>
<td>
{% if userblock.note %}
<span title="{{ userblock.note }}">{% icon 'info.svg' %}</span>
{% endif %}
<form action="{% url "unblock-user" project=object.slug %}"
method="post"
class="inlineform">
Expand Down
8 changes: 8 additions & 0 deletions weblate/trans/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@
lang_label,
)
plural = translation.plural
placeables_set = set()

Check failure on line 352 in weblate/trans/forms.py

View workflow job for this annotation

GitHub Actions / mypy

Need type annotation for "placeables_set" (hint: "placeables_set: set[<type>] = ...")
for text in plurals:
placeables_set.update(hl[2] for hl in highlight_string(text, unit))
placeables = list(placeables_set)
Expand Down Expand Up @@ -728,9 +728,9 @@
form = ExtraUploadForm
kwargs["initial"] = {"author_name": user.full_name, "author_email": user.email}
elif user.has_perm("upload.overwrite", translation):
form = UploadForm

Check failure on line 731 in weblate/trans/forms.py

View workflow job for this annotation

GitHub Actions / mypy

Incompatible types in assignment (expression has type "type[UploadForm]", variable has type "type[ExtraUploadForm]")
else:
form = SimpleUploadForm

Check failure on line 733 in weblate/trans/forms.py

View workflow job for this annotation

GitHub Actions / mypy

Incompatible types in assignment (expression has type "type[SimpleUploadForm]", variable has type "type[ExtraUploadForm]")
result = form(*args, **kwargs)
for method in [x[0] for x in result.fields["method"].choices]:
if not check_upload_method_permissions(user, translation, method):
Expand Down Expand Up @@ -855,8 +855,8 @@
any new search.
"""
data = copy.copy(self.data) # pylint: disable=access-member-before-definition
data["offset"] = "1"

Check failure on line 858 in weblate/trans/forms.py

View workflow job for this annotation

GitHub Actions / mypy

Unsupported target for indexed assignment ("Mapping[str, Any]")
data["checksum"] = ""

Check failure on line 859 in weblate/trans/forms.py

View workflow job for this annotation

GitHub Actions / mypy

Unsupported target for indexed assignment ("Mapping[str, Any]")
self.data = data
return self

Expand Down Expand Up @@ -1360,6 +1360,14 @@
),
required=False,
)
note = forms.CharField(
required=False,
widget=forms.Textarea,
label=gettext_lazy("Block note"),
help_text=gettext_lazy(
"Internal notes regarding blocking the user that are not visible to the user."
),
)

def __init__(self, *args, **kwargs) -> None:
if "auto_id" not in kwargs:
Expand Down
10 changes: 10 additions & 0 deletions weblate/trans/tests/test_acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ def test_block_user(self) -> None:
)
self.assertRedirects(response, self.access_url)
self.assertEqual(self.project.userblock_set.count(), 1)
self.assertEqual(self.project.userblock_set.filter(note="").count(), 1)

# Block user, for second time
response = self.client.post(
Expand All @@ -424,6 +425,15 @@ def test_block_user(self) -> None:
self.assertRedirects(response, self.access_url)
self.assertEqual(self.project.userblock_set.count(), 0)

# Block user with a note
response = self.client.post(
reverse("block-user", kwargs=self.kw_project),
{"user": self.second_user.username, "note": "Spamming"},
)
self.assertRedirects(response, self.access_url)
self.assertEqual(self.project.userblock_set.count(), 1)
self.assertEqual(self.project.userblock_set.filter(note="Spamming").count(), 1)

def test_delete_group(self) -> None:
self.project.add_user(self.user, "Administration")
group = self.project.defined_groups.get(name="Memory")
Expand Down
3 changes: 2 additions & 1 deletion weblate/trans/views/acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ def block_user(request: AuthenticatedHttpRequest, project):
else:
expiry = None
_userblock, created = user.userblock_set.get_or_create(
project=obj, defaults={"expiry": expiry}
project=obj,
defaults={"expiry": expiry, "note": form.cleaned_data.get("note", "")},
)
if created:
AuditLog.objects.create(
Expand Down
Loading