Skip to content

Commit d6bd756

Browse files
authored
Merge pull request #1198 from thunderstore-io/visibility-mixin-save-fix
Pass through update_fields and other kwargs in VisibilityMixin save override
2 parents 7cc1068 + b733af5 commit d6bd756

File tree

5 files changed

+86
-53
lines changed

5 files changed

+86
-53
lines changed

django/thunderstore/community/models/community.py

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from thunderstore.core.exceptions import PermissionValidationError
2020
from thunderstore.core.mixins import TimestampMixin
2121
from thunderstore.core.types import UserType
22-
from thunderstore.core.utils import check_validity
22+
from thunderstore.core.utils import check_validity, extend_update_fields_if_present
2323
from thunderstore.permissions.utils import validate_user
2424

2525
if TYPE_CHECKING:
@@ -134,67 +134,52 @@ def aggregated(self) -> "AggregatedFields":
134134
else CommunityAggregatedFields.get_empty()
135135
)
136136

137-
def save(self, *args, **kwargs):
137+
def save(self, **kwargs):
138138
if self.pk:
139139
in_db = type(self).objects.get(pk=self.pk)
140140
if in_db.identifier != self.identifier:
141141
raise ValidationError("Field 'identifier' is read only")
142142
if not self.icon:
143143
self.icon_width = 0
144144
self.icon_height = 0
145-
if "update_fields" in kwargs:
146-
kwargs["update_fields"] = set(
147-
kwargs["update_fields"]
148-
+ (
149-
"icon_width",
150-
"icon_height",
151-
),
152-
)
145+
kwargs = extend_update_fields_if_present(
146+
kwargs,
147+
"icon_width",
148+
"icon_height",
149+
)
153150
if not self.background_image:
154151
self.background_image_width = 0
155152
self.background_image_height = 0
156-
if "update_fields" in kwargs:
157-
kwargs["update_fields"] = set(
158-
kwargs["update_fields"]
159-
+ (
160-
"background_image_width",
161-
"background_image_height",
162-
),
163-
)
153+
kwargs = extend_update_fields_if_present(
154+
kwargs,
155+
"background_image_width",
156+
"background_image_height",
157+
)
164158
if not self.hero_image:
165159
self.hero_image_width = 0
166160
self.hero_image_height = 0
167-
if "update_fields" in kwargs:
168-
kwargs["update_fields"] = set(
169-
kwargs["update_fields"]
170-
+ (
171-
"hero_image_width",
172-
"hero_image_height",
173-
),
174-
)
161+
kwargs = extend_update_fields_if_present(
162+
kwargs,
163+
"hero_image_width",
164+
"hero_image_height",
165+
)
175166
if not self.cover_image:
176167
self.cover_image_width = 0
177168
self.cover_image_height = 0
178-
if "update_fields" in kwargs:
179-
kwargs["update_fields"] = set(
180-
kwargs["update_fields"]
181-
+ (
182-
"cover_image_width",
183-
"cover_image_height",
184-
),
185-
)
169+
kwargs = extend_update_fields_if_present(
170+
kwargs,
171+
"cover_image_width",
172+
"cover_image_height",
173+
)
186174
if not self.community_icon:
187175
self.community_icon_width = 0
188176
self.community_icon_height = 0
189-
if "update_fields" in kwargs:
190-
kwargs["update_fields"] = set(
191-
kwargs["update_fields"]
192-
+ (
193-
"community_icon_width",
194-
"community_icon_height",
195-
),
196-
)
197-
return super().save(*args, **kwargs)
177+
kwargs = extend_update_fields_if_present(
178+
kwargs,
179+
"community_icon_width",
180+
"community_icon_height",
181+
)
182+
return super().save(**kwargs)
198183

199184
def __str__(self):
200185
return self.name

django/thunderstore/core/mixins.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,16 @@
77
from django.urls import reverse
88

99
from thunderstore.cache.storage import CACHE_STORAGE
10+
from thunderstore.core.utils import extend_update_fields_if_present
1011

1112

1213
class TimestampMixin(models.Model):
1314
datetime_created = models.DateTimeField(auto_now_add=True)
1415
datetime_updated = models.DateTimeField(auto_now=True)
1516

