├── .gitignore ├── LICENSE ├── README.md ├── db.sqlite3 ├── habitat ├── default.toml └── plan.sh ├── manage.py ├── project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── requirements.txt └── rest_api ├── __init__.py ├── admin.py ├── apps.py ├── migrations ├── 0001_initial.py ├── 0002_remove_bucketlist_owner.py ├── 0003_bucketlist_owner.py ├── 0004_auto_20170205_1054.py ├── 0005_auto_20170205_1101.py ├── 0006_auto_20170206_1800.py └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | 92 | # sqlite 93 | *.sqlite3 94 | db.sqlite3 95 | 96 | # ignore habitat results folder 97 | results/ 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jee Githinji Gikera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-rest-api 2 | A REST api written in Django for people with deadlines 3 | 4 | ## Technologies used 5 | * [Django](https://www.djangoproject.com/): The web framework for perfectionists with deadlines (Django builds better web apps with less code). 6 | * [DRF](www.django-rest-framework.org/): A powerful and flexible toolkit for building Web APIs 7 | 8 | 9 | ## Installation 10 | * If you wish to run your own build, first ensure you have python globally installed in your computer. If not, you can get python [here](https://www.python.org"). 11 | * After doing this, confirm that you have installed virtualenv globally as well. If not, run this: 12 | ```bash 13 | $ pip install virtualenv 14 | ``` 15 | * Then, Git clone this repo to your PC 16 | ```bash 17 | $ git clone https://github.com/gitgik/django-rest-api.git 18 | ``` 19 | 20 | * #### Dependencies 21 | 1. Cd into your the cloned repo as such: 22 | ```bash 23 | $ cd django-rest-api 24 | ``` 25 | 2. Create and fire up your virtual environment: 26 | ```bash 27 | $ virtualenv venv -p python3 28 | $ source venv/bin/activate 29 | ``` 30 | 3. Install the dependencies needed to run the app: 31 | ```bash 32 | $ pip install -r requirements.txt 33 | ``` 34 | 4. Make those migrations work 35 | ```bash 36 | $ python manage.py makemigrations 37 | $ python manage.py migrate 38 | ``` 39 | 40 | * #### Run It 41 | Fire up the server using this one simple command: 42 | ```bash 43 | $ python manage.py runserver 44 | ``` 45 | You can now access the file api service on your browser by using 46 | ``` 47 | http://localhost:8000/auth/ 48 | ``` 49 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgik/django-rest-api/c11620ea1f9f339acfdb80fab548e9fe301354f5/db.sqlite3 -------------------------------------------------------------------------------- /habitat/default.toml: -------------------------------------------------------------------------------- 1 | listening_port=8080 2 | -------------------------------------------------------------------------------- /habitat/plan.sh: -------------------------------------------------------------------------------- 1 | pkg_origin=gitgik 2 | pkg_name=django-api 3 | pkg_version=0.1.0 4 | pkg_maintainer="jeegiks@gmail.com" 5 | pkg_upstream_url="https://github.com/gitgik/django-rest-api.git" 6 | pkg_exports=([port]=listening_port) 7 | pkg_exposes=(port) 8 | pkg_build_deps=(core/virtualenv) 9 | pkg_deps=(core/python core/coreutils) 10 | pkg_licence=('MIT') 11 | pkg_interpreters=(bin/python3) 12 | 13 | do_verify () { 14 | return 0 15 | } 16 | 17 | do_clean() { 18 | return 0 19 | } 20 | 21 | do_unpack() { 22 | # copy the contents of the source directory to the habitat cache path 23 | PROJECT_ROOT="${PLAN_CONTEXT}/.." 24 | 25 | mkdir -p $pkg_prefix 26 | build_line "Copying project data to $pkg_prefix/" 27 | cp -r $PROJECT_ROOT/project $pkg_prefix/ 28 | cp -r $PROJECT_ROOT/rest_api $pkg_prefix/ 29 | cp -r $PROJECT_ROOT/*.py $pkg_prefix/ 30 | cp -r $PROJECT_ROOT/requirements.txt $pkg_prefix/ 31 | build_line "Copying .env file with preset variables..." 32 | } 33 | 34 | do_build() { 35 | return 0 36 | } 37 | 38 | do_install() { 39 | cd $pkg_prefix 40 | build_line "Creating virtual environment..." 41 | virtualenv venv 42 | source venv/bin/activate 43 | pip3 install -r requirements.txt 44 | build_line "Done installing requirements..." 45 | } 46 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgik/django-rest-api/c11620ea1f9f339acfdb80fab548e9fe301354f5/project/__init__.py -------------------------------------------------------------------------------- /project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '!zxmc^48#eli@!494w9^#56#nf*xp7s&#zu+n)82z&yxsnwnu#' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['localhost', 'testserver'] 29 | 30 | 31 | REST_FRAMEWORK = { 32 | 'DEFAULT_PERMISSION_CLASSES': ( 33 | 'rest_framework.permissions.IsAuthenticated', 34 | ), 35 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 36 | 'rest_framework.authentication.BasicAuthentication', 37 | 'rest_framework.authentication.TokenAuthentication', 38 | ) 39 | } 40 | 41 | # Application definition 42 | 43 | INSTALLED_APPS = [ 44 | 'django.contrib.admin', 45 | 'django.contrib.auth', 46 | 'django.contrib.contenttypes', 47 | 'django.contrib.sessions', 48 | 'django.contrib.messages', 49 | 'django.contrib.staticfiles', 50 | 'rest_framework', 51 | 'rest_framework.authtoken', 52 | 'rest_api', 53 | ] 54 | 55 | MIDDLEWARE = [ 56 | 'django.middleware.security.SecurityMiddleware', 57 | 'django.contrib.sessions.middleware.SessionMiddleware', 58 | 'django.middleware.common.CommonMiddleware', 59 | 'django.middleware.csrf.CsrfViewMiddleware', 60 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 61 | 'django.contrib.messages.middleware.MessageMiddleware', 62 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 63 | ] 64 | 65 | ROOT_URLCONF = 'project.urls' 66 | 67 | TEMPLATES = [ 68 | { 69 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 70 | 'DIRS': [], 71 | 'APP_DIRS': True, 72 | 'OPTIONS': { 73 | 'context_processors': [ 74 | 'django.template.context_processors.debug', 75 | 'django.template.context_processors.request', 76 | 'django.contrib.auth.context_processors.auth', 77 | 'django.contrib.messages.context_processors.messages', 78 | ], 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = 'project.wsgi.application' 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 88 | 89 | DATABASES = { 90 | 'default': { 91 | 'ENGINE': 'django.db.backends.sqlite3', 92 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 93 | } 94 | } 95 | 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 109 | }, 110 | { 111 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 112 | }, 113 | ] 114 | 115 | 116 | # Internationalization 117 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 118 | 119 | LANGUAGE_CODE = 'en-us' 120 | 121 | TIME_ZONE = 'UTC' 122 | 123 | USE_I18N = True 124 | 125 | USE_L10N = True 126 | 127 | USE_TZ = True 128 | 129 | 130 | # Static files (CSS, JavaScript, Images) 131 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 132 | 133 | STATIC_URL = '/static/' 134 | -------------------------------------------------------------------------------- /project/urls.py: -------------------------------------------------------------------------------- 1 | """project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | url(r'^', include('rest_api.urls')) 22 | ] 23 | -------------------------------------------------------------------------------- /project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.4 2 | djangorestframework==3.5.3 3 | wheel==0.24.0 4 | -------------------------------------------------------------------------------- /rest_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgik/django-rest-api/c11620ea1f9f339acfdb80fab548e9fe301354f5/rest_api/__init__.py -------------------------------------------------------------------------------- /rest_api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Bucketlist 3 | 4 | from rest_framework.authtoken.admin import TokenAdmin 5 | 6 | TokenAdmin.raw_id_fields = ('user',) 7 | 8 | # Register your models here. 9 | admin.site.register(Bucketlist) 10 | -------------------------------------------------------------------------------- /rest_api/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class RestApiConfig(AppConfig): 7 | name = 'rest_api' 8 | -------------------------------------------------------------------------------- /rest_api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2017-02-05 08:52 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Bucketlist', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('name', models.CharField(max_length=255, unique=True)), 24 | ('date_created', models.DateTimeField(auto_now_add=True)), 25 | ('date_modified', models.DateTimeField(auto_now=True)), 26 | ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bucketlists', to=settings.AUTH_USER_MODEL)), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /rest_api/migrations/0002_remove_bucketlist_owner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2017-02-05 10:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('rest_api', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='bucketlist', 17 | name='owner', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /rest_api/migrations/0003_bucketlist_owner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2017-02-05 10:48 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('rest_api', '0002_remove_bucketlist_owner'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='bucketlist', 20 | name='owner', 21 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='bucketlists', to=settings.AUTH_USER_MODEL), 22 | preserve_default=False, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /rest_api/migrations/0004_auto_20170205_1054.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2017-02-05 10:54 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('rest_api', '0003_bucketlist_owner'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='bucketlist', 19 | name='owner', 20 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bucketlists', to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /rest_api/migrations/0005_auto_20170205_1101.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2017-02-05 11:01 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('rest_api', '0004_auto_20170205_1054'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='bucketlist', 19 | name='owner', 20 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='bucketlists', to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /rest_api/migrations/0006_auto_20170206_1800.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2017-02-06 18:00 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('rest_api', '0005_auto_20170205_1101'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='bucketlist', 19 | name='owner', 20 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bucketlists', to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /rest_api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgik/django-rest-api/c11620ea1f9f339acfdb80fab548e9fe301354f5/rest_api/migrations/__init__.py -------------------------------------------------------------------------------- /rest_api/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | from django.dispatch import receiver 5 | from django.db.models.signals import post_save 6 | from rest_framework.authtoken.models import Token 7 | from django.contrib.auth.models import User 8 | 9 | 10 | class Bucketlist(models.Model): 11 | """This class represents the bucketlist model.""" 12 | name = models.CharField(max_length=255, blank=False, unique=True) 13 | owner = models.ForeignKey( 14 | 'auth.User', 15 | related_name='bucketlists', 16 | on_delete=models.CASCADE) 17 | date_created = models.DateTimeField(auto_now_add=True) 18 | date_modified = models.DateTimeField(auto_now=True) 19 | 20 | def __str__(self): 21 | """Return a human readable representation of the model instance.""" 22 | return "{}".format(self.name) 23 | 24 | # This receiver handles token creation when a new user is created. 25 | @receiver(post_save, sender=User) 26 | def create_auth_token(sender, instance=None, created=False, **kwargs): 27 | if created: 28 | Token.objects.create(user=instance) 29 | -------------------------------------------------------------------------------- /rest_api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | from .models import Bucketlist 3 | 4 | 5 | class IsOwner(BasePermission): 6 | """Custom permission class to allow bucketlist owners to edit them.""" 7 | 8 | def has_object_permission(self, request, view, obj): 9 | """Return True if permission is granted to the bucketlist owner.""" 10 | if isinstance(obj, Bucketlist): 11 | return obj.owner == request.user 12 | return obj.owner == request.user 13 | -------------------------------------------------------------------------------- /rest_api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Bucketlist 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class BucketlistSerializer(serializers.ModelSerializer): 7 | """Serializer to map the model instance into json format.""" 8 | 9 | owner = serializers.ReadOnlyField(source='owner.username') 10 | 11 | class Meta: 12 | """Map this serializer to a model and their fields.""" 13 | model = Bucketlist 14 | fields = ('id', 'name', 'owner', 'date_created', 'date_modified') 15 | read_only_fields = ('date_created', 'date_modified') 16 | 17 | 18 | class UserSerializer(serializers.ModelSerializer): 19 | """A user serializer to aid in authentication and authorization.""" 20 | 21 | bucketlists = serializers.PrimaryKeyRelatedField( 22 | many=True, queryset=Bucketlist.objects.all()) 23 | 24 | class Meta: 25 | """Map this serializer to the default django user model.""" 26 | model = User 27 | fields = ('id', 'username', 'bucketlists') 28 | -------------------------------------------------------------------------------- /rest_api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from rest_framework.test import APIClient 3 | from rest_framework import status 4 | from django.core.urlresolvers import reverse 5 | from .models import Bucketlist 6 | from django.contrib.auth.models import User 7 | 8 | 9 | class ModelTestCase(TestCase): 10 | """This class defines the test suite for the bucketlist model.""" 11 | 12 | def setUp(self): 13 | """Define the test client and other test variables.""" 14 | user = User.objects.create(username="nerd") 15 | self.name = "Write world class code" 16 | # specify owner of a bucketlist 17 | self.bucketlist = Bucketlist(name=self.name, owner=user) 18 | 19 | def test_model_can_create_a_bucketlist(self): 20 | """Test the bucketlist model can create a bucketlist.""" 21 | old_count = Bucketlist.objects.count() 22 | self.bucketlist.save() 23 | new_count = Bucketlist.objects.count() 24 | self.assertNotEqual(old_count, new_count) 25 | 26 | def test_model_returns_readable_representation(self): 27 | """Test a readable string is returned for the model instance.""" 28 | self.assertEqual(str(self.bucketlist), self.name) 29 | 30 | 31 | class ViewsTestCase(TestCase): 32 | """Test suite for the api views.""" 33 | 34 | def setUp(self): 35 | """Define the test client and other test variables.""" 36 | user = User.objects.create(username="nerd") 37 | 38 | # Initialize client and force it to use authentication 39 | self.client = APIClient() 40 | self.client.force_authenticate(user=user) 41 | 42 | # Since user model instance is not serializable, use its Id/PK 43 | self.bucketlist_data = {'name': 'Go to Ibiza', 'owner': user.id} 44 | self.response = self.client.post( 45 | reverse('create'), 46 | self.bucketlist_data, 47 | format="json") 48 | 49 | def test_api_can_create_a_bucketlist(self): 50 | """Test the api has bucket creation capability.""" 51 | self.assertEqual(self.response.status_code, status.HTTP_201_CREATED) 52 | 53 | def test_authorization_is_enforced(self): 54 | """Test that the api has user authorization.""" 55 | new_client = APIClient() 56 | res = new_client.get('/bucketlists/', kwargs={'pk': 3}, format="json") 57 | self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN) 58 | 59 | def test_api_can_get_a_bucketlist(self): 60 | """Test the api can get a given bucketlist.""" 61 | bucketlist = Bucketlist.objects.get(id=1) 62 | response = self.client.get( 63 | '/bucketlists/', 64 | kwargs={'pk': bucketlist.id}, format="json") 65 | 66 | self.assertEqual(response.status_code, status.HTTP_200_OK) 67 | self.assertContains(response, bucketlist) 68 | 69 | def test_api_can_update_bucketlist(self): 70 | """Test the api can update a given bucketlist.""" 71 | bucketlist = Bucketlist.objects.get() 72 | change_bucketlist = {'name': 'Something new'} 73 | res = self.client.put( 74 | reverse('details', kwargs={'pk': bucketlist.id}), 75 | change_bucketlist, format='json' 76 | ) 77 | self.assertEqual(res.status_code, status.HTTP_200_OK) 78 | 79 | def test_api_can_delete_bucketlist(self): 80 | """Test the api can delete a bucketlist.""" 81 | bucketlist = Bucketlist.objects.get() 82 | response = self.client.delete( 83 | reverse('details', kwargs={'pk': bucketlist.id}), 84 | format='json', 85 | follow=True) 86 | self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT) 87 | -------------------------------------------------------------------------------- /rest_api/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | from rest_framework.urlpatterns import format_suffix_patterns 3 | from .views import CreateView, DetailsView, UserView, UserDetailsView 4 | from rest_framework.authtoken.views import obtain_auth_token 5 | 6 | urlpatterns = { 7 | url(r'^bucketlists/$', CreateView.as_view(), name="create"), 8 | url(r'^bucketlists/(?P[0-9]+)/$', 9 | DetailsView.as_view(), name="details"), 10 | url(r'^auth/', include('rest_framework.urls', 11 | namespace='rest_framework')), 12 | url(r'^users/$', UserView.as_view(), name="users"), 13 | url(r'users/(?P[0-9]+)/$', 14 | UserDetailsView.as_view(), name="user_details"), 15 | url(r'^get-token/', obtain_auth_token), 16 | } 17 | 18 | urlpatterns = format_suffix_patterns(urlpatterns) 19 | -------------------------------------------------------------------------------- /rest_api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, permissions 2 | from .permissions import IsOwner 3 | from .serializers import BucketlistSerializer, UserSerializer 4 | from .models import Bucketlist 5 | from django.contrib.auth.models import User 6 | 7 | 8 | class CreateView(generics.ListCreateAPIView): 9 | """This class handles the GET and POSt requests of our rest api.""" 10 | queryset = Bucketlist.objects.all() 11 | serializer_class = BucketlistSerializer 12 | permission_classes = ( 13 | permissions.IsAuthenticated, 14 | IsOwner) 15 | 16 | def perform_create(self, serializer): 17 | """Save the post data when creating a new bucketlist.""" 18 | serializer.save(owner=self.request.user) 19 | 20 | 21 | class DetailsView(generics.RetrieveUpdateDestroyAPIView): 22 | """This class handles GET, PUT, PATCH and DELETE requests.""" 23 | 24 | queryset = Bucketlist.objects.all() 25 | serializer_class = BucketlistSerializer 26 | permission_classes = ( 27 | permissions.IsAuthenticated, 28 | IsOwner) 29 | 30 | 31 | class UserView(generics.ListAPIView): 32 | """View to list the user queryset.""" 33 | queryset = User.objects.all() 34 | serializer_class = UserSerializer 35 | 36 | 37 | class UserDetailsView(generics.RetrieveAPIView): 38 | """View to retrieve a user instance.""" 39 | queryset = User.objects.all() 40 | serializer_class = UserSerializer 41 | --------------------------------------------------------------------------------