Skip to content

Commit 20c2574

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 20c2574

File tree

7 files changed

+84
-38
lines changed

7 files changed

+84
-38
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: 21 additions & 20 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,25 @@ 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 = (
314+
SpamModeration.Status.SPAM_LIKELY
315+
if spam_context
316+
else SpamModeration.Status.SCHEDULED_FOR_CHECK
317+
)
318+
default_spam_moderation = {
319+
"status": default_status,
320+
"detection_method": (
321+
spam_context.get("detection_method", "") if spam_context else ""
322+
),
323+
"detection_details": (
324+
spam_context.get("detection_details", "") if spam_context else ""
325+
),
326+
}
313327

314-
# SpamModeration updates the content instance on save
315-
spam_moderation, created = SpamModeration.objects.get_or_create(
328+
SpamModeration.objects.update_or_create(
316329
content_type=content_type,
317330
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-
},
331+
defaults=default_spam_moderation,
327332
)
328-
329-
if not created:
330-
spam_moderation.status = SpamModeration.Status.SCHEDULED_FOR_CHECK
331-
spam_moderation.save()

django/core/models.py

Lines changed: 7 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,10 @@ 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 {
198+
self.Status.SPAM,
199+
self.Status.SPAM_LIKELY,
200+
}
199201
related_object.save()
200202

201203
def __str__(self):

django/core/settings/defaults.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,9 @@ 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 = (
531+
read_secret("llm_spam_check_api_key", "unconfigured") or "unconfigured"
532+
)
531533

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

django/core/tests/test_views.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,13 @@ 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(
168+
event.spam_moderation.status, SpamModeration.Status.SPAM_LIKELY
169+
)
167170
self.assertEqual(event.spam_moderation.detection_method, "honeypot")
171+
self.assertTrue(event.is_marked_spam)
168172

169173
def test_job_creation_with_timer_spam(self):
170174
# FIXME: should incorporate how long a typical request takes to resolve
@@ -179,9 +183,11 @@ def test_job_creation_with_timer_spam(self):
179183
)
180184
self.assertResponseCreated(response)
181185
job = Job.objects.get(title=data["title"])
182-
self.assertTrue(job.is_marked_spam)
186+
183187
self.assertIsNotNone(job.spam_moderation)
188+
self.assertEqual(job.spam_moderation.status, SpamModeration.Status.SPAM_LIKELY)
184189
self.assertEqual(job.spam_moderation.detection_method, "form_submit_time")
190+
self.assertTrue(job.is_marked_spam)
185191

186192
def test_mark_spam(self):
187193
data = self.event_factory.get_request_data()
@@ -193,18 +199,29 @@ def test_mark_spam(self):
193199
format="json",
194200
)
195201
event = Event.objects.get(title=data["title"])
202+
self.assertIsNotNone(event.spam_moderation)
203+
self.assertEqual(
204+
event.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK
205+
)
206+
# by default, all created objects will have is_marked_spam = False unless spam_moderation.status is explicitly SPAM or SPAM_LIKELY
196207
self.assertFalse(event.is_marked_spam)
197-
self.assertIsNone(event.spam_moderation)
208+
198209
response = self.client.post(
199210
reverse("core:event-mark-spam", kwargs={"pk": event.id}),
200211
data,
201212
HTTP_ACCEPT="application/json",
202213
format="json",
203214
)
215+
204216
event.refresh_from_db()
205-
# non-moderators cannot mark content as spam
217+
# non-moderators cannot mark content as spam (set status to SPAM)
206218
self.assertEquals(response.status_code, 403)
219+
self.assertIsNotNone(event.spam_moderation)
220+
self.assertEqual(
221+
event.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK
222+
)
207223
self.assertFalse(event.is_marked_spam)
224+
208225
# check moderator
209226
self.client.login(
210227
username=self.moderator.username, password=self.user_factory.password
@@ -217,12 +234,17 @@ def test_mark_spam(self):
217234
format="json",
218235
)
219236
event.refresh_from_db()
220-
self.assertTrue(event.is_marked_spam)
221237
self.assertIsNotNone(event.spam_moderation)
238+
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.SPAM)
239+
self.assertTrue(event.is_marked_spam)
240+
222241
event.mark_not_spam(self.moderator)
223242
event.refresh_from_db()
224-
self.assertFalse(event.is_marked_spam)
243+
225244
self.assertIsNotNone(event.spam_moderation)
245+
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.NOT_SPAM)
246+
self.assertFalse(event.is_marked_spam)
247+
226248
# check superuser
227249
self.client.login(
228250
username=self.superuser.username, password=self.user_factory.password
@@ -234,9 +256,10 @@ def test_mark_spam(self):
234256
format="json",
235257
)
236258
event.refresh_from_db()
237-
self.assertTrue(event.is_marked_spam)
259+
238260
self.assertIsNotNone(event.spam_moderation)
239261
self.assertEqual(event.spam_moderation.status, SpamModeration.Status.SPAM)
262+
self.assertTrue(event.is_marked_spam)
240263

241264
def test_event_creation_without_spam(self):
242265
data = self.event_factory.get_request_data()
@@ -248,8 +271,12 @@ def test_event_creation_without_spam(self):
248271
)
249272
self.assertResponseCreated(response)
250273
event = Event.objects.get(title=data["title"])
274+
275+
self.assertIsNotNone(event.spam_moderation)
276+
self.assertEqual(
277+
event.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK
278+
)
251279
self.assertFalse(event.is_marked_spam)
252-
self.assertIsNone(event.spam_moderation)
253280

254281
def test_job_update_with_spam(self):
255282
data = self.job_factory.get_request_data()
@@ -261,8 +288,13 @@ def test_job_update_with_spam(self):
261288
)
262289
self.assertResponseCreated(response)
263290
job = Job.objects.get(title=data["title"])
291+
292+
self.assertIsNotNone(job.spam_moderation)
293+
self.assertEqual(
294+
job.spam_moderation.status, SpamModeration.Status.SCHEDULED_FOR_CHECK
295+
)
264296
self.assertFalse(job.is_marked_spam)
265-
self.assertIsNone(job.spam_moderation)
297+
266298
data = self.job_factory.get_request_data(
267299
honeypot_value="spammy content",
268300
elapsed_time=settings.SPAM_LIKELY_SECONDS_THRESHOLD + 1,
@@ -274,9 +306,11 @@ def test_job_update_with_spam(self):
274306
format="json",
275307
)
276308
job.refresh_from_db()
277-
self.assertTrue(job.is_marked_spam)
309+
278310
self.assertIsNotNone(job.spam_moderation)
311+
self.assertEqual(job.spam_moderation.status, SpamModeration.Status.SPAM_LIKELY)
279312
self.assertEqual(job.spam_moderation.detection_method, "honeypot")
313+
self.assertTrue(job.is_marked_spam)
280314

281315
def test_exclude_spam_from_public_views(self):
282316
data = self.event_factory.get_request_data(honeypot_value="spammy content")

django/curator/tests/test_llm_spam_moderation.py

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

2222
self.job_factory = JobFactory(submitter=self.user)
2323
self.event_factory = EventFactory(submitter=self.user)
24+
self.codebase_factory = CodebaseFactory(submitter=self.user)
2425

2526
today = timezone.now()
2627

@@ -38,8 +39,7 @@ def setUp(self):
3839
title="Test Event",
3940
)
4041

41-
codebase_factory = CodebaseFactory(submitter=self.user)
42-
self.codebase = codebase_factory.create(
42+
self.codebase = self.codebase_factory.create(
4343
title="Test Codebase", description="Codebase Description"
4444
)
4545

0 commit comments

Comments
 (0)