16-
def save(self, *args, **kwargs):
17-
update_fields = kwargs.pop("update_fields", [])
18-
if update_fields:
19-
kwargs["update_fields"] = tuple(
20-
set(list(update_fields) + ["datetime_updated"])
21-
)
22-
super().save(*args, **kwargs)
17+
def save(self, **kwargs):
18+
kwargs = extend_update_fields_if_present(kwargs, "datetime_updated")
19+
super().save(**kwargs)
2320

2421
class Meta:
2522
abstract = True

django/thunderstore/core/tests/test_utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from thunderstore.core.utils import (
88
capture_exception,
99
check_validity,
10+
extend_update_fields_if_present,
1011
make_full_url,
1112
replace_cdn,
1213
sanitize_filename,
@@ -187,3 +188,41 @@ def test_replace_cdn__raises_for_relative_urls(url: str) -> None:
187188
match="Absolute URL including protocol required",
188189
):
189190
replace_cdn(url, "irrelevant")
191+
192+
193+
def test_extend_update_fields_if_present__no_update_fields_key() -> None:
194+
original = {"some": "value"}
195+
result = extend_update_fields_if_present(original, "new_field")
196+
assert result is not original
197+
# Should not add update_fields key when it's not present in original
198+
assert "update_fields" not in result
199+
# Original dict remains unchanged
200+
assert original == {"some": "value"}
201+
202+
203+
def test_extend_update_fields_if_present__update_fields_none() -> None:
204+
original = {"update_fields": None, "other": 1}
205+
result = extend_update_fields_if_present(original, "x")
206+
# Copy returned
207+
assert result is not original
208+
# When update_fields is None, it should be left as-is
209+
assert result["update_fields"] is None
210+
assert result["other"] == 1
211+
# Original remains unmodified
212+
assert original["update_fields"] is None
213+
214+
215+
def test_extend_update_fields_if_present__extends_and_deduplicates() -> None:
216+
original = {"update_fields": ["a", "b"]}
217+
result = extend_update_fields_if_present(original, "b", "c")
218+
# Should return a set with union of existing and new fields
219+
assert isinstance(result["update_fields"], set)
220+
assert result["update_fields"] == {"a", "b", "c"}
221+
# Original should not be mutated
222+
assert original["update_fields"] == ["a", "b"]
223+
224+
225+
def test_extend_update_fields_if_present__accepts_tuple_and_empty_new_fields() -> None:
226+
original = {"update_fields": ("x",)}
227+
result = extend_update_fields_if_present(original)
228+
assert result["update_fields"] == {"x"}

django/thunderstore/core/utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
22
import urllib.parse
3-
from typing import Callable, Optional
3+
from typing import Any, Callable, Dict, List, Optional
44

55
from django.conf import settings
66
from django.core.exceptions import ValidationError
@@ -111,6 +111,16 @@ def sanitize_filepath(filepath: Optional[str]) -> Optional[str]:
111111
)
112112

113113

114+
def extend_update_fields_if_present(
115+
original_kwargs: Dict[str, Any], *new_fields: str
116+
) -> Dict[str, Any]:
117+
"""Returns a copy of original_kwargs with the update_fields field updated"""
118+
result = {**original_kwargs}
119+
if (upfields := original_kwargs.get("update_fields")) is not None:
120+
result["update_fields"] = {*upfields, *new_fields}
121+
return result
122+
123+
114124
def validate_filepath_prefix(filepath: Optional[str]) -> Optional[str]:
115125
stripped = sanitize_filepath(filepath)
116126
if stripped != filepath:

django/thunderstore/permissions/mixins.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.db.models import Q
66

77
from thunderstore.core.types import UserType
8+
from thunderstore.core.utils import extend_update_fields_if_present
89
from thunderstore.permissions.models import VisibilityFlags
910

1011

@@ -71,13 +72,14 @@ def set_zero_visibility(self):
7172
self.visibility.admin_list = False
7273

7374
@transaction.atomic
74-
def save(self, *args, **kwargs):
75+
def save(self, **kwargs):
7576
if not self.pk and not self.visibility:
7677
self.visibility = VisibilityFlags.objects.create_public()
78+
kwargs = extend_update_fields_if_present(kwargs, "visibility")
7779

7880
self.update_visibility()
7981

80-
super().save()
82+
super().save(**kwargs)
8183

8284
class Meta:
8385
abstract = True

0 commit comments

Comments
 (0)