\$\begingroup\$
\$\endgroup\$
This is my first go at Django, so I'm looking first to learn how to make this more idiomatic. Also, is there a better way to set up my tests? To my HTTP status codes make sense?
chores/tests.py
import datetime
import json
from django.test import TestCase, Client
from django.urls import reverse
from rest_framework import status
from .models import Chore
class ChoreTest(TestCase):
def test_update(self):
chore = Chore(name='sweep',
period=7,
due=datetime.date.today())
chore.update()
self.assertEqual(chore.due,
datetime.date.today() + datetime.timedelta(days=7))
def test_update_overdue_chore(self):
chore = Chore(name='sweep',
period=7,
due=datetime.date.today() - datetime.timedelta(days=1))
chore.update()
self.assertEqual(chore.due,
datetime.date.today() + datetime.timedelta(days=7))
class TestChoresView(TestCase):
def setUp(self) -> None:
Chore.objects.create(name='sweep',
period=7,
due=datetime.date.today())
Chore.objects.create(name='wipe',
period=3,
due=datetime.date.today() + datetime.timedelta(days=1))
self.client = Client()
def test_get_all_chores(self):
response = self.client.get(reverse('all'))
response_content = json.loads(response.content)
self.assertEqual(2, len(response_content))
all_names = [chore['name'] for chore in response_content]
self.assertIn('sweep', all_names)
self.assertIn('wipe', all_names)
def test_chores_correctly_serialized(self):
response = self.client.get(reverse('all'))
response_content = json.loads(response.content)
expected_keys = ('name', 'period', 'due', 'id')
for key in expected_keys:
self.assertIn(key, response_content[0].keys())
self.assertIn(key, response_content[1].keys())
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_update_chore(self):
chore = Chore.objects.get(name='sweep')
response = self.client.generic('POST', reverse('update'),
json.dumps({'id': chore.pk}))
chore = Chore.objects.get(name='sweep')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(chore.due,
datetime.date.today() + datetime.timedelta(days=chore.period))
def test_update_invalid_chore(self):
response = self.client.generic('POST', reverse('update'),
json.dumps({'id': 1337}))
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_get_due_chores(self):
response = self.client.get(reverse('due'))
response_content = json.loads(response.content)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(1, len(response_content))
self.assertEqual(response_content[0]['name'], 'sweep')
def test_get_no_chores_due(self):
# Start by updating all chores, so none will be due
chores = Chore.objects.all()
for chore in chores:
chore.update()
response = self.client.get(reverse('due'))
response_content = json.loads(response.content)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(1, len(response_content))
chores/views.py
import datetime
import json
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Chore
from .serializers import ChoreSerializer
@ensure_csrf_cookie
def index(request):
return render(request, 'chores/index.html')
@ensure_csrf_cookie
@api_view(['GET'])
def chores(request):
all_chores = Chore.objects.all()
serializer = ChoreSerializer(all_chores, many=True)
return Response(serializer.data)
@ensure_csrf_cookie
@api_view(['POST'])
def update_chore(request):
content = json.loads(request.body)
chore_id = content['id']
try:
chore = Chore.objects.get(pk=chore_id)
except Chore.DoesNotExist:
return Response(status=status.HTTP_400_BAD_REQUEST)
chore.update()
chore.save()
return Response(status=status.HTTP_200_OK)
@api_view(['GET'])
def due_chores(request):
today = datetime.date.today()
all_chores = Chore.objects.filter(due__lte=today)
serializer = ChoreSerializer(all_chores, many=True)
return Response(serializer.data)
chores/models.py
import datetime
from django.db import models
class Chore(models.Model):
name = models.CharField(max_length=100)
# Number of days in between instances of scheduled chore
period = models.IntegerField()
# Due date of chore
due = models.DateField()
def __str__(self):
return self.name
def update(self) -> None:
"""
Mark a chore as complete by updating the due date.
:return: None
"""
self.due = datetime.date.today() + datetime.timedelta(days=self.period)
asked Jan 30, 2020 at 22:48
1 Answer 1
\$\begingroup\$
\$\endgroup\$
Some suggestions:
Chore
'speriod
field should probably beperiod_days
to avoid the comment.- The comment on
update
makes it look like it should be calledmark_complete
or something, so that you can remove the comment. In any case I would expect the view rather than the model to do that update. - It is customary to use a
ModelViewSet
to encapsulate the sort of functions you have in views.py. Chore.DoesNotExist
should result in a HTTP 404 response code, which is the default. No need for custom code. In general, letting DRF decide the response code is much easier.- The test classes have inconsistent naming.
lang-py