We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Orphan screenshot warning doesn't disappear after clear all of unused screenshots
### Describe the issue

Hello!

Recently I found that WebLate doesn't get rid of the "Here is at least one unused screenshot presented" warning after I removed all screenshots that has no assigned string. They appeared because of the recent project-side rework that caused removal of a pile of strings that got been replaced with another one. And once I got that warning, I started to remove these screenshots (as they are now obsolete on my side). However, the warning still not gone after one day since I removed all unused screnshots. And, additionally, there is totally no way to remove it manually...

### I already tried

- [X] I've read and searched [the documentation](https://docs.weblate.org/).
- [X] I've searched for similar filed issues in this repository.

### Steps to reproduce the behavior

1. Have a pile of strings.
2. Upload a screenshot for each of them.
3. On the project side, remove some number of strings, and add another one.
4. Wait when "Unused screenshots" warning will pops up.
5. Open screenshots list and remove every screenshot marked by red exclamation sign.
6. Wait again, and...
7. It still appears. I checked trice the whole list of screenshots on all 7 pages (on my end), I swear I removed every unused screenshot, and warning still wasn't gone.

### Expected behavior

The warning gets disappear after all unused screenshots got been removed or assigned to anything also.

### Screenshots

![Снимок экрана от 2024-03-05 21-04-08](https://github.com/WeblateOrg/weblate/assets/6751442/bbd36054-e0d2-4bfe-86f9-c9a2b9b58c06)


### Exception traceback

_No response_

### How do you run Weblate?

weblate.org service

### Weblate versions

_No response_

### Weblate deploy checks

_No response_

### Additional context

The project and its component where the problem ocurred: https://hosted.weblate.org/projects/thextech/engine-general/

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `weblate/screenshots/models.py`
Content:
```
1 # Copyright © Michal Čihař <michal@weblate.org>
2 #
3 # SPDX-License-Identifier: GPL-3.0-or-later
4 
5 from __future__ import annotations
6 
7 import fnmatch
8 import os
9 from typing import Any, BinaryIO
10 
11 from django.conf import settings
12 from django.core.exceptions import ValidationError
13 from django.core.files import File
14 from django.core.files.storage import default_storage
15 from django.db import models
16 from django.db.models import Q
17 from django.db.models.signals import m2m_changed
18 from django.dispatch import receiver
19 from django.urls import reverse
20 from django.utils.translation import gettext_lazy
21 
22 from weblate.auth.models import get_anonymous
23 from weblate.checks.flags import Flags
24 from weblate.screenshots.fields import ScreenshotField
25 from weblate.trans.mixins import UserDisplayMixin
26 from weblate.trans.models import Translation, Unit
27 from weblate.trans.signals import vcs_post_update
28 from weblate.trans.tasks import component_alerts
29 from weblate.utils.decorators import disable_for_loaddata
30 from weblate.utils.errors import report_error
31 from weblate.utils.validators import validate_bitmap
32 
33 
34 class ScreenshotQuerySet(models.QuerySet):
35     def order(self):
36         return self.order_by("name")
37 
38     def filter_access(self, user):
39         result = self
40         if user.needs_project_filter:
41             result = result.filter(
42                 translation__component__project__in=user.allowed_projects
43             )
44         if user.needs_component_restrictions_filter:
45             result = result.filter(
46                 Q(translation__component__restricted=False)
47                 | Q(translation__component_id__in=user.component_permissions)
48             )
49         return result
50 
51 
52 class Screenshot(models.Model, UserDisplayMixin):
53     name = models.CharField(
54         verbose_name=gettext_lazy("Screenshot name"), max_length=200
55     )
56     repository_filename = models.CharField(
57         verbose_name=gettext_lazy("Repository path to screenshot"),
58         help_text=gettext_lazy("Scan for screenshot file change on repository update."),
59         blank=True,
60         max_length=200,
61     )
62     image = ScreenshotField(
63         verbose_name=gettext_lazy("Image"),
64         help_text=gettext_lazy("Upload image up to 2000x2000 pixels."),
65         upload_to="screenshots/",
66     )
67     translation = models.ForeignKey(Translation, on_delete=models.deletion.CASCADE)
68     units = models.ManyToManyField(Unit, blank=True, related_name="screenshots")
69     timestamp = models.DateTimeField(auto_now_add=True)
70     user = models.ForeignKey(
71         settings.AUTH_USER_MODEL,
72         null=True,
73         blank=True,
74         on_delete=models.deletion.SET_NULL,
75     )
76 
77     objects = ScreenshotQuerySet.as_manager()
78 
79     class Meta:
80         verbose_name = "Screenshot"
81         verbose_name_plural = "Screenshots"
82 
83     def __str__(self):
84         return self.name
85 
86     def get_absolute_url(self):
87         return reverse("screenshot", kwargs={"pk": self.pk})
88 
89     def __init__(self, *args, **kwargs):
90         """Constructor to initialize some cache properties."""
91         super().__init__(*args, **kwargs)
92         # Project backup integration
93         self.import_data: dict[str, Any] = {}
94         self.import_handle: BinaryIO | None = None
95 
96     @property
97     def filter_name(self):
98         return f"screenshot:{Flags.format_value(self.name)}"
99 
100 
101 @receiver(m2m_changed, sender=Screenshot.units.through)
102 @disable_for_loaddata
103 def change_screenshot_assignment(sender, instance, action, **kwargs):
104     # Update alerts in case there is change in string assignment
105     if instance.translation.component.alert_set.filter(
106         name="UnusedScreenshot"
107     ).exists():
108         component_alerts.delay([instance.pk])
109 
110 
111 def validate_screenshot_image(component, filename):
112     """Returns True if image is validated."""
113     try:
114         full_name = os.path.join(component.full_path, filename)
115         with open(full_name, "rb") as f:
116             image_file = File(f, name=os.path.basename(filename))
117             validate_bitmap(image_file)
118     except ValidationError as error:
119         component.log_error("failed to validate screenshot %s: %s", filename, error)
120         report_error(cause="Could not validate image from repository")
121         return False
122     return True
123 
124 
125 @receiver(vcs_post_update)
126 def sync_screenshots_from_repo(sender, component, previous_head: str, **kwargs):
127     repository = component.repository
128     changed_files = repository.get_changed_files(compare_to=previous_head)
129 
130     screenshots = Screenshot.objects.filter(
131         translation__component=component, repository_filename__in=changed_files
132     )
133 
134     # Update existing screenshots
135     for screenshot in screenshots:
136         filename = screenshot.repository_filename
137         component.log_debug("detected screenshot change in repository: %s", filename)
138         changed_files.remove(filename)
139 
140         if validate_screenshot_image(component, filename):
141             full_name = os.path.join(component.full_path, filename)
142             with open(full_name, "rb") as f:
143                 screenshot.image = File(
144                     f,
145                     name=default_storage.get_available_name(os.path.basename(filename)),
146                 )
147                 screenshot.save(update_fields=["image"])
148                 component.log_info("updated screenshot from repository: %s", filename)
149 
150     # Add new screenshots matching screenshot filemask
151     for filename in changed_files:
152         if fnmatch.fnmatch(
153             filename, component.screenshot_filemask
154         ) and validate_screenshot_image(component, filename):
155             full_name = os.path.join(component.full_path, filename)
156             with open(full_name, "rb") as f:
157                 screenshot = Screenshot.objects.create(
158                     name=filename,
159                     repository_filename=filename,
160                     image=File(
161                         f,
162                         name=default_storage.get_available_name(
163                             os.path.basename(filename)
164                         ),
165                     ),
166                     translation=component.source_translation,
167                     user=get_anonymous(),
168                 )
169                 screenshot.save()
170                 component.log_info("create screenshot from repository: %s", filename)
171
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/weblate/screenshots/models.py b/weblate/screenshots/models.py
--- a/weblate/screenshots/models.py
+++ b/weblate/screenshots/models.py
@@ -14,7 +14,7 @@
 from django.core.files.storage import default_storage
 from django.db import models
 from django.db.models import Q
-from django.db.models.signals import m2m_changed
+from django.db.models.signals import m2m_changed, post_delete
 from django.dispatch import receiver
 from django.urls import reverse
 from django.utils.translation import gettext_lazy
@@ -24,8 +24,8 @@
 from weblate.screenshots.fields import ScreenshotField
 from weblate.trans.mixins import UserDisplayMixin
 from weblate.trans.models import Translation, Unit
+from weblate.trans.models.alert import update_alerts
 from weblate.trans.signals import vcs_post_update
-from weblate.trans.tasks import component_alerts
 from weblate.utils.decorators import disable_for_loaddata
 from weblate.utils.errors import report_error
 from weblate.utils.validators import validate_bitmap
@@ -105,7 +105,16 @@
     if instance.translation.component.alert_set.filter(
         name="UnusedScreenshot"
     ).exists():
-        component_alerts.delay([instance.pk])
+        update_alerts(instance.translation.component, alerts={"UnusedScreenshot"})
+
+
+@receiver(post_delete, sender=Screenshot)
+def update_alerts_on_screenshot_delete(sender, instance, **kwargs):
+    # Update the unused screenshot alert if screenshot is deleted
+    if instance.translation.component.alert_set.filter(
+        name="UnusedScreenshot"
+    ).exists():
+        update_alerts(instance.translation.component, alerts={"UnusedScreenshot"})
 
 
 def validate_screenshot_image(component, filename):

{"golden_diff": "diff --git a/weblate/screenshots/models.py b/weblate/screenshots/models.py\n--- a/weblate/screenshots/models.py\n+++ b/weblate/screenshots/models.py\n@@ -14,7 +14,7 @@\n from django.core.files.storage import default_storage\n from django.db import models\n from django.db.models import Q\n-from django.db.models.signals import m2m_changed\n+from django.db.models.signals import m2m_changed, post_delete\n from django.dispatch import receiver\n from django.urls import reverse\n from django.utils.translation import gettext_lazy\n@@ -24,8 +24,8 @@\n from weblate.screenshots.fields import ScreenshotField\n from weblate.trans.mixins import UserDisplayMixin\n from weblate.trans.models import Translation, Unit\n+from weblate.trans.models.alert import update_alerts\n from weblate.trans.signals import vcs_post_update\n-from weblate.trans.tasks import component_alerts\n from weblate.utils.decorators import disable_for_loaddata\n from weblate.utils.errors import report_error\n from weblate.utils.validators import validate_bitmap\n@@ -105,7 +105,16 @@\n     if instance.translation.component.alert_set.filter(\n         name=\"UnusedScreenshot\"\n     ).exists():\n-        component_alerts.delay([instance.pk])\n+        update_alerts(instance.translation.component, alerts={\"UnusedScreenshot\"})\n+\n+\n+@receiver(post_delete, sender=Screenshot)\n+def update_alerts_on_screenshot_delete(sender, instance, **kwargs):\n+    # Update the unused screenshot alert if screenshot is deleted\n+    if instance.translation.component.alert_set.filter(\n+        name=\"UnusedScreenshot\"\n+    ).exists():\n+        update_alerts(instance.translation.component, alerts={\"UnusedScreenshot\"})\n \n \n def validate_screenshot_image(component, filename):\n", "issue": "Orphan screenshot warning doesn't disappear after clear all of unused screenshots\n### Describe the issue\n\nHello!\r\n\r\nRecently I found that WebLate doesn't get rid of the \"Here is at least one unused screenshot presented\" warning after I removed all screenshots that has no assigned string. They appeared because of the recent project-side rework that caused removal of a pile of strings that got been replaced with another one. And once I got that warning, I started to remove these screenshots (as they are now obsolete on my side). However, the warning still not gone after one day since I removed all unused screnshots. And, additionally, there is totally no way to remove it manually...\n\n### I already tried\n\n- [X] I've read and searched [the documentation](https://docs.weblate.org/).\n- [X] I've searched for similar filed issues in this repository.\n\n### Steps to reproduce the behavior\n\n1. Have a pile of strings.\r\n2. Upload a screenshot for each of them.\r\n3. On the project side, remove some number of strings, and add another one.\r\n4. Wait when \"Unused screenshots\" warning will pops up.\r\n5. Open screenshots list and remove every screenshot marked by red exclamation sign.\r\n6. Wait again, and...\r\n7. It still appears. I checked trice the whole list of screenshots on all 7 pages (on my end), I swear I removed every unused screenshot, and warning still wasn't gone.\n\n### Expected behavior\n\nThe warning gets disappear after all unused screenshots got been removed or assigned to anything also.\n\n### Screenshots\n\n![\u0421\u043d\u0438\u043c\u043e\u043a \u044d\u043a\u0440\u0430\u043d\u0430 \u043e\u0442 2024-03-05 21-04-08](https://github.com/WeblateOrg/weblate/assets/6751442/bbd36054-e0d2-4bfe-86f9-c9a2b9b58c06)\r\n\n\n### Exception traceback\n\n_No response_\n\n### How do you run Weblate?\n\nweblate.org service\n\n### Weblate versions\n\n_No response_\n\n### Weblate deploy checks\n\n_No response_\n\n### Additional context\n\nThe project and its component where the problem ocurred: https://hosted.weblate.org/projects/thextech/engine-general/\n", "before_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import annotations\n\nimport fnmatch\nimport os\nfrom typing import Any, BinaryIO\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.core.files import File\nfrom django.core.files.storage import default_storage\nfrom django.db import models\nfrom django.db.models import Q\nfrom django.db.models.signals import m2m_changed\nfrom django.dispatch import receiver\nfrom django.urls import reverse\nfrom django.utils.translation import gettext_lazy\n\nfrom weblate.auth.models import get_anonymous\nfrom weblate.checks.flags import Flags\nfrom weblate.screenshots.fields import ScreenshotField\nfrom weblate.trans.mixins import UserDisplayMixin\nfrom weblate.trans.models import Translation, Unit\nfrom weblate.trans.signals import vcs_post_update\nfrom weblate.trans.tasks import component_alerts\nfrom weblate.utils.decorators import disable_for_loaddata\nfrom weblate.utils.errors import report_error\nfrom weblate.utils.validators import validate_bitmap\n\n\nclass ScreenshotQuerySet(models.QuerySet):\n    def order(self):\n        return self.order_by(\"name\")\n\n    def filter_access(self, user):\n        result = self\n        if user.needs_project_filter:\n            result = result.filter(\n                translation__component__project__in=user.allowed_projects\n            )\n        if user.needs_component_restrictions_filter:\n            result = result.filter(\n                Q(translation__component__restricted=False)\n                | Q(translation__component_id__in=user.component_permissions)\n            )\n        return result\n\n\nclass Screenshot(models.Model, UserDisplayMixin):\n    name = models.CharField(\n        verbose_name=gettext_lazy(\"Screenshot name\"), max_length=200\n    )\n    repository_filename = models.CharField(\n        verbose_name=gettext_lazy(\"Repository path to screenshot\"),\n        help_text=gettext_lazy(\"Scan for screenshot file change on repository update.\"),\n        blank=True,\n        max_length=200,\n    )\n    image = ScreenshotField(\n        verbose_name=gettext_lazy(\"Image\"),\n        help_text=gettext_lazy(\"Upload image up to 2000x2000 pixels.\"),\n        upload_to=\"screenshots/\",\n    )\n    translation = models.ForeignKey(Translation, on_delete=models.deletion.CASCADE)\n    units = models.ManyToManyField(Unit, blank=True, related_name=\"screenshots\")\n    timestamp = models.DateTimeField(auto_now_add=True)\n    user = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        null=True,\n        blank=True,\n        on_delete=models.deletion.SET_NULL,\n    )\n\n    objects = ScreenshotQuerySet.as_manager()\n\n    class Meta:\n        verbose_name = \"Screenshot\"\n        verbose_name_plural = \"Screenshots\"\n\n    def __str__(self):\n        return self.name\n\n    def get_absolute_url(self):\n        return reverse(\"screenshot\", kwargs={\"pk\": self.pk})\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Constructor to initialize some cache properties.\"\"\"\n        super().__init__(*args, **kwargs)\n        # Project backup integration\n        self.import_data: dict[str, Any] = {}\n        self.import_handle: BinaryIO | None = None\n\n    @property\n    def filter_name(self):\n        return f\"screenshot:{Flags.format_value(self.name)}\"\n\n\n@receiver(m2m_changed, sender=Screenshot.units.through)\n@disable_for_loaddata\ndef change_screenshot_assignment(sender, instance, action, **kwargs):\n    # Update alerts in case there is change in string assignment\n    if instance.translation.component.alert_set.filter(\n        name=\"UnusedScreenshot\"\n    ).exists():\n        component_alerts.delay([instance.pk])\n\n\ndef validate_screenshot_image(component, filename):\n    \"\"\"Returns True if image is validated.\"\"\"\n    try:\n        full_name = os.path.join(component.full_path, filename)\n        with open(full_name, \"rb\") as f:\n            image_file = File(f, name=os.path.basename(filename))\n            validate_bitmap(image_file)\n    except ValidationError as error:\n        component.log_error(\"failed to validate screenshot %s: %s\", filename, error)\n        report_error(cause=\"Could not validate image from repository\")\n        return False\n    return True\n\n\n@receiver(vcs_post_update)\ndef sync_screenshots_from_repo(sender, component, previous_head: str, **kwargs):\n    repository = component.repository\n    changed_files = repository.get_changed_files(compare_to=previous_head)\n\n    screenshots = Screenshot.objects.filter(\n        translation__component=component, repository_filename__in=changed_files\n    )\n\n    # Update existing screenshots\n    for screenshot in screenshots:\n        filename = screenshot.repository_filename\n        component.log_debug(\"detected screenshot change in repository: %s\", filename)\n        changed_files.remove(filename)\n\n        if validate_screenshot_image(component, filename):\n            full_name = os.path.join(component.full_path, filename)\n            with open(full_name, \"rb\") as f:\n                screenshot.image = File(\n                    f,\n                    name=default_storage.get_available_name(os.path.basename(filename)),\n                )\n                screenshot.save(update_fields=[\"image\"])\n                component.log_info(\"updated screenshot from repository: %s\", filename)\n\n    # Add new screenshots matching screenshot filemask\n    for filename in changed_files:\n        if fnmatch.fnmatch(\n            filename, component.screenshot_filemask\n        ) and validate_screenshot_image(component, filename):\n            full_name = os.path.join(component.full_path, filename)\n            with open(full_name, \"rb\") as f:\n                screenshot = Screenshot.objects.create(\n                    name=filename,\n                    repository_filename=filename,\n                    image=File(\n                        f,\n                        name=default_storage.get_available_name(\n                            os.path.basename(filename)\n                        ),\n                    ),\n                    translation=component.source_translation,\n                    user=get_anonymous(),\n                )\n                screenshot.save()\n                component.log_info(\"create screenshot from repository: %s\", filename)\n", "path": "weblate/screenshots/models.py"}], "after_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import annotations\n\nimport fnmatch\nimport os\nfrom typing import Any, BinaryIO\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.core.files import File\nfrom django.core.files.storage import default_storage\nfrom django.db import models\nfrom django.db.models import Q\nfrom django.db.models.signals import m2m_changed, post_delete\nfrom django.dispatch import receiver\nfrom django.urls import reverse\nfrom django.utils.translation import gettext_lazy\n\nfrom weblate.auth.models import get_anonymous\nfrom weblate.checks.flags import Flags\nfrom weblate.screenshots.fields import ScreenshotField\nfrom weblate.trans.mixins import UserDisplayMixin\nfrom weblate.trans.models import Translation, Unit\nfrom weblate.trans.models.alert import update_alerts\nfrom weblate.trans.signals import vcs_post_update\nfrom weblate.utils.decorators import disable_for_loaddata\nfrom weblate.utils.errors import report_error\nfrom weblate.utils.validators import validate_bitmap\n\n\nclass ScreenshotQuerySet(models.QuerySet):\n    def order(self):\n        return self.order_by(\"name\")\n\n    def filter_access(self, user):\n        result = self\n        if user.needs_project_filter:\n            result = result.filter(\n                translation__component__project__in=user.allowed_projects\n            )\n        if user.needs_component_restrictions_filter:\n            result = result.filter(\n                Q(translation__component__restricted=False)\n                | Q(translation__component_id__in=user.component_permissions)\n            )\n        return result\n\n\nclass Screenshot(models.Model, UserDisplayMixin):\n    name = models.CharField(\n        verbose_name=gettext_lazy(\"Screenshot name\"), max_length=200\n    )\n    repository_filename = models.CharField(\n        verbose_name=gettext_lazy(\"Repository path to screenshot\"),\n        help_text=gettext_lazy(\"Scan for screenshot file change on repository update.\"),\n        blank=True,\n        max_length=200,\n    )\n    image = ScreenshotField(\n        verbose_name=gettext_lazy(\"Image\"),\n        help_text=gettext_lazy(\"Upload image up to 2000x2000 pixels.\"),\n        upload_to=\"screenshots/\",\n    )\n    translation = models.ForeignKey(Translation, on_delete=models.deletion.CASCADE)\n    units = models.ManyToManyField(Unit, blank=True, related_name=\"screenshots\")\n    timestamp = models.DateTimeField(auto_now_add=True)\n    user = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        null=True,\n        blank=True,\n        on_delete=models.deletion.SET_NULL,\n    )\n\n    objects = ScreenshotQuerySet.as_manager()\n\n    class Meta:\n        verbose_name = \"Screenshot\"\n        verbose_name_plural = \"Screenshots\"\n\n    def __str__(self):\n        return self.name\n\n    def get_absolute_url(self):\n        return reverse(\"screenshot\", kwargs={\"pk\": self.pk})\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Constructor to initialize some cache properties.\"\"\"\n        super().__init__(*args, **kwargs)\n        # Project backup integration\n        self.import_data: dict[str, Any] = {}\n        self.import_handle: BinaryIO | None = None\n\n    @property\n    def filter_name(self):\n        return f\"screenshot:{Flags.format_value(self.name)}\"\n\n\n@receiver(m2m_changed, sender=Screenshot.units.through)\n@disable_for_loaddata\ndef change_screenshot_assignment(sender, instance, action, **kwargs):\n    # Update alerts in case there is change in string assignment\n    if instance.translation.component.alert_set.filter(\n        name=\"UnusedScreenshot\"\n    ).exists():\n        update_alerts(instance.translation.component, alerts={\"UnusedScreenshot\"})\n\n\n@receiver(post_delete, sender=Screenshot)\ndef update_alerts_on_screenshot_delete(sender, instance, **kwargs):\n    # Update the unused screenshot alert if screenshot is deleted\n    if instance.translation.component.alert_set.filter(\n        name=\"UnusedScreenshot\"\n    ).exists():\n        update_alerts(instance.translation.component, alerts={\"UnusedScreenshot\"})\n\n\ndef validate_screenshot_image(component, filename):\n    \"\"\"Returns True if image is validated.\"\"\"\n    try:\n        full_name = os.path.join(component.full_path, filename)\n        with open(full_name, \"rb\") as f:\n            image_file = File(f, name=os.path.basename(filename))\n            validate_bitmap(image_file)\n    except ValidationError as error:\n        component.log_error(\"failed to validate screenshot %s: %s\", filename, error)\n        report_error(cause=\"Could not validate image from repository\")\n        return False\n    return True\n\n\n@receiver(vcs_post_update)\ndef sync_screenshots_from_repo(sender, component, previous_head: str, **kwargs):\n    repository = component.repository\n    changed_files = repository.get_changed_files(compare_to=previous_head)\n\n    screenshots = Screenshot.objects.filter(\n        translation__component=component, repository_filename__in=changed_files\n    )\n\n    # Update existing screenshots\n    for screenshot in screenshots:\n        filename = screenshot.repository_filename\n        component.log_debug(\"detected screenshot change in repository: %s\", filename)\n        changed_files.remove(filename)\n\n        if validate_screenshot_image(component, filename):\n            full_name = os.path.join(component.full_path, filename)\n            with open(full_name, \"rb\") as f:\n                screenshot.image = File(\n                    f,\n                    name=default_storage.get_available_name(os.path.basename(filename)),\n                )\n                screenshot.save(update_fields=[\"image\"])\n                component.log_info(\"updated screenshot from repository: %s\", filename)\n\n    # Add new screenshots matching screenshot filemask\n    for filename in changed_files:\n        if fnmatch.fnmatch(\n            filename, component.screenshot_filemask\n        ) and validate_screenshot_image(component, filename):\n            full_name = os.path.join(component.full_path, filename)\n            with open(full_name, \"rb\") as f:\n                screenshot = Screenshot.objects.create(\n                    name=filename,\n                    repository_filename=filename,\n                    image=File(\n                        f,\n                        name=default_storage.get_available_name(\n                            os.path.basename(filename)\n                        ),\n                    ),\n                    translation=component.source_translation,\n                    user=get_anonymous(),\n                )\n                screenshot.save()\n                component.log_info(\"create screenshot from repository: %s\", filename)\n", "path": "weblate/screenshots/models.py"}]}