Django
Jump to navigation
Jump to search
Django, REST and TDD example project
Install python 3 env
- required packages:
sudo apt-get install python3-pip virtualenv
- optional update:
sudo pip3 install --user --upgrade setuptools pip virtualenv
Create a virtual env with Django
virtualenv -p python3 env # go in virtual env, use deactivate to exit # OR: virtualenv --no-site-packages --always-copy --python python3 env source env/bin/activate pip3 install Django==1.9.2 python -c "import django; print(django.get_version())"
Create our test project
django-admin.py startproject tdd_rest_project cd tdd_rest_project python manage.py migrate # create database using sqlite initially python manage.py createsuperuser # create main user (e.g. 'user:linea@2014') # run follow to test the admin server python manage.py runserver # open at http://localhost:8000/admin/ ^C # Ctrl+C to stop
- NOTE: A Django project is a single database divided in several mini-apps
Add addictional packages to test and implement REST api
pip3 install webtest==2.0.20 django-webtest==1.7.8 pip3 install djangorestframework==3.3.2
- Fix requirements
pip freeze echo -n > requirements.txt echo Django==1.9.2 >> requirements.txt echo WebTest==2.0.20 >> requirements.txt echo django-webtest==1.7.8 >> requirements.txt echo djangorestframework==3.3.2 >> requirements.txt
Create our first REST mini Application INSIDE project sub-folder
- NOTE: we can have several mini-app in the same project
The follow is get from http://arunrocks.com/understanding-tdd-with-django/
applying the http://www.django-rest-framework.org/tutorial/quickstart/ tutorial
cd tdd_rest_project django-admin.py startapp shorturls_app
- Configure the app
- Add the app in the project tdd_rest_project/settings.py file
[...] INSTALLED_APPS = [ [...] 'rest_framework', 'tdd_rest_project.shorturls_app' ] REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',), 'PAGE_SIZE': 10 } [...]
Add our first test: the url received is less then the original
- Add our first test sobtituting shorturls_app/tests.py content with follow:
from django.test import TestCase from .models import Link class ShortenerText(TestCase): def test_shortens(self): """ Test that urls get shorter """ url = "http://www.example.com/" l = Link(url=url) short_url = Link.shorten(l) self.assertLess(len(short_url), len(url))
- Run the test, it will fail
python ../manage.py test shorturls_app
- Add our fist model sobstituting shorturls_app/models.py with follow
from django.db import models class Link(models.Model): url = models.URLField() @staticmethod def shorten(long_url): return ""
- Migrate the model (this create database tables)
python ../manage.py makemigrations python ../manage.py migrate
- Run the test again, it will success, because "" is shorter then "http://www.example.com/"
python ../manage.py test shorturls_app
Add our second test: obtain the original url from reduced one
- append follow to ShortenerText class in shorturls_app/tests.py
[...] def test_recover_link(self): """ Tests that the shortened then expanded url is the same as original """ url = "http://www.example.com/" l = Link(url=url) short_url = Link.shorten(l) l.save() # Another user asks for the expansion of short_url exp_url = Link.expand(short_url) self.assertEqual(url, exp_url)
- Run the test again, it will fail, because Link model have not expand function
python ../manage.py test shorturls_app
- So we can create a dummy expand function, but the test will fail again
In this case, we can alter the Link methods to use the primary key as shorten url, in a way that shorten method returns the primary key, and the expand method returns the url related to the primary key that the user inform.
So, change the Link model sobstituting the shorturls_app/models.py with the follow one:
from django.db import models class Link(models.Model): url = models.URLField() @staticmethod def shorten(link): l, _ = Link.objects.get_or_create(url=link.url) return str(l.pk) @staticmethod def expand(slug): link_id = int(slug) l = Link.objects.get(pk=link_id) return l.url
- Run the test again, it will succeed
python ../manage.py test shorturls_app
Add our first REST interface
- Add a View for Links in shorturls_app/views.py
from rest_framework import viewsets from .serializers import LinkSerializer from .models import Link class LinkViewSet(viewsets.ModelViewSet): """ API endpoint that allows links to be viewed or edited. """ queryset = Link.objects.all().order_by('-url') serializer_class = LinkSerializer
- Add a Link serializer in shorturls_app/serializers.py
from rest_framework import serializers from .models import Link class LinkSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Link fields = ['url']
- Configure common project urls adding the follow in tdd_rest_project/urls.py:
[...] from django.conf.urls import include from rest_framework import routers from .shorturls_app import views router = routers.DefaultRouter() router.register(r'links', views.LinkViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns.append( url(r'^rest/', include(router.urls)) ) urlpatterns.append( url(r'^api-auth/', include( 'rest_framework.urls', namespace='rest_framework')) )
- Run the server
python ../manage.py runserver
- Open 'localhost:8000/rest/links' in your browser, log-in and enjoy
Add our first test to our REST interface
- Sobstitute wit follow:
Auth details in http://www.django-rest-framework.org/api-guide/testing/#forcing-authentication
from django.test import TestCase from django.contrib.auth.models import User from rest_framework.test import APIRequestFactory from rest_framework.test import force_authenticate import json from .models import Link from .views import LinkViewSet class ShortenerText(TestCase): def test_shortens(self): """ Test that urls get shorter """ url = "http://www.example.com/" l = Link(url=url) short_url = Link.shorten(l) self.assertLess(len(short_url), len(url)) def test_recover_link(self): """ Tests that the shortened then expanded url is the same as original """ url = "http://www.example.com/" l = Link(url=url) short_url = Link.shorten(l) l.save() # Another user asks for the expansion of short_url exp_url = Link.expand(short_url) self.assertEqual(url, exp_url) def test_rest_obtaining_a_saved_link_link(self): """ Tests a REST request retreiving a saved url """ rest_url = "/rest/links/" data = "http://www.example.com/" # save link l = Link(url=data) l.save() # Another user asks for the expansion of short_url user = User(username='user', password='user', is_staff=True) user.save() request = APIRequestFactory().post( rest_url, json.dumps({'url': data}), content_type='application/json' ) force_authenticate(request, user=user) view = LinkViewSet.as_view({'post': 'create'}) response = view(request) self.assertEqual(response.data, {'url': data})