├── Code ├── bookmarks │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── locations │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── 0002_like_note.py │ ├── models.py │ ├── serializers.py │ ├── templates │ │ └── locations │ │ │ └── hello.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── manage.py ├── static │ └── style.css └── templates │ └── base.html ├── LICENSE └── README.md /Code/bookmarks/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for bookmarks project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/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/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'oqqe0)ye)3su&cemkk@o2r9se1l497%2ytdz=!me5q9xm!^8u4' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'locations' 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'bookmarks.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [ 60 | os.path.join(BASE_DIR, 'templates'), 61 | ], 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'bookmarks.wsgi.application' 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.postgresql', 83 | 'NAME': 'bookmarks2', 84 | 'USER': 'bookmarksuser', 85 | 'PASSWORD': 'mypasswordhere', 86 | 'HOST': 'localhost', 87 | 'PORT': '5432', 88 | } 89 | } 90 | ''' 91 | 'default': { 92 | 'ENGINE': 'django.db.backends.sqlite3', 93 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 94 | } 95 | ''' 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/2.0/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/2.0/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/2.0/howto/static-files/ 132 | 133 | STATIC_URL = '/static/' 134 | 135 | STATICFILES_DIRS = ( 136 | os.path.join(BASE_DIR, "static"), 137 | '/static/', 138 | ) 139 | -------------------------------------------------------------------------------- /Code/bookmarks/urls.py: -------------------------------------------------------------------------------- 1 | """bookmarks URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/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: path('', 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: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('locations/', include('locations.urls')), 21 | path('admin/', admin.site.urls), 22 | ] 23 | -------------------------------------------------------------------------------- /Code/bookmarks/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for bookmarks 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/2.0/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", "bookmarks.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Code/locations/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /Code/locations/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LocationsConfig(AppConfig): 5 | name = 'locations' 6 | -------------------------------------------------------------------------------- /Code/locations/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-02-22 02:58 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | def create_bookmarks(apps, schema_editor): 8 | Bookmark = apps.get_model('locations', 'Bookmark') 9 | Comment = apps.get_model('locations', 'Comment') 10 | bookmark1 = Bookmark(link='google.com') 11 | bookmark1.save() 12 | bookmark2 = Bookmark(link='cnn.com') 13 | bookmark2.save() 14 | bookmark3 = Bookmark(link='apple.com') 15 | bookmark3.save() 16 | comment1 = Comment(text='These people make the iPhone', bookmark=bookmark3) 17 | comment1.save() 18 | 19 | class Migration(migrations.Migration): 20 | 21 | initial = True 22 | 23 | dependencies = [ 24 | ] 25 | 26 | operations = [ 27 | migrations.CreateModel( 28 | name='Bookmark', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('link', models.URLField(db_index=True, max_length=1000)), 32 | ], 33 | ), 34 | migrations.CreateModel( 35 | name='Comment', 36 | fields=[ 37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('time', models.DateTimeField(auto_now_add=True)), 39 | ('text', models.TextField()), 40 | ('bookmark', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='locations.Bookmark')), 41 | ], 42 | ), 43 | migrations.RunPython(create_bookmarks), 44 | ] 45 | -------------------------------------------------------------------------------- /Code/locations/migrations/0002_like_note.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-07-09 09:34 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('locations', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Like', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('bookmark', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='locations.Bookmark')), 19 | ('comment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='locations.Comment')), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='Note', 24 | fields=[ 25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('time', models.DateTimeField(auto_now_add=True)), 27 | ('text', models.TextField()), 28 | ('bookmark', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='locations.Bookmark')), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /Code/locations/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models import Model 3 | from django.db.models.fields import URLField, DateTimeField, TextField 4 | from django.db.models.fields.related import ForeignKey 5 | from django.db.utils import IntegrityError 6 | 7 | # Create your models here. 8 | 9 | class Bookmark(Model): 10 | link = URLField(max_length=1000, null=False, blank=False, db_index=True) 11 | 12 | def __str__(self): 13 | return str(self.link) 14 | 15 | 16 | class Comment(Model): 17 | bookmark = ForeignKey(Bookmark, on_delete=models.CASCADE, related_name='comments') 18 | time = DateTimeField(auto_now_add=True, null=False, blank=True) 19 | text = TextField() 20 | 21 | def __str__(self): 22 | return self.text 23 | 24 | def link_str(self): 25 | return 'This comment: {} is for link: {}'.format( 26 | self.text, self.bookmark.link 27 | ) 28 | 29 | class Note(Model): 30 | bookmark = ForeignKey(Bookmark, on_delete=models.CASCADE, related_name='notes') 31 | time = DateTimeField(auto_now_add=True, null=False, blank=True) 32 | text = TextField() 33 | 34 | def __str__(self): 35 | return self.text 36 | 37 | 38 | class Like(Model): 39 | bookmark = ForeignKey( 40 | Bookmark, on_delete=models.CASCADE, related_name='likes', null=True 41 | ) 42 | comment = ForeignKey( 43 | Comment, on_delete=models.CASCADE, related_name='likes', null=True 44 | ) 45 | 46 | def __str__(self): 47 | return '{} like'.format( 48 | self.bookmark.link if self.bookmark else self.comment.link 49 | ) 50 | 51 | def clean(self): 52 | super().clean(self) 53 | if self.bookmark is None: 54 | if self.comment is None: 55 | raise IntegrityError( 56 | 'A like must be made to either a bookmark or a comment' 57 | ) 58 | elif self.comment is not None: 59 | raise IntegrityError( 60 | 'A like cannot be made to both a bookmark and a comment' 61 | ) 62 | -------------------------------------------------------------------------------- /Code/locations/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import HyperlinkedModelSerializer, Serializer,\ 2 | ModelSerializer 3 | from rest_framework.fields import IntegerField, URLField 4 | from locations.models import Bookmark, Comment, Note, Like 5 | 6 | class BookmarkManualSerializer(Serializer): 7 | id = IntegerField(read_only=True) 8 | link = URLField(required=False, max_length=1000) 9 | 10 | def create(self, validated_data): 11 | """ 12 | Create and return a new `Bookmark` instance, given the validated data. 13 | """ 14 | return Bookmark.objects.create(**validated_data) 15 | 16 | def update(self, instance, validated_data): 17 | """ 18 | Update and return an existing `Snippet` instance, given the validated data. 19 | """ 20 | instance.url = validated_data.get('url', instance.url) 21 | instance.save() 22 | return instance 23 | 24 | 25 | class CommentSerializer(HyperlinkedModelSerializer): 26 | class Meta: 27 | model = Comment 28 | fields = ['url', 'id', 'bookmark', 'time', 'text'] 29 | 30 | 31 | class CommentSerializerWithLikes(HyperlinkedModelSerializer): 32 | num_likes = IntegerField(read_only=True) 33 | class Meta: 34 | model = Comment 35 | fields = ['url', 'id', 'bookmark', 'time', 'text', 'num_likes'] 36 | 37 | 38 | class NoteSerializer(HyperlinkedModelSerializer): 39 | class Meta: 40 | model = Note 41 | fields = ['url', 'id', 'bookmark', 'time', 'text'] 42 | 43 | class BookmarkSerializer(ModelSerializer): 44 | class Meta: 45 | model = Bookmark 46 | fields = ['id', 'link'] 47 | 48 | 49 | class BookmarkLinkSerializer(HyperlinkedModelSerializer): 50 | comments = CommentSerializer(many=True, read_only=True) 51 | notes = NoteSerializer(many=True, read_only=True) 52 | num_likes = IntegerField(read_only=True) 53 | 54 | class Meta: 55 | model = Bookmark 56 | fields = ['url', 'id', 'link', 'comments', 'notes', 'num_likes'] 57 | -------------------------------------------------------------------------------- /Code/locations/templates/locations/hello.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Hello {{ name }}{% endblock %} 4 | {% block header %} 5 |

Hello {{ name }} 6 | {% if name == 'Fred' %}, you're a winner!{% else %}, you have not won.{% endif %} 7 |

{% endblock %} 8 | {% block content %} 9 |

Today is the {% now "jS F Y" %}

10 |

There are {{ name|length }} characters in your name

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /Code/locations/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import SimpleTestCase, TestCase 2 | from django.test.client import RequestFactory 3 | from django.urls.base import reverse 4 | from django.test import tag 5 | from .views import TemplateHelloPerson, BookmarkViewSet 6 | from rest_framework.test import APIClient 7 | from locations.models import Bookmark, Note, Comment, Like 8 | from django.utils.six import BytesIO 9 | from rest_framework.parsers import JSONParser 10 | from django.db.models.aggregates import Count 11 | from rest_framework.test import APIRequestFactory 12 | from unittest.mock import patch, Mock 13 | from rest_framework.response import Response 14 | from rest_framework import status 15 | 16 | # Create your tests here. 17 | 18 | 19 | class ITTest_TemplateHelloPerson(SimpleTestCase): 20 | @tag('integration_test') 21 | def test_render(self): 22 | response = self.client.get( 23 | reverse('hello-view3', kwargs={'name': 'Allan'}), follow=True 24 | ) 25 | self.assertEqual(response.status_code, 200) 26 | self.assertEqual(response.resolver_match.url_name, 'hello-view3') 27 | self.assertContains(response, b'Hello Allan') 28 | self.assertContains( 29 | response, b'

There are 5 characters in your name

' 30 | ) 31 | self.assertContains( 32 | response, b'you have not won' 33 | ) 34 | 35 | 36 | class UTTest_TemplateHelloPerson(SimpleTestCase): 37 | def setUp(self): 38 | super().setUp() 39 | self.request = RequestFactory().get('/fake-path') 40 | self.view = TemplateHelloPerson() 41 | self.view = setup_view_test(self.view, self.request) 42 | 43 | @tag('unit_test') 44 | def test_class_attributes(self): 45 | self.assertEqual(self.view.template_name, 'locations/hello.html') 46 | 47 | @tag('unit_test') 48 | def test_get_context_data(self): 49 | self.view.kwargs['name'] = 'Fred' 50 | context = self.view.get_context_data() 51 | self.assertEqual(context['name'], 'Fred') 52 | 53 | 54 | def setup_view_test(view, request, *args, **kwargs): 55 | """ 56 | Mimic as_view() returned callable, but returns view instance. 57 | 58 | args and kwargs are the same you would pass to ``reverse()`` 59 | """ 60 | view.request = request 61 | view.args = args 62 | view.kwargs = kwargs 63 | return view 64 | 65 | 66 | class Test_BookmarkViewset(TestCase): 67 | def setUp(self): 68 | super().setUp() 69 | bookmark1 = Bookmark(link="http://www.google.com/") 70 | bookmark1.save() 71 | bookmark2 = Bookmark(link="http://www.cnn.com/") 72 | bookmark2.save() 73 | bookmark3 = Bookmark(link="http://www.bbc.co.uk/") 74 | bookmark3.save() 75 | note = Note(text="This is a note", bookmark=bookmark2) 76 | note.save() 77 | comment = Comment(bookmark=bookmark3, text="This is a comment") 78 | comment.save() 79 | like1 = Like(bookmark=bookmark1) 80 | like1.save() 81 | like2 = Like(comment=comment) 82 | like2.save() 83 | 84 | @tag('integration_test') 85 | def test_get(self): 86 | client = APIClient() 87 | result = client.get('/locations/bookmarks/') 88 | stream = BytesIO(result.content) 89 | data = JSONParser().parse(stream) 90 | self.assertEqual(len(data), 6) 91 | 92 | @tag('integration_test') 93 | def test_add_like(self): 94 | bookmark = Bookmark.objects.annotate(num_likes=Count('likes')).get(id=6) 95 | self.assertEqual(bookmark.num_likes, 0) 96 | client = APIClient() 97 | result = client.post('/locations/bookmarks/6/add_like/') 98 | bookmark = Bookmark.objects.annotate(num_likes=Count('likes')).get(id=6) 99 | self.assertEqual(bookmark.num_likes, 1) 100 | result = client.post('/locations/bookmarks/6/add_like/') 101 | bookmark = Bookmark.objects.annotate(num_likes=Count('likes')).get(id=6) 102 | self.assertEqual(bookmark.num_likes, 2) 103 | 104 | @tag('unit_test') 105 | @patch('locations.views.Response') 106 | @patch('locations.views.BookmarkViewSet.get_object') 107 | @patch('locations.views.Like') 108 | def test_add_like(self, l_patch, go_patch, r_patch): 109 | factory = APIRequestFactory() 110 | request = factory.post( 111 | '/locations/bookmarks/6/add_like/', {} 112 | ) 113 | view = BookmarkViewSet() 114 | result = view.add_like(request) 115 | self.assertEqual(go_patch.call_count, 1) 116 | self.assertEqual(l_patch.call_count, 1) 117 | self.assertEqual(l_patch.return_value.bookmark, go_patch.return_value) 118 | self.assertEqual(l_patch.return_value.save.call_count, 1) 119 | self.assertEqual(r_patch.call_count, 1) 120 | self.assertEqual(r_patch.call_args[0], ({'status': 'bookmark like set'},)) 121 | self.assertEqual(r_patch.call_args[1], {'status': status.HTTP_201_CREATED}) 122 | self.assertEqual(result, r_patch.return_value) 123 | -------------------------------------------------------------------------------- /Code/locations/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | from django.urls import path 3 | from locations import views 4 | from .views import SimpleHelloWorld, SimpleHelloPerson, TemplateHelloPerson,\ 5 | SimpleHelloWorldAPI 6 | from locations.views import BookmarkListView, BookmarkDetailView,\ 7 | BookmarkList, BookmarkDetail, BookmarkViewSet, CommentViewSet,\ 8 | NoteViewSet 9 | from rest_framework.urlpatterns import format_suffix_patterns 10 | from rest_framework import routers 11 | 12 | router = routers.DefaultRouter() 13 | router.register(r'bookmarks', BookmarkViewSet) 14 | router.register(r'comments', CommentViewSet) 15 | router.register(r'notes', NoteViewSet) 16 | 17 | urlpatterns = [ 18 | path('hello1/', SimpleHelloWorld.as_view(), name='hello-view1'), 19 | path('hello2//', SimpleHelloPerson.as_view(), name='hello-view2'), 20 | path('hello3//', TemplateHelloPerson.as_view(), name='hello-view3'), 21 | path('hello_api//', SimpleHelloWorldAPI.as_view(), name='hello-api'), 22 | url(r'^', include(router.urls)), 23 | ] 24 | 25 | standardview_urlpatters = [ 26 | url(r'^bookmarks_trad_view/$', BookmarkListView.as_view()), 27 | url(r'^bookmarks_trad_view/(?P[0-9]+)/$', BookmarkDetailView.as_view()), 28 | url(r'^bookmarks_class_view/$', BookmarkList.as_view()), 29 | url(r'^bookmarks_class_view/(?P[0-9]+)/$', BookmarkDetail.as_view()), 30 | url( 31 | r'^bookmarks_viewset/$', 32 | BookmarkViewSet.as_view({'get': 'list', 'post': 'create'}) 33 | ), 34 | url( 35 | r'^bookmarks_viewset/(?P[0-9]+)/$', 36 | BookmarkViewSet.as_view( 37 | { 38 | 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 39 | 'delete': 'destroy' 40 | } 41 | ) 42 | ), 43 | ] 44 | 45 | standardview_urlpatters = format_suffix_patterns(standardview_urlpatters) 46 | 47 | urlpatterns += standardview_urlpatters 48 | -------------------------------------------------------------------------------- /Code/locations/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.views.generic import View, TemplateView 3 | from django.http import HttpResponse, JsonResponse 4 | from rest_framework.views import APIView 5 | from rest_framework.generics import ListCreateAPIView,\ 6 | RetrieveUpdateDestroyAPIView 7 | from rest_framework.viewsets import ModelViewSet 8 | from rest_framework.response import Response 9 | from rest_framework import status 10 | from django.http import Http404 11 | from locations.models import Bookmark, Comment, Note, Like 12 | from locations.serializers import BookmarkSerializer, NoteSerializer,\ 13 | CommentSerializer, CommentSerializerWithLikes, BookmarkLinkSerializer 14 | from rest_framework.decorators import action 15 | from django.db.models.aggregates import Count 16 | 17 | # Create your views here. 18 | 19 | 20 | class SimpleHelloWorld(View): 21 | """ 22 | View that returns Hello World 23 | """ 24 | def get(self, request, *args, **kwargs): 25 | return HttpResponse('

Hello world

') 26 | 27 | 28 | class SimpleHelloPerson(View): 29 | """ 30 | View that returns Hello $person parameter 31 | """ 32 | def get(self, request, *args, **kwargs): 33 | return HttpResponse('

Hello {}

'.format(kwargs['name'])) 34 | 35 | 36 | class TemplateHelloPerson(TemplateView): 37 | """ 38 | View that uses template to return Hello $person parameter 39 | """ 40 | template_name = 'locations/hello.html' 41 | def get_context_data(self, **kwargs): 42 | context = super().get_context_data(**kwargs) 43 | context['name'] = self.kwargs['name'] 44 | return context 45 | 46 | 47 | class SimpleHelloWorldAPI(View): 48 | """ 49 | View that returns a name parameter in a JSON response 50 | """ 51 | def get(self, request, *args, **kwargs): 52 | if kwargs['name'].lower() != 'fred': 53 | return JsonResponse( 54 | { 55 | 'description': 'This endpoint welcomes the user', 56 | 'welcome': 'Hello {}'.format(kwargs['name']) 57 | }, 58 | status=200 59 | ) 60 | else: 61 | return JsonResponse( 62 | { 63 | 'description': 'This demonstrates an error', 64 | 'error': '{} is not an authorised user'.format( 65 | kwargs['name'] 66 | ) 67 | }, 68 | status=403 69 | ) 70 | 71 | 72 | class BookmarkListView(APIView): 73 | """ 74 | List all bookmarks, or create a new bookmark 75 | """ 76 | def get(self, request, format=None): 77 | bookmarks = Bookmark.objects.all() 78 | serializer = BookmarkSerializer(bookmarks, many=True) 79 | return Response(serializer.data) 80 | 81 | def post(self, request, format=None): 82 | serializer = BookmarkSerializer(data=request.data) 83 | if serializer.is_valid(): 84 | serializer.save() 85 | return Response(serializer.data, status=status.HTTP_201_CREATED) 86 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 87 | 88 | 89 | class BookmarkDetailView(APIView): 90 | """ 91 | Retrieve, update or delete a bookmark. 92 | """ 93 | def get_object(self, pk): 94 | try: 95 | return Bookmark.objects.get(pk=pk) 96 | except Bookmark.DoesNotExist: 97 | raise Http404 98 | 99 | def get(self, request, pk, format=None): 100 | bookmark = self.get_object(pk) 101 | serializer = BookmarkSerializer(bookmark) 102 | return Response(serializer.data) 103 | 104 | def put(self, request, pk, format=None): 105 | bookmark = self.get_object(pk) 106 | serializer = BookmarkSerializer(bookmark, data=request.data) 107 | if serializer.is_valid(): 108 | serializer.save() 109 | return Response(serializer.data) 110 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 111 | 112 | def delete(self, request, pk, format=None): 113 | bookmark = self.get_object(pk) 114 | bookmark.delete() 115 | return Response(status=status.HTTP_204_NO_CONTENT) 116 | 117 | 118 | class BookmarkList(ListCreateAPIView): 119 | queryset = Bookmark.objects.all() 120 | serializer_class = BookmarkSerializer 121 | 122 | 123 | class BookmarkDetail(RetrieveUpdateDestroyAPIView): 124 | queryset = Bookmark.objects.all() 125 | serializer_class = BookmarkSerializer 126 | 127 | 128 | class BookmarkViewSet(ModelViewSet): 129 | queryset = Bookmark.objects.all() 130 | serializer_class = BookmarkLinkSerializer 131 | 132 | def get_queryset(self): 133 | return Bookmark.objects.annotate( 134 | num_likes=Count('likes') 135 | ) 136 | 137 | @action(methods=['post'], detail=True) 138 | def add_like(self, request, pk=None): 139 | object = self.get_object() 140 | like = Like() 141 | like.bookmark = object 142 | like.save() 143 | return Response( 144 | {'status': 'bookmark like set'}, 145 | status=status.HTTP_201_CREATED 146 | ) 147 | 148 | class NoteViewSet(ModelViewSet): 149 | queryset = Note.objects.all() 150 | serializer_class = NoteSerializer 151 | 152 | 153 | class CommentViewSet(ModelViewSet): 154 | queryset = Comment.objects.all() 155 | serializer_class = CommentSerializerWithLikes 156 | 157 | def get_queryset(self): 158 | return Comment.objects.annotate( 159 | num_likes=Count('likes') 160 | ) 161 | 162 | @action(methods=['post'], detail=True) 163 | def add_like(self, request, pk=None): 164 | object = self.get_object() 165 | like = Like() 166 | like.comment = object 167 | like.save() 168 | return Response( 169 | {'status': 'comment like set'}, 170 | status=status.HTTP_201_CREATED 171 | ) 172 | -------------------------------------------------------------------------------- /Code/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", "bookmarks.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Code/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; 3 | margin: 0; 4 | } 5 | 6 | #wrapper { 7 | height: 100vh; 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | #header{ 13 | padding: 5px; 14 | height: 90px; 15 | background-color: #109010; 16 | color: #FFF; 17 | } 18 | 19 | #content { 20 | flex: 1; 21 | background-color: #FFF; 22 | padding: 5px; 23 | } 24 | -------------------------------------------------------------------------------- /Code/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Default title{% endblock %} 6 | 7 | 8 |
9 | 12 | 13 |
14 | {% block content %}{% endblock %} 15 |
16 |
17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 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 | # Building REST APIs with Python [Video] 2 | This is the code repository for [Building REST APIs with Python [Video]](https://www.packtpub.com/web-development/building-rest-apis-python-video?utm_source=github&utm_medium=repository&utm_campaign=9781788293143), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the video course from start to finish. 3 | ## About the Video Course 4 | With the growing demand for full-stack developers, it is important to learn how you can combine the powers of Python with other libraries to create amazing applications. 5 | 6 | Django has basic front-end support but we show how complex operations can be performed at the front-end. We show you how to unleash the power of Python's Rest API and other functionalities to create compelling applications powered by ReactJS. 7 | 8 | We will be using a production-level database such as Postgres. Delving into key aspects such as code reusability, deployment and maintaining your application, we discuss production server configuration and bundle technologies with Python to provide an end-to-end web development solution. 9 | 10 | 11 | 12 |

What You Will Learn

13 |
14 |
    15 |
  • Set up your own application project in Django 16 |
  • Organize your Django project's development and productions environment 17 |
  • Write Django views and use routes to handle incoming requests 18 |
  • Create your own Django templates for your Python web API and learn to use template filters and tags 19 |
  • Work with RESTful APIs in Django 20 |
  • Quickly build clean APIs with the Django REST Framework
21 | 22 | ## Instructions and Navigation 23 | ### Assumed Knowledge 24 | To fully benefit from the coverage included in this course, you will need:
25 | This course targets intermediate or expert Python developers keen to become full-stack Python developers by building fully functioning applications. Prior knowledge of HTML, CSS, and JavaScript is assumed. 26 | ### Technical Requirements 27 | This course has the following software requirements:
28 | Python 29 | Angular, web development 30 | full-stack development 31 | Postgresql 32 | MySQL 33 | Django 34 | JavaScript 35 | 36 | ## Related Products 37 | * [Mastering Unsupervised Learning with Python [Video]](https://www.packtpub.com/application-development/mastering-unsupervised-learning-python-video?utm_source=github&utm_medium=repository&utm_campaign=9781788996563) 38 | 39 | * [SEO Link Building: Rank in Google with EDU and GOV Backlinks [Video]](https://www.packtpub.com/web-development/seo-link-building-rank-google-edu-and-gov-backlinks-video?utm_source=github&utm_medium=repository&utm_campaign=9781789611892) 40 | 41 | * [Learn Design Patterns with Java [Video]](https://www.packtpub.com/application-development/learn-design-patterns-java-video?utm_source=github&utm_medium=repository&utm_campaign=9781788838795) 42 | 43 | --------------------------------------------------------------------------------