aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar wookie184 <[email protected]>2024-04-02 19:38:06 +0100
committerGravatar GitHub <[email protected]>2024-04-02 19:38:06 +0100
commita1201ad1c73ff3b8b76432b5780232dac09d21c5 (patch)
tree762dc4c7f3d43776218a724ffbe103353ca3cfc7
parentAdd test case for DRF 3.15 regression (diff)
parentMerge pull request #1287 from python-discord/upsert-tags-in-three-queries (diff)
Merge branch 'main' into add-test-case-drf-3.15-regression
-rw-r--r--poetry.lock38
-rw-r--r--pydis_site/apps/api/migrations/0095_user_display_name.py18
-rw-r--r--pydis_site/apps/api/models/bot/user.py5
-rw-r--r--pydis_site/apps/api/serializers.py4
-rw-r--r--pydis_site/apps/api/tests/test_github_webhook_filter.py23
-rw-r--r--pydis_site/apps/api/tests/test_users.py13
-rw-r--r--pydis_site/apps/api/views.py23
-rw-r--r--pydis_site/apps/api/viewsets/bot/user.py14
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md2
-rw-r--r--pydis_site/apps/content/tests/test_utils.py12
-rw-r--r--pydis_site/apps/content/utils.py31
-rw-r--r--pyproject.toml2
13 files changed, 133 insertions, 54 deletions
diff --git a/poetry.lock b/poetry.lock
index 8239793e..947c45df 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1006,28 +1006,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "ruff"
-version = "0.3.4"
+version = "0.3.5"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"},
- {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"},
- {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"},
- {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"},
- {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"},
- {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"},
+ {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"},
+ {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"},
+ {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"},
+ {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"},
+ {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"},
+ {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"},
+ {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"},
+ {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"},
+ {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"},
+ {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"},
+ {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"},
]
[[package]]
@@ -1224,4 +1224,4 @@ brotli = ["Brotli"]
[metadata]
lock-version = "2.0"
python-versions = "3.11.*"
-content-hash = "4028778c4e713fb935bcc463c3ed209f6251f09c62db700b95524469022b7895"
+content-hash = "622e5580f57efb78ad8ef32a76b9491e9e526edf0f87b0e74395c930155b23f3"
diff --git a/pydis_site/apps/api/migrations/0095_user_display_name.py b/pydis_site/apps/api/migrations/0095_user_display_name.py
new file mode 100644
index 00000000..82381830
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0095_user_display_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.3 on 2024-04-01 12:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("api", "0094_migrate_mailing_listdata"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="user",
+ name="display_name",
+ field=models.CharField(blank=True, help_text="The display name, taken from Discord.", max_length=32),
+ ),
+ migrations.RunSQL("UPDATE api_user SET display_name = name;", elidable=True),
+ ]
diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py
index afc5ba1e..1cb10988 100644
--- a/pydis_site/apps/api/models/bot/user.py
+++ b/pydis_site/apps/api/models/bot/user.py
@@ -33,6 +33,11 @@ class User(ModelReprMixin, models.Model):
max_length=32,
help_text="The username, taken from Discord.",
)
+ display_name = models.CharField(
+ max_length=32,
+ blank=True,
+ help_text="The display name, taken from Discord.",
+ )
discriminator = models.PositiveSmallIntegerField(
validators=(
MaxValueValidator(
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index ea94214f..60d3637c 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -434,7 +434,7 @@ class FilterListSerializer(ModelSerializer):
schema = {name: getattr(instance, name) for name in BASE_FILTERLIST_FIELDS}
schema['filters'] = [
FilterSerializer(many=False).to_representation(instance=item)
- for item in Filter.objects.filter(filter_list=instance.id)
+ for item in Filter.objects.filter(filter_list=instance.id).prefetch_related('filter_list')
]
settings = {name: getattr(instance, name) for name in BASE_SETTINGS_FIELDS}
@@ -673,7 +673,7 @@ class UserSerializer(ModelSerializer):
"""Metadata defined for the Django REST Framework."""
model = User
- fields = ('id', 'name', 'discriminator', 'roles', 'in_guild')
+ fields = ('id', 'name', 'display_name', 'discriminator', 'roles', 'in_guild')
depth = 1
list_serializer_class = UserListSerializer
diff --git a/pydis_site/apps/api/tests/test_github_webhook_filter.py b/pydis_site/apps/api/tests/test_github_webhook_filter.py
index 8ca60511..d64e1a13 100644
--- a/pydis_site/apps/api/tests/test_github_webhook_filter.py
+++ b/pydis_site/apps/api/tests/test_github_webhook_filter.py
@@ -1,3 +1,4 @@
+import io
from unittest import mock
from urllib.error import HTTPError
@@ -44,8 +45,10 @@ class GitHubWebhookFilterAPITests(APITestCase):
context_mock.read.return_value = b'{"status": "ok"}'
response = self.client.post(url, data=payload, headers=headers)
- self.assertEqual(response.status_code, context_mock.status)
- self.assertEqual(response.headers.get('X-Clacks-Overhead'), 'Joe Armstrong')
+ response_body = response.json()
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response_body.get("headers", {}).get("X-Clacks-Overhead"), 'Joe Armstrong')
+ self.assertEqual(response_body.get("original_status"), 299)
def test_rate_limit_is_logged_to_sentry(self):
url = reverse('api:github-webhook-filter', args=('id', 'token'))
@@ -56,6 +59,22 @@ class GitHubWebhookFilterAPITests(APITestCase):
mock.patch.object(GitHubWebhookFilterView, "logger") as logger,
):
urlopen.side_effect = HTTPError(None, 429, 'Too Many Requests', {}, None)
+ urlopen.side_effect.fp = io.BytesIO()
+ logger.warning = mock.PropertyMock()
+ self.client.post(url, data=payload, headers=headers)
+
+ logger.warning.assert_called_once()
+
+ def test_other_error_is_logged(self):
+ url = reverse('api:github-webhook-filter', args=('id', 'token'))
+ payload = {}
+ headers = {'X-GitHub-Event': 'pull_request_review'}
+ with (
+ mock.patch('urllib.request.urlopen') as urlopen,
+ mock.patch.object(GitHubWebhookFilterView, "logger") as logger,
+ ):
+ urlopen.side_effect = HTTPError(None, 451, 'Unavailable For Legal Reasons', {}, None)
+ urlopen.side_effect.fp = io.BytesIO()
logger.warning = mock.PropertyMock()
self.client.post(url, data=payload, headers=headers)
diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py
index cff4a825..5dda6344 100644
--- a/pydis_site/apps/api/tests/test_users.py
+++ b/pydis_site/apps/api/tests/test_users.py
@@ -61,7 +61,8 @@ class CreationTests(AuthenticatedAPITestCase):
url = reverse('api:bot:user-list')
data = {
'id': 42,
- 'name': "Test",
+ 'name': "test",
+ 'display_name': "Test Display",
'discriminator': 42,
'roles': [
self.role.id
@@ -75,6 +76,7 @@ class CreationTests(AuthenticatedAPITestCase):
user = User.objects.get(id=42)
self.assertEqual(user.name, data['name'])
+ self.assertEqual(user.display_name, data['display_name'])
self.assertEqual(user.discriminator, data['discriminator'])
self.assertEqual(user.in_guild, data['in_guild'])
@@ -83,7 +85,8 @@ class CreationTests(AuthenticatedAPITestCase):
data = [
{
'id': 5,
- 'name': "test man",
+ 'name': "testman",
+ 'display_name': "Test Display 1",
'discriminator': 42,
'roles': [
self.role.id
@@ -92,7 +95,8 @@ class CreationTests(AuthenticatedAPITestCase):
},
{
'id': 8,
- 'name': "another test man",
+ 'name': "anothertestman",
+ 'display_name': "Test Display 2",
'discriminator': 555,
'roles': [],
'in_guild': False
@@ -200,7 +204,8 @@ class MultiPatchTests(AuthenticatedAPITestCase):
data = [
{
"id": 1,
- "name": "User 1 patched!",
+ "name": "user1patched",
+ "display_name": "User 1 Patched",
"discriminator": 1010,
"roles": [self.role_developer.id],
"in_guild": False
diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py
index 1fa3efc2..a3b0016c 100644
--- a/pydis_site/apps/api/views.py
+++ b/pydis_site/apps/api/views.py
@@ -303,9 +303,26 @@ class GitHubWebhookFilterView(APIView):
(response_status, headers, body) = self.send_webhook(
webhook_id, webhook_token, request.data, dict(request.headers),
)
- headers.pop('Connection', None)
- headers.pop('Content-Length', None)
- return Response(data=body, headers=headers, status=response_status)
+
+ body_decoded = body.decode("utf-8")
+
+ if (
+ not (status.HTTP_200_OK <= response_status < status.HTTP_300_MULTIPLE_CHOICES)
+ and response_status != status.HTTP_429_TOO_MANY_REQUESTS
+ ):
+ self.logger.warning(
+ "Failed to send GitHub webhook to Discord. Response code %d, body: %s",
+ response_status,
+ body_decoded,
+ )
+
+ response_body = {
+ "original_status": response_status,
+ "data": body_decoded,
+ "headers": headers,
+ }
+
+ return Response(response_body)
def send_webhook(
self,
diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py
index 77378336..d06eb868 100644
--- a/pydis_site/apps/api/viewsets/bot/user.py
+++ b/pydis_site/apps/api/viewsets/bot/user.py
@@ -64,7 +64,8 @@ class UserViewSet(ModelViewSet):
... 'results': [
... {
... 'id': 409107086526644234,
- ... 'name': "Python",
+ ... 'name': "python",
+ ... 'display_name': "Python",
... 'discriminator': 4329,
... 'roles': [
... 352427296948486144,
@@ -79,6 +80,7 @@ class UserViewSet(ModelViewSet):
#### Optional Query Parameters
- username: username to search for
+ - display_name: display name to search for
- discriminator: discriminator to search for
- page_size: number of Users in one page, defaults to 10,000
- page: page number
@@ -92,7 +94,8 @@ class UserViewSet(ModelViewSet):
#### Response format
>>> {
... 'id': 409107086526644234,
- ... 'name': "Python",
+ ... 'name': "python",
+ ... 'display_name': "Python",
... 'discriminator': 4329,
... 'roles': [
... 352427296948486144,
@@ -170,6 +173,7 @@ class UserViewSet(ModelViewSet):
>>> {
... 'id': int,
... 'name': str,
+ ... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
@@ -192,6 +196,7 @@ class UserViewSet(ModelViewSet):
>>> {
... 'id': int,
... 'name': str,
+ ... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
@@ -210,6 +215,7 @@ class UserViewSet(ModelViewSet):
>>> {
... 'id': int,
... 'name': str,
+ ... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
@@ -229,6 +235,7 @@ class UserViewSet(ModelViewSet):
... {
... 'id': int,
... 'name': str,
+ ... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
@@ -236,6 +243,7 @@ class UserViewSet(ModelViewSet):
... {
... 'id': int,
... 'name': str,
+ ... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
@@ -260,7 +268,7 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.all().order_by("id")
pagination_class = UserListPagination
filter_backends = (DjangoFilterBackend,)
- filterset_fields = ('name', 'discriminator')
+ filterset_fields = ('name', 'discriminator', 'display_name')
def get_serializer(self, *args, **kwargs) -> ModelSerializer:
"""Set Serializer many attribute to True if request body contains a list."""
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md
index f54ee664..a414bc20 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md
@@ -15,7 +15,7 @@ This page will focus on the quickest steps one can take, with mentions of altern
### Setup Project Dependencies
Below are the dependencies you **must** have installed to get started with the bot.
-1. Make sure you have [Python 3.11](https://www.python.org/downloads/) installed. It helps if it is your system's default Python version.
+1. Make sure you have [Python 3.12](https://www.python.org/downloads/) installed. It helps if it is your system's default Python version.
1. [Install Poetry](https://github.com/python-poetry/poetry#installation).
1. [Install the project's dependencies](../installing-project-dependencies).
1. Docker.
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md
index 7861c3d9..56d95db4 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/sir-lancebot.md
@@ -10,7 +10,7 @@ You should have already forked the [`sir-lancebot`](https://github.com/python-di
Remember to ensure that you have read the [contributing guidelines](../contributing-guidelines) in full before you start contributing.
### Requirements
-- [Python 3.10.*](https://www.python.org/downloads/)
+- [Python 3.12.*](https://www.python.org/downloads/)
- [Poetry](https://github.com/python-poetry/poetry#installation)
- [Git](https://git-scm.com/downloads)
- [Windows Installer](https://git-scm.com/download/win)
diff --git a/pydis_site/apps/content/tests/test_utils.py b/pydis_site/apps/content/tests/test_utils.py
index 7f7736f9..d26c59d5 100644
--- a/pydis_site/apps/content/tests/test_utils.py
+++ b/pydis_site/apps/content/tests/test_utils.py
@@ -370,7 +370,7 @@ class TagUtilsTests(TestCase):
self.assertEqual(self.commit, models.Tag.objects.get(name=tag.name).last_commit)
@mock.patch.object(utils, "set_tag_commit")
- def test_exiting_commit(self, set_commit_mock: mock.Mock):
+ def test_existing_commit(self, set_commit_mock: mock.Mock):
"""Test that a commit is saved when the data has not changed."""
tag = models.Tag.objects.create(name="tag-name", body="old body", last_commit=self.commit)
@@ -378,8 +378,18 @@ class TagUtilsTests(TestCase):
tag.last_commit = None
utils.record_tags([tag])
+ tag.refresh_from_db()
self.assertEqual(self.commit, tag.last_commit)
result = utils.get_tag("tag-name")
self.assertEqual(tag, result)
set_commit_mock.assert_not_called()
+
+ def test_deletes_tags_no_longer_present(self):
+ """Test that no longer known tags are deleted."""
+ tag = models.Tag.objects.create(name="tag-name", body="old body", last_commit=self.commit)
+
+ utils.record_tags([])
+
+ with self.assertRaises(models.Tag.DoesNotExist):
+ tag.refresh_from_db()
diff --git a/pydis_site/apps/content/utils.py b/pydis_site/apps/content/utils.py
index 5a146e10..720063e4 100644
--- a/pydis_site/apps/content/utils.py
+++ b/pydis_site/apps/content/utils.py
@@ -12,6 +12,7 @@ import frontmatter
import httpx
import markdown
import yaml
+from django.db import transaction
from django.http import Http404
from django.utils import timezone
from markdown.extensions.toc import TocExtension
@@ -194,23 +195,19 @@ def set_tag_commit(tag: Tag) -> None:
def record_tags(tags: list[Tag]) -> None:
"""Sync the database with an updated set of tags."""
- # Remove entries which no longer exist
- Tag.objects.exclude(name__in=[tag.name for tag in tags]).delete()
-
- # Insert/update the tags
- for new_tag in tags:
- try:
- old_tag = Tag.objects.get(name=new_tag.name)
- except Tag.DoesNotExist:
- # The tag is not in the database yet,
- # pretend it's previous state is the current state
- old_tag = new_tag
-
- if old_tag.sha == new_tag.sha and old_tag.last_commit_id is not None:
- # We still have an up-to-date commit entry
- new_tag.last_commit_id = old_tag.last_commit_id
-
- new_tag.save()
+ with transaction.atomic():
+ # Remove any tags that we don't want to keep in the future
+ Tag.objects.exclude(name__in=(tag.name for tag in tags)).delete()
+
+ # Upsert the data!
+ Tag.objects.bulk_create(
+ tags,
+ update_conflicts=True,
+ # last_commit is not included here. We want to keep that
+ # from the tag that might already be in the database.
+ update_fields=('last_updated', 'sha', 'group', 'body'),
+ unique_fields=('name',),
+ )
# Drop old, unused commits
Commit.objects.filter(tag__isnull=True).delete()
diff --git a/pyproject.toml b/pyproject.toml
index 760ec819..964f03eb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ psycopg = {extras = ["binary"], version = "3.1.18"}
[tool.poetry.group.dev.dependencies]
python-dotenv = "1.0.1"
taskipy = "1.12.2"
-ruff = "0.3.4"
+ruff = "0.3.5"
[tool.poetry.group.lint.dependencies]
pre-commit = "3.7.0"