First commit of the V2.

New base, new info.
This commit is contained in:
2019-04-18 00:58:59 +02:00
parent 698b6dd865
commit 31d5f7ade4
185 changed files with 32824 additions and 0 deletions

0
music/__init__.py Normal file
View File

5
music/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Songs
admin.site.register(Songs)

5
music/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class MusicConfig(AppConfig):
name = 'music'

18
music/decorators.py Normal file
View File

@ -0,0 +1,18 @@
from rest_framework.response import Response
from rest_framework.views import status
def validate_request_data(fn):
def decorated(*args, **kwargs):
# args[0] == GenericView Object
title = args[0].request.data.get("title", "")
artist = args[0].request.data.get("artist", "")
if not title and not artist:
return Response(
data={
"message": "Both title and artist are required to add a song"
},
status=status.HTTP_400_BAD_REQUEST
)
return fn(*args, **kwargs)
return decorated

View File

@ -0,0 +1,22 @@
# Generated by Django 2.0.3 on 2018-03-21 13:27
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Songs',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('artist', models.CharField(max_length=255)),
],
),
]

View File

11
music/models.py Normal file
View File

@ -0,0 +1,11 @@
from django.db import models
class Songs(models.Model):
# song title
title = models.CharField(max_length=255, null=False)
# name of artist or group/band
artist = models.CharField(max_length=255, null=False)
def __str__(self):
return "{} - {}".format(self.title, self.artist)

32
music/serializers.py Normal file
View File

@ -0,0 +1,32 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Songs
class SongsSerializer(serializers.ModelSerializer):
class Meta:
model = Songs
fields = ("title", "artist")
def update(self, instance, validated_data):
instance.title = validated_data.get("title", instance.title)
instance.artist = validated_data.get("artist", instance.artist)
instance.save()
return instance
def create(self, validated_data):
return Songs.objects.create(**validated_data)
class TokenSerializer(serializers.Serializer):
"""
This serializer serializes the token data
"""
token = serializers.CharField(max_length=255)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "email")

329
music/tests.py Normal file
View File

@ -0,0 +1,329 @@
import json
from django.urls import reverse
from django.contrib.auth.models import User
from rest_framework.test import APITestCase, APIClient
from rest_framework.views import status
from .models import Songs
from .serializers import SongsSerializer
# tests for models
class SongsModelTest(APITestCase):
def setUp(self):
self.a_song = Songs.objects.create(
title="Ugandan anthem",
artist="George William Kakoma"
)
def test_song(self):
""""
This test ensures that the song created in the setup
exists
"""
self.assertEqual(self.a_song.title, "Ugandan anthem")
self.assertEqual(self.a_song.artist, "George William Kakoma")
self.assertEqual(str(self.a_song), "Ugandan anthem - George William Kakoma")
# tests for views
class BaseViewTest(APITestCase):
client = APIClient()
@staticmethod
def create_song(title="", artist=""):
"""
Create a song in the db
:param title:
:param artist:
:return:
"""
if title != "" and artist != "":
Songs.objects.create(title=title, artist=artist)
def make_a_request(self, kind="post", **kwargs):
"""
Make a post request to create a song
:param kind: HTTP VERB
:return:
"""
if kind == "post":
return self.client.post(
reverse(
"songs-list-create",
kwargs={
"version": kwargs["version"]
}
),
data=json.dumps(kwargs["data"]),
content_type='application/json'
)
elif kind == "put":
return self.client.put(
reverse(
"songs-detail",
kwargs={
"version": kwargs["version"],
"pk": kwargs["id"]
}
),
data=json.dumps(kwargs["data"]),
content_type='application/json'
)
else:
return None
def fetch_a_song(self, pk=0):
return self.client.get(
reverse(
"songs-detail",
kwargs={
"version": "v1",
"pk": pk
}
)
)
def delete_a_song(self, pk=0):
return self.client.delete(
reverse(
"songs-detail",
kwargs={
"version": "v1",
"pk": pk
}
)
)
def login_a_user(self, username="", password=""):
url = reverse(
"auth-login",
kwargs={
"version": "v1"
}
)
return self.client.post(
url,
data=json.dumps({
"username": username,
"password": password
}),
content_type="application/json"
)
def login_client(self, username="", password=""):
# get a token from DRF
response = self.client.post(
reverse("create-token"),
data=json.dumps(
{
'username': username,
'password': password
}
),
content_type='application/json'
)
self.token = response.data['token']
# set the token in the header
self.client.credentials(
HTTP_AUTHORIZATION='Bearer ' + self.token
)
self.client.login(username=username, password=password)
return self.token
def register_a_user(self, username="", password="", email=""):
return self.client.post(
reverse(
"auth-register",
kwargs={
"version": "v1"
}
),
data=json.dumps(
{
"username": username,
"password": password,
"email": email
}
),
content_type='application/json'
)
def setUp(self):
# create a admin user
self.user = User.objects.create_superuser(
username="test_user",
email="test@mail.com",
password="testing",
first_name="test",
last_name="user",
)
# add test data
self.create_song("like glue", "sean paul")
self.create_song("simple song", "konshens")
self.create_song("love is wicked", "brick and lace")
self.create_song("jam rock", "damien marley")
self.valid_data = {
"title": "test song",
"artist": "test artist"
}
self.invalid_data = {
"title": "",
"artist": ""
}
self.valid_song_id = 1
self.invalid_song_id = 100
class GetAllSongsTest(BaseViewTest):
def test_get_all_songs(self):
"""
This test ensures that all songs added in the setUp method
exist when we make a GET request to the songs/ endpoint
"""
self.login_client('test_user', 'testing')
# hit the API endpoint
response = self.client.get(
reverse("songs-list-create", kwargs={"version": "v1"})
)
# fetch the data from db
expected = Songs.objects.all()
serialized = SongsSerializer(expected, many=True)
self.assertEqual(response.data, serialized.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
class GetASingleSongsTest(BaseViewTest):
def test_get_a_song(self):
"""
This test ensures that a single song of a given id is
returned
"""
self.login_client('test_user', 'testing')
# hit the API endpoint
response = self.fetch_a_song(self.valid_song_id)
# fetch the data from db
expected = Songs.objects.get(pk=self.valid_song_id)
serialized = SongsSerializer(expected)
self.assertEqual(response.data, serialized.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# test with a song that does not exist
response = self.fetch_a_song(self.invalid_song_id)
self.assertEqual(
response.data["message"],
"Song with id: 100 does not exist"
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
class AddSongsTest(BaseViewTest):
def test_create_a_song(self):
"""
This test ensures that a single song can be added
"""
self.login_client('test_user', 'testing')
# hit the API endpoint
response = self.make_a_request(
kind="post",
version="v1",
data=self.valid_data
)
self.assertEqual(response.data, self.valid_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# test with invalid data
response = self.make_a_request(
kind="post",
version="v1",
data=self.invalid_data
)
self.assertEqual(
response.data["message"],
"Both title and artist are required to add a song"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
class UpdateSongsTest(BaseViewTest):
def test_update_a_song(self):
"""
This test ensures that a single song can be updated. In this
test we update the second song in the db with valid data and
the third song with invalid data and make assertions
"""
self.login_client('test_user', 'testing')
# hit the API endpoint
response = self.make_a_request(
kind="put",
version="v1",
id=2,
data=self.valid_data
)
self.assertEqual(response.data, self.valid_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# test with invalid data
response = self.make_a_request(
kind="put",
version="v1",
id=3,
data=self.invalid_data
)
self.assertEqual(
response.data["message"],
"Both title and artist are required to add a song"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
class DeleteSongsTest(BaseViewTest):
def test_delete_a_song(self):
"""
This test ensures that when a song of given id can be deleted
"""
self.login_client('test_user', 'testing')
# hit the API endpoint
response = self.delete_a_song(1)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
# test with invalid data
response = self.delete_a_song(100)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
class AuthLoginUserTest(BaseViewTest):
"""
Tests for the auth/login/ endpoint
"""
def test_login_user_with_valid_credentials(self):
# test login with valid credentials
response = self.login_a_user("test_user", "testing")
# assert token key exists
self.assertIn("token", response.data)
# assert status code is 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# test login with invalid credentials
response = self.login_a_user("anonymous", "pass")
# assert status code is 401 UNAUTHORIZED
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
class AuthRegisterUserTest(BaseViewTest):
"""
Tests for auth/register/ endpoint
"""
def test_register_a_user(self):
response = self.register_a_user("new_user", "new_pass", "new_user@mail.com")
# assert status code is 201 CREATED
self.assertEqual(response.data["username"], "new_user")
self.assertEqual(response.data["email"], "new_user@mail.com")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# test with invalid data
response = self.register_a_user()
# assert status code
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

10
music/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from .views import ListCreateSongsView, SongsDetailView, LoginView, RegisterUsers
urlpatterns = [
path('songs/', ListCreateSongsView.as_view(), name="songs-list-create"),
path('songs/<int:pk>/', SongsDetailView.as_view(), name="songs-detail"),
path('auth/login/', LoginView.as_view(), name="auth-login"),
path('auth/register/', RegisterUsers.as_view(), name="auth-register")
]

149
music/views.py Normal file
View File

@ -0,0 +1,149 @@
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from rest_framework import generics
from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.views import status
from rest_framework_jwt.settings import api_settings
from .decorators import validate_request_data
from .models import Songs
from .serializers import SongsSerializer, TokenSerializer, UserSerializer
import logging
import json
logger = logging.getLogger(__name__)
# Get the JWT settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class ListCreateSongsView(generics.ListCreateAPIView):
"""
GET songs/
POST songs/
"""
queryset = Songs.objects.all()
serializer_class = SongsSerializer
permission_classes = (permissions.IsAuthenticated,)
@validate_request_data
def post(self, request, *args, **kwargs):
a_song = Songs.objects.create(
title=request.data["title"],
artist=request.data["artist"]
)
return Response(
data=SongsSerializer(a_song).data,
status=status.HTTP_201_CREATED
)
class SongsDetailView(generics.RetrieveUpdateDestroyAPIView):
"""
GET songs/:id/
PUT songs/:id/
DELETE songs/:id/
"""
queryset = Songs.objects.all()
serializer_class = SongsSerializer
def get(self, request, *args, **kwargs):
try:
a_song = self.queryset.get(pk=kwargs["pk"])
return Response(SongsSerializer(a_song).data)
except Songs.DoesNotExist:
return Response(
data={
"message": "Song with id: {} does not exist".format(kwargs["pk"])
},
status=status.HTTP_404_NOT_FOUND
)
@validate_request_data
def put(self, request, *args, **kwargs):
try:
a_song = self.queryset.get(pk=kwargs["pk"])
serializer = SongsSerializer()
updated_song = serializer.update(a_song, request.data)
return Response(SongsSerializer(updated_song).data)
except Songs.DoesNotExist:
return Response(
data={
"message": "Song with id: {} does not exist".format(kwargs["pk"])
},
status=status.HTTP_404_NOT_FOUND
)
def delete(self, request, *args, **kwargs):
try:
a_song = self.queryset.get(pk=kwargs["pk"])
a_song.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Songs.DoesNotExist:
return Response(
data={
"message": "Song with id: {} does not exist".format(kwargs["pk"])
},
status=status.HTTP_404_NOT_FOUND
)
class LoginView(generics.CreateAPIView):
"""
POST auth/login/
"""
# This permission class will over ride the global permission
# class setting
permission_classes = (permissions.AllowAny,)
queryset = User.objects.all()
def post(self, request, *args, **kwargs):
logger.error('post LoginView')
username = request.data.get("username", "")
password = request.data.get("password", "")
user = authenticate(request, username=username, password=password)
if user is not None:
# login saves the users ID in the session,
# using Djangos session framework.
login(request, user)
serializer = TokenSerializer(data={
# using drf jwt utility functions to generate a token
"token": jwt_encode_handler(
jwt_payload_handler(user)
)})
serializer.is_valid()
return Response(serializer.data)
return Response(status=status.HTTP_401_UNAUTHORIZED)
class RegisterUsers(generics.CreateAPIView):
"""
POST auth/register/
"""
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
logger.error('post registerUsers')
username = request.data.get("username", "")
password = request.data.get("password", "")
email = request.data.get("email", "")
if not username and not password and not email:
return Response(
data={
"message": "username, password and email is required to register a user"
},
status=status.HTTP_400_BAD_REQUEST
)
new_user = User.objects.create_user(
username=username, password=password, email=email
)
return Response(
data=UserSerializer(new_user).data,
status=status.HTTP_201_CREATED
)