Skip to content

Commit 3c79668

Browse files
Add state update to homework content endpoint
- Add optional 'state' field to POST /data/{course}/homework/{hw}/content - Allow updating homework state (CL/OP/SC) when creating questions - Add 4 tests for state update functionality - Update documentation in endpoints.md and homework-questions skill 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b48445c commit 3c79668

4 files changed

Lines changed: 209 additions & 7 deletions

File tree

.claude/skills/homework-questions/skill.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,57 @@ curl -X POST "https://courses.datatalks.club/data/<course_slug>/homework/<homewo
153153
}'
154154
```
155155

156+
### Update Homework State
157+
158+
You can also update the homework state when creating questions:
159+
160+
```bash
161+
curl -X POST "https://courses.datatalks.club/data/<course_slug>/homework/<homework_slug>/content" \
162+
-H "Authorization: Token ${AUTH_TOKEN}" \
163+
-H "Content-Type: application/json" \
164+
-d '{
165+
"questions": [
166+
{
167+
"text": "What is 2+2?",
168+
"question_type": "MC",
169+
"answer_type": "INT",
170+
"possible_answers": ["3", "4", "5"],
171+
"correct_answer": "2"
172+
}
173+
],
174+
"state": "OP"
175+
}'
176+
```
177+
178+
**States:**
179+
- `CL` - Closed (not visible to students)
180+
- `OP` - Open (students can submit)
181+
- `SC` - Scored (grading completed)
182+
183+
You can also update state without adding questions:
184+
185+
```bash
186+
curl -X POST "https://courses.datatalks.club/data/<course_slug>/homework/<homework_slug>/content" \
187+
-H "Authorization: Token ${AUTH_TOKEN}" \
188+
-H "Content-Type: application/json" \
189+
-d '{"state": "OP"}'
190+
```
191+
192+
**Response includes state change:**
193+
```json
194+
{
195+
"success": true,
196+
"course": "ml-zoomcamp",
197+
"homework": "hw-1",
198+
"created_questions": [],
199+
"errors": [],
200+
"homework_state": {
201+
"old": "CL",
202+
"new": "OP"
203+
}
204+
}
205+
```
206+
156207
## Field Reference
157208

158209
| Field | Type | Required | Description |
@@ -163,6 +214,7 @@ curl -X POST "https://courses.datatalks.club/data/<course_slug>/homework/<homewo
163214
| `possible_answers` | array | No | Array of answer options (for MC/CB questions) |
164215
| `correct_answer` | string | No | Correct answer (1-based index for MC/CB, value for others) |
165216
| `scores_for_correct_answer` | int | No | Points for correct answer (default: 1) |
217+
| `state` | string | No | Homework state: `CL`, `OP`, or `SC` |
166218

167219
## Question Types
168220

courses/tests/test_data.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2301,4 +2301,88 @@ def test_post_partial_success(self):
23012301

23022302
# Both should succeed since they're valid
23032303
self.assertEqual(len(result["created_questions"]), 2)
2304-
self.assertEqual(len(result["errors"]), 0)
2304+
self.assertEqual(len(result["errors"]), 0)
2305+
2306+
def test_post_update_state_from_closed_to_open(self):
2307+
"""Test POST updates homework state from closed to open"""
2308+
self.assertEqual(self.homework.state, "CL")
2309+
2310+
data = {
2311+
"questions": [
2312+
{"text": "New question", "question_type": "FF"}
2313+
],
2314+
"state": "OP"
2315+
}
2316+
2317+
response = self.client.post(
2318+
self.url, json.dumps(data), content_type="application/json"
2319+
)
2320+
2321+
self.assertEqual(response.status_code, 200)
2322+
result = response.json()
2323+
2324+
self.assertIn("homework_state", result)
2325+
self.assertEqual(result["homework_state"]["old"], "CL")
2326+
self.assertEqual(result["homework_state"]["new"], "OP")
2327+
2328+
# Verify state was updated in DB
2329+
self.homework.refresh_from_db()
2330+
self.assertEqual(self.homework.state, "OP")
2331+
2332+
def test_post_update_state_only(self):
2333+
"""Test POST can update state without adding questions"""
2334+
self.assertEqual(self.homework.state, "CL")
2335+
2336+
data = {"state": "OP"}
2337+
2338+
response = self.client.post(
2339+
self.url, json.dumps(data), content_type="application/json"
2340+
)
2341+
2342+
self.assertEqual(response.status_code, 200)
2343+
result = response.json()
2344+
2345+
self.assertEqual(result["homework_state"]["old"], "CL")
2346+
self.assertEqual(result["homework_state"]["new"], "OP")
2347+
self.assertEqual(len(result["created_questions"]), 0)
2348+
2349+
self.homework.refresh_from_db()
2350+
self.assertEqual(self.homework.state, "OP")
2351+
2352+
def test_post_update_state_invalid(self):
2353+
"""Test POST with invalid state returns error"""
2354+
data = {
2355+
"questions": [{"text": "Question", "question_type": "FF"}],
2356+
"state": "INVALID"
2357+
}
2358+
2359+
response = self.client.post(
2360+
self.url, json.dumps(data), content_type="application/json"
2361+
)
2362+
2363+
self.assertEqual(response.status_code, 400)
2364+
result = response.json()
2365+
self.assertIn("Invalid state", result["error"])
2366+
2367+
def test_post_update_all_states(self):
2368+
"""Test POST can update to all valid states"""
2369+
# CL -> OP
2370+
data = {"state": "OP"}
2371+
response = self.client.post(self.url, json.dumps(data), content_type="application/json")
2372+
self.assertEqual(response.status_code, 200)
2373+
self.homework.refresh_from_db()
2374+
self.assertEqual(self.homework.state, "OP")
2375+
2376+
# OP -> SC
2377+
data = {"state": "SC"}
2378+
response = self.client.post(self.url, json.dumps(data), content_type="application/json")
2379+
self.assertEqual(response.status_code, 200)
2380+
self.homework.refresh_from_db()
2381+
self.assertEqual(self.homework.state, "SC")
2382+
2383+
# SC -> CL
2384+
data = {"state": "CL"}
2385+
response = self.client.post(self.url, json.dumps(data), content_type="application/json")
2386+
self.assertEqual(response.status_code, 200)
2387+
self.homework.refresh_from_db()
2388+
self.assertEqual(self.homework.state, "CL")

courses/views/data.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ def homework_content_view(request, course_slug: str, homework_slug: str):
548548
Get or create questions for a homework.
549549
550550
GET: Returns homework details and all questions.
551-
POST: Creates questions for the homework.
551+
POST: Creates questions for the homework and optionally updates state.
552552
553553
POST Expected JSON payload:
554554
{
@@ -561,7 +561,8 @@ def homework_content_view(request, course_slug: str, homework_slug: str):
561561
"correct_answer": "2",
562562
"scores_for_correct_answer": 1
563563
}
564-
]
564+
],
565+
"state": "OP" // Optional: Update homework state (CL=closed, OP=open, SC=scored)
565566
}
566567
"""
567568
try:
@@ -603,11 +604,26 @@ def homework_content_view(request, course_slug: str, homework_slug: str):
603604
"questions": questions_data,
604605
})
605606

606-
# POST: Create new questions
607+
# POST: Create new questions and optionally update state
607608
if request.method != "POST":
608609
return JsonResponse({"error": "Method not allowed"}, status=405)
609610

610611
data = json.loads(request.body)
612+
613+
# Update homework state if provided
614+
state_updated = False
615+
new_state = data.get("state")
616+
if new_state:
617+
valid_states = [s.value for s in HomeworkState]
618+
if new_state not in valid_states:
619+
return JsonResponse({
620+
"error": f"Invalid state. Must be one of: {valid_states}"
621+
}, status=400)
622+
old_state = homework.state
623+
homework.state = new_state
624+
homework.save()
625+
state_updated = True
626+
611627
questions_data = data.get("questions", [])
612628
created_questions = []
613629
errors = []
@@ -634,13 +650,21 @@ def homework_content_view(request, course_slug: str, homework_slug: str):
634650
"error": str(e)
635651
})
636652

637-
return JsonResponse({
653+
response_data = {
638654
"success": True,
639655
"course": course_slug,
640656
"homework": homework_slug,
641657
"created_questions": created_questions,
642658
"errors": errors,
643-
})
659+
}
660+
661+
if state_updated:
662+
response_data["homework_state"] = {
663+
"old": old_state,
664+
"new": new_state
665+
}
666+
667+
return JsonResponse(response_data)
644668

645669
except Http404:
646670
return JsonResponse({"error": "Course or homework not found"}, status=404)

endpoints.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,15 @@ Creates questions for a homework.
432432
| `possible_answers` | array | No | Array of answer options (for MC/CB) |
433433
| `correct_answer` | string | No | Correct answer (index for MC/CB, value for others) |
434434
| `scores_for_correct_answer` | int | No | Points for correct answer (default: 1) |
435+
| `state` | string | No | Homework state: `CL`, `OP`, or `SC` |
436+
437+
### Homework States
438+
439+
| Code | Name | Description |
440+
|------|------|-------------|
441+
| `CL` | Closed | Not visible to students |
442+
| `OP` | Open | Students can submit |
443+
| `SC` | Scored | Grading completed |
435444

436445
### Response
437446

@@ -451,9 +460,25 @@ Creates questions for a homework.
451460
}
452461
```
453462

463+
If `state` is provided, response includes state change:
464+
465+
```json
466+
{
467+
"success": true,
468+
"course": "ml-zoomcamp",
469+
"homework": "hw-1",
470+
"created_questions": [],
471+
"errors": [],
472+
"homework_state": {
473+
"old": "CL",
474+
"new": "OP"
475+
}
476+
}
477+
```
478+
454479
### Error Responses
455480

456-
- `400 Bad Request`: Invalid JSON
481+
- `400 Bad Request`: Invalid JSON or invalid state value
457482
- `401 Unauthorized`: Missing or invalid authentication token
458483
- `404 Not Found`: Course or homework not found
459484
- `405 Method Not Allowed`: Wrong HTTP method (only GET and POST allowed)
@@ -483,4 +508,21 @@ curl -X POST \
483508
]
484509
}' \
485510
https://courses.datatalks.club/data/ml-zoomcamp/homework/hw-1/content
511+
512+
# Create questions and open homework
513+
curl -X POST \
514+
-H "Authorization: Token ${AUTH_TOKEN}" \
515+
-H "Content-Type: application/json" \
516+
-d '{
517+
"questions": [{"text": "What is 2+2?", "question_type": "MC"}],
518+
"state": "OP"
519+
}' \
520+
https://courses.datatalks.club/data/ml-zoomcamp/homework/hw-1/content
521+
522+
# Update homework state only (no new questions)
523+
curl -X POST \
524+
-H "Authorization: Token ${AUTH_TOKEN}" \
525+
-H "Content-Type: application/json" \
526+
-d '{"state": "OP"}' \
527+
https://courses.datatalks.club/data/ml-zoomcamp/homework/hw-1/content
486528
```

0 commit comments

Comments
 (0)