Skip to content

Commit b46ba40

Browse files
committed
fix: set is_marked_spam to True for SpamModeration.Status SPAM or SPAM_LIKELY
- fix tests - add asdf & direnv to .gitignore and .dockerignore
1 parent 0b8d8db commit b46ba40

File tree

8 files changed

+71
-41
lines changed

8 files changed

+71
-41
lines changed

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.direnv/
2+
.tool-versions
3+
.envrc
14
.git
25
.yarn/cache
36
.yarn/install-state.gz

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,7 @@ vignettes/*.pdf
180180

181181
# End of https://www.toptal.com/developers/gitignore/api/r
182182

183+
# asdf & direnv
184+
.direnv/
185+
.tool-versions
186+
.envrc

django/core/mixins.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,11 @@ class SpamCatcherViewSetMixin:
249249

250250
def perform_create(self, serializer: serializers.Serializer):
251251
super().perform_create(serializer)
252-
self.handle_spam_detection(serializer)
252+
self.create_spam_moderation_object(serializer)
253253

254254
def perform_update(self, serializer):
255255
super().perform_update(serializer)
256-
self.handle_spam_detection(serializer)
256+
self.create_spam_moderation_object(serializer)
257257

258258
def _validate_content_object(self, instance):
259259
# make sure that the instance has a spam_moderation attribute as well as the
@@ -294,10 +294,10 @@ def mark_spam(self, request, **kwargs):
294294
spam_moderation.save()
295295
return redirect(instance.get_list_url())
296296

297-
def handle_spam_detection(self, serializer: serializers.Serializer):
297+
def create_spam_moderation_object(self, serializer: serializers.Serializer):
298298
try:
299299
self._validate_content_object(serializer.instance)
300-
self._record_spam(
300+
self._create_spam_moderation_object(
301301
serializer.instance,
302302
(
303303
serializer.context["spam_context"]
@@ -308,24 +308,19 @@ def handle_spam_detection(self, serializer: serializers.Serializer):
308308
except ValueError as e:
309309
logger.warning("Cannot flag %s as spam: %s", serializer.instance, e)
310310

311-
def _record_spam(self, instance, spam_context: dict = None):
311+
def _create_spam_moderation_object(self, instance, spam_context: dict = None):
312312
content_type = ContentType.objects.get_for_model(type(instance))
313+
default_status = SpamModeration.Status.SPAM_LIKELY if spam_context else SpamModeration.Status.SCHEDULED_FOR_CHECK
314+
default_spam_moderation = {
315+
"status": default_status,
316+
"detection_method": spam_context.get("detection_method",
317+
"") if spam_context else "",
318+
"detection_details": spam_context.get("detection_details",
319+
"") if spam_context else ""
320+
}
313321

314-
# SpamModeration updates the content instance on save
315-
spam_moderation, created = SpamModeration.objects.get_or_create(
322+
SpamModeration.objects.update_or_create(
316323
content_type=content_type,
317324
object_id=instance.id,
318-
defaults={
319-
"status": SpamModeration.Status.SCHEDULED_FOR_CHECK,
320-
"detection_method": (
321-
spam_context["detection_method"] if spam_context else ""
322-
),
323-
"detection_details": (
324-
spam_context["detection_details"] if spam_context else ""
325-
),
326-
},
327-
)
328-
329-
if not created:
330-
spam_moderation.status = SpamModeration.Status.SCHEDULED_FOR_CHECK
331-
spam_moderation.save()
325+
defaults=default_spam_moderation
326+
)

django/core/models.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,15 @@ def deploy_environment(self):
134134

135135
class SpamModeration(models.Model):
136136
class Status(models.TextChoices):
137-
UNREVIEWED = "unreviewed", _("Unreviewed")
138137
SPAM = "spam", _("Confirmed spam")
139138
NOT_SPAM = "not_spam", _("Confirmed not spam")
140139
SCHEDULED_FOR_CHECK = "scheduled_for_check", _("Scheduled for check by LLM")
141-
SPAM_LIKELY = "spam_likely", _("Marked spam by LLM")
142-
NOT_SPAM_LIKELY = "not_spam_likely", _("Marked as not spam by LLM")
140+
SPAM_LIKELY = "spam_likely", _("Automatically marked as spam")
141+
NOT_SPAM_LIKELY = "not_spam_likely", _("Automatically marked as not spam")
143142

144143
status = models.CharField(
145144
choices=Status.choices,
146-
default=Status.UNREVIEWED,
145+
default=Status.SCHEDULED_FOR_CHECK,
147146
max_length=32,
148147
)
149148
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
@@ -195,7 +194,7 @@ def update_related_object(self):
195194
related_object = self.content_object
196195
if hasattr(related_object, "is_marked_spam"):
197196
related_object.spam_moderation = self
198-
related_object.is_marked_spam = self.status == self.Status.SPAM
197+
related_object.is_marked_spam = self.status in {self.Status.SPAM, self.Status.SPAM_LIKELY}
199198
related_object.save()
200199

201200
def __str__(self):

django/core/settings/defaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ def is_test(self):
527527
DISCOURSE_API_KEY = read_secret("discourse_api_key", "unconfigured")
528528
DISCOURSE_API_USERNAME = os.getenv("DISCOURSE_API_USERNAME", "unconfigured")
529529

530-
LLM_SPAM_CHECK_API_KEY = read_secret("llm_spam_check_api_key", "unconfigured")
530+
LLM_SPAM_CHECK_API_KEY = read_secret("llm_spam_check_api_key", "unconfigured") or "unconfigured"
531531

532532
# https://docs.djangoproject.com/en/4.2/ref/settings/#templates
533533
TEMPLATES = [

django/core/settings/dev.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "server"]
1616

17+
1718
# IP Address inside docker container
1819
INTERNAL_IPS = ["172.18.0.1"]
1920

django/core/tests/test_views.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,11 @@ def test_event_creation_with_honeypot_spam(self):
162162
)
163163
self.assertResponseCreated(response)
164164
event = Event.objects.get(title=data["title"])
165-
self.assertTrue(event.is_marked_spam)
165+
166166
self.assertIsNotNone(event.spam_moderation)
167+
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.SPAM_LIKELY)
167168
self.assertEqual(event.spam_moderation.detection_method, "honeypot")
169+
self.assertTrue(event.is_marked_spam)
168170

169171
def test_job_creation_with_timer_spam(self):
170172
# FIXME: should incorporate how long a typical request takes to resolve
@@ -179,9 +181,11 @@ def test_job_creation_with_timer_spam(self):
179181
)
180182
self.assertResponseCreated(response)
181183
job = Job.objects.get(title=data["title"])
182-
self.assertTrue(job.is_marked_spam)
184+
183185
self.assertIsNotNone(job.spam_moderation)
186+
self.assertEqual(job.spam_moderation.status, SpamModeration.Status.SPAM_LIKELY)
184187
self.assertEqual(job.spam_moderation.detection_method, "form_submit_time")
188+
self.assertTrue(job.is_marked_spam)
185189

186190
def test_mark_spam(self):
187191
data = self.event_factory.get_request_data()
@@ -193,18 +197,27 @@ def test_mark_spam(self):
193197
format="json",
194198
)
195199
event = Event.objects.get(title=data["title"])
200+
self.assertIsNotNone(event.spam_moderation)
201+
self.assertEqual(
202+
event.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK)
203+
# by default, all created objects will have is_marked_spam = False unless spam_moderation.status is explicitly SPAM or SPAM_LIKELY
196204
self.assertFalse(event.is_marked_spam)
197-
self.assertIsNone(event.spam_moderation)
205+
198206
response = self.client.post(
199207
reverse("core:event-mark-spam", kwargs={"pk": event.id}),
200208
data,
201209
HTTP_ACCEPT="application/json",
202210
format="json",
203211
)
212+
204213
event.refresh_from_db()
205-
# non-moderators cannot mark content as spam
214+
# non-moderators cannot mark content as spam (set status to SPAM)
206215
self.assertEquals(response.status_code, 403)
216+
self.assertIsNotNone(event.spam_moderation)
217+
self.assertEqual(
218+
event.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK)
207219
self.assertFalse(event.is_marked_spam)
220+
208221
# check moderator
209222
self.client.login(
210223
username=self.moderator.username, password=self.user_factory.password
@@ -217,12 +230,17 @@ def test_mark_spam(self):
217230
format="json",
218231
)
219232
event.refresh_from_db()
220-
self.assertTrue(event.is_marked_spam)
221233
self.assertIsNotNone(event.spam_moderation)
234+
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.SPAM)
235+
self.assertTrue(event.is_marked_spam)
236+
222237
event.mark_not_spam(self.moderator)
223238
event.refresh_from_db()
224-
self.assertFalse(event.is_marked_spam)
239+
225240
self.assertIsNotNone(event.spam_moderation)
241+
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.NOT_SPAM)
242+
self.assertFalse(event.is_marked_spam)
243+
226244
# check superuser
227245
self.client.login(
228246
username=self.superuser.username, password=self.user_factory.password
@@ -234,9 +252,10 @@ def test_mark_spam(self):
234252
format="json",
235253
)
236254
event.refresh_from_db()
237-
self.assertTrue(event.is_marked_spam)
255+
238256
self.assertIsNotNone(event.spam_moderation)
239257
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.SPAM)
258+
self.assertTrue(event.is_marked_spam)
240259

241260
def test_event_creation_without_spam(self):
242261
data = self.event_factory.get_request_data()
@@ -248,8 +267,11 @@ def test_event_creation_without_spam(self):
248267
)
249268
self.assertResponseCreated(response)
250269
event = Event.objects.get(title=data["title"])
270+
271+
self.assertIsNotNone(event.spam_moderation)
272+
self.assertEqual(
273+
event.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK)
251274
self.assertFalse(event.is_marked_spam)
252-
self.assertIsNone(event.spam_moderation)
253275

254276
def test_job_update_with_spam(self):
255277
data = self.job_factory.get_request_data()
@@ -261,8 +283,11 @@ def test_job_update_with_spam(self):
261283
)
262284
self.assertResponseCreated(response)
263285
job = Job.objects.get(title=data["title"])
286+
287+
self.assertIsNotNone(job.spam_moderation)
288+
self.assertEqual(job.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK)
264289
self.assertFalse(job.is_marked_spam)
265-
self.assertIsNone(job.spam_moderation)
290+
266291
data = self.job_factory.get_request_data(
267292
honeypot_value="spammy content",
268293
elapsed_time=settings.SPAM_LIKELY_SECONDS_THRESHOLD + 1,
@@ -274,9 +299,11 @@ def test_job_update_with_spam(self):
274299
format="json",
275300
)
276301
job.refresh_from_db()
277-
self.assertTrue(job.is_marked_spam)
302+
278303
self.assertIsNotNone(job.spam_moderation)
304+
self.assertEqual(job.spam_moderation.status, SpamModeration.Status.SPAM_LIKELY)
279305
self.assertEqual(job.spam_moderation.detection_method, "honeypot")
306+
self.assertTrue(job.is_marked_spam)
280307

281308
def test_exclude_spam_from_public_views(self):
282309
data = self.event_factory.get_request_data(honeypot_value="spammy content")
@@ -288,4 +315,4 @@ def test_exclude_spam_from_public_views(self):
288315
)
289316
public_url = reverse("core:event-list")
290317
response = self.client.get(public_url)
291-
self.assertNotContains(response, data["title"])
318+
self.assertNotContains(response, data["title"])

django/curator/tests/test_llm_spam_moderation.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def setUp(self):
2121

2222
self.job_factory = JobFactory(submitter=self.user)
2323
self.event_factory = EventFactory(submitter=self.user)
24-
24+
self.codebase_factory = CodebaseFactory(submitter=self.user)
25+
2526
today = timezone.now()
2627

2728
# Create test objects
@@ -38,8 +39,8 @@ def setUp(self):
3839
title="Test Event",
3940
)
4041

41-
codebase_factory = CodebaseFactory(submitter=self.user)
42-
self.codebase = codebase_factory.create(
42+
43+
self.codebase = self.codebase_factory.create(
4344
title="Test Codebase", description="Codebase Description"
4445
)
4546

0 commit comments

Comments
 (0)