├── .gitignore ├── LICENSE ├── README.md ├── Vagrantfile ├── deploy ├── nginx_profiles_api.conf ├── setup.sh ├── supervisor_profiles_api.conf └── update.sh ├── hello_world.py ├── manage.py ├── profiles_api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_profilefeeditem.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── profiles_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python,vagrant 2 | # Edit at https://www.gitignore.io/?templates=python,vagrant 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # celery beat schedule file 90 | celerybeat-schedule 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Environments 96 | .env 97 | .venv 98 | env/ 99 | venv/ 100 | ENV/ 101 | env.bak/ 102 | venv.bak/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | .dmypy.json 117 | dmypy.json 118 | 119 | # Pyre type checker 120 | .pyre/ 121 | 122 | ### Python Patch ### 123 | .venv/ 124 | 125 | ### Vagrant ### 126 | # General 127 | .vagrant/ 128 | 129 | # Log files (if you are creating logs in debug mode, uncomment this) 130 | # *.logs 131 | 132 | ### Vagrant Patch ### 133 | *.box 134 | 135 | # End of https://www.gitignore.io/api/python,vagrant 136 | eb-package.zip 137 | static/ 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 London App Developer Ltd 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 | # Profiles REST API 2 | 3 | Profiles REST API course code. 4 | 5 | This is the full source code for our course on Udemy: [Build a Backend REST API with Python & Django - Beginner](https://londonapp.dev/c1). 6 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://vagrantcloud.com/search. 15 | config.vm.box = "ubuntu/bionic64" 16 | config.vm.box_version = "~> 20191107.0.0" 17 | 18 | config.vm.network "forwarded_port", guest: 8000, host: 8000 19 | 20 | config.vm.provision "shell", inline: <<-SHELL 21 | systemctl disable apt-daily.service 22 | systemctl disable apt-daily.timer 23 | 24 | sudo apt-get update 25 | sudo apt-get install -y python3-venv zip 26 | touch /home/vagrant/.bash_aliases 27 | if ! grep -q PYTHON_ALIAS_ADDED /home/vagrant/.bash_aliases; then 28 | echo "# PYTHON_ALIAS_ADDED" >> /home/vagrant/.bash_aliases 29 | echo "alias python='python3'" >> /home/vagrant/.bash_aliases 30 | fi 31 | SHELL 32 | end 33 | -------------------------------------------------------------------------------- /deploy/nginx_profiles_api.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | 4 | location /static { 5 | alias /usr/local/apps/profiles-rest-api/static; 6 | } 7 | 8 | location / { 9 | proxy_pass http://127.0.0.1:9000/; 10 | proxy_set_header Host $host; 11 | proxy_set_header X-Real-IP $remote_addr; 12 | proxy_set_header X-Forwarded-For $remote_addr; 13 | proxy_set_header X-Forwarded-Proto $scheme; 14 | proxy_redirect off; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /deploy/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # TODO: Set to URL of git repo. 6 | PROJECT_GIT_URL='https://github.com/LondonAppDev/profiles-rest-api.git' 7 | 8 | PROJECT_BASE_PATH='/usr/local/apps/profiles-rest-api' 9 | 10 | echo "Installing dependencies..." 11 | apt-get update 12 | apt-get install -y python3-dev python3-venv sqlite python-pip supervisor nginx git 13 | 14 | # Create project directory 15 | mkdir -p $PROJECT_BASE_PATH 16 | git clone $PROJECT_GIT_URL $PROJECT_BASE_PATH 17 | 18 | # Create virtual environment 19 | mkdir -p $PROJECT_BASE_PATH/env 20 | python3 -m venv $PROJECT_BASE_PATH/env 21 | 22 | # Install python packages 23 | $PROJECT_BASE_PATH/env/bin/pip install -r $PROJECT_BASE_PATH/requirements.txt 24 | $PROJECT_BASE_PATH/env/bin/pip install uwsgi==2.0.18 25 | 26 | # Run migrations and collectstatic 27 | cd $PROJECT_BASE_PATH 28 | $PROJECT_BASE_PATH/env/bin/python manage.py migrate 29 | $PROJECT_BASE_PATH/env/bin/python manage.py collectstatic --noinput 30 | 31 | # Configure supervisor 32 | cp $PROJECT_BASE_PATH/deploy/supervisor_profiles_api.conf /etc/supervisor/conf.d/profiles_api.conf 33 | supervisorctl reread 34 | supervisorctl update 35 | supervisorctl restart profiles_api 36 | 37 | # Configure nginx 38 | cp $PROJECT_BASE_PATH/deploy/nginx_profiles_api.conf /etc/nginx/sites-available/profiles_api.conf 39 | rm /etc/nginx/sites-enabled/default 40 | ln -s /etc/nginx/sites-available/profiles_api.conf /etc/nginx/sites-enabled/profiles_api.conf 41 | systemctl restart nginx.service 42 | 43 | echo "DONE! :)" 44 | -------------------------------------------------------------------------------- /deploy/supervisor_profiles_api.conf: -------------------------------------------------------------------------------- 1 | [program:profiles_api] 2 | environment = 3 | DEBUG=0 4 | command = /usr/local/apps/profiles-rest-api/env/bin/uwsgi --http :9000 --wsgi-file /usr/local/apps/profiles-rest-api/profiles_project/wsgi.py 5 | directory = /usr/local/apps/profiles-rest-api/ 6 | user = root 7 | autostart = true 8 | autorestart = true 9 | stdout_logfile = /var/log/supervisor/profiles_api.log 10 | stderr_logfile = /var/log/supervisor/profiles_api_err.log 11 | -------------------------------------------------------------------------------- /deploy/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | PROJECT_BASE_PATH='/usr/local/apps/profiles-rest-api' 6 | 7 | git pull 8 | $PROJECT_BASE_PATH/env/bin/python manage.py migrate 9 | $PROJECT_BASE_PATH/env/bin/python manage.py collectstatic --noinput 10 | supervisorctl restart profiles_api 11 | 12 | echo "DONE! :)" 13 | -------------------------------------------------------------------------------- /hello_world.py: -------------------------------------------------------------------------------- 1 | print('Hello World!') 2 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'profiles_project.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /profiles_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LondonAppDev/profiles-rest-api/e7c4c6c6d34c197eac12e9c4bcb71dad1ade32c1/profiles_api/__init__.py -------------------------------------------------------------------------------- /profiles_api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from profiles_api import models 4 | 5 | 6 | admin.site.register(models.UserProfile) 7 | admin.site.register(models.ProfileFeedItem) 8 | -------------------------------------------------------------------------------- /profiles_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProfilesApiConfig(AppConfig): 5 | name = 'profiles_api' 6 | -------------------------------------------------------------------------------- /profiles_api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-06 13:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ('auth', '0011_update_proxy_permissions'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='UserProfile', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('password', models.CharField(max_length=128, verbose_name='password')), 20 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 21 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 22 | ('email', models.EmailField(max_length=255, unique=True)), 23 | ('name', models.CharField(max_length=255)), 24 | ('is_active', models.BooleanField(default=True)), 25 | ('is_staff', models.BooleanField(default=False)), 26 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 27 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 28 | ], 29 | options={ 30 | 'abstract': False, 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /profiles_api/migrations/0002_profilefeeditem.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-25 19:51 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('profiles_api', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ProfileFeedItem', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('status_text', models.CharField(max_length=255)), 20 | ('created_on', models.DateTimeField(auto_now_add=True)), 21 | ('user_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /profiles_api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LondonAppDev/profiles-rest-api/e7c4c6c6d34c197eac12e9c4bcb71dad1ade32c1/profiles_api/migrations/__init__.py -------------------------------------------------------------------------------- /profiles_api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractBaseUser 3 | from django.contrib.auth.models import PermissionsMixin 4 | from django.contrib.auth.models import BaseUserManager 5 | from django.conf import settings 6 | 7 | 8 | class UserProfileManager(BaseUserManager): 9 | """Manager for user profiles""" 10 | 11 | def create_user(self, email, name, password=None): 12 | """Create a new user profile""" 13 | if not email: 14 | raise ValueError('User must have an email address') 15 | 16 | email = self.normalize_email(email) 17 | user = self.model(email=email, name=name) 18 | 19 | user.set_password(password) 20 | user.save(using=self._db) 21 | 22 | return user 23 | 24 | def create_superuser(self, email, name, password): 25 | """Create and save a new superuser with given details""" 26 | user = self.create_user(email, name, password) 27 | 28 | user.is_superuser = True 29 | user.is_staff = True 30 | user.save(using=self._db) 31 | 32 | return user 33 | 34 | 35 | class UserProfile(AbstractBaseUser, PermissionsMixin): 36 | """Database model for users in the system""" 37 | email = models.EmailField(max_length=255, unique=True) 38 | name = models.CharField(max_length=255) 39 | is_active = models.BooleanField(default=True) 40 | is_staff = models.BooleanField(default=False) 41 | 42 | objects = UserProfileManager() 43 | 44 | USERNAME_FIELD = 'email' 45 | REQUIRED_FIELDS = ['name'] 46 | 47 | def get_full_name(self): 48 | """Retrieve full name of user""" 49 | return self.name 50 | 51 | def get_short_name(self): 52 | """Retrieve shot name of user""" 53 | return self.name 54 | 55 | def __str__(self): 56 | """Return string representation of our user""" 57 | return self.email 58 | 59 | 60 | class ProfileFeedItem(models.Model): 61 | """Profile status update""" 62 | user_profile = models.ForeignKey( 63 | settings.AUTH_USER_MODEL, 64 | on_delete=models.CASCADE 65 | ) 66 | status_text = models.CharField(max_length=255) 67 | created_on = models.DateTimeField(auto_now_add=True) 68 | 69 | def __str__(self): 70 | """Return the model as a string""" 71 | return self.status_text 72 | -------------------------------------------------------------------------------- /profiles_api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class UpdateOwnProfile(permissions.BasePermission): 5 | """Allow user to edit their own profile""" 6 | 7 | def has_object_permission(self, request, view, obj): 8 | """Check user is trying to edit their own profile""" 9 | if request.method in permissions.SAFE_METHODS: 10 | return True 11 | 12 | return obj.id == request.user.id 13 | 14 | 15 | class UpdateOwnStatus(permissions.BasePermission): 16 | """Allow users to update their own status""" 17 | 18 | def has_object_permission(self, request, view, obj): 19 | """Check the user is trying to update their own status""" 20 | if request.method in permissions.SAFE_METHODS: 21 | return True 22 | 23 | return obj.user_profile.id == request.user.id 24 | -------------------------------------------------------------------------------- /profiles_api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from profiles_api import models 4 | 5 | 6 | class HelloSerializer(serializers.Serializer): 7 | """Serializes a name field for testing our APIView""" 8 | name = serializers.CharField(max_length=10) 9 | 10 | 11 | class UserProfileSerializer(serializers.ModelSerializer): 12 | """Serializes a user profile object""" 13 | 14 | class Meta: 15 | model = models.UserProfile 16 | fields = ('id', 'email', 'name', 'password') 17 | extra_kwargs = { 18 | 'password': { 19 | 'write_only': True, 20 | 'style': {'input_type': 'password'} 21 | } 22 | } 23 | 24 | def create(self, validated_data): 25 | """Create and return a new user""" 26 | user = models.UserProfile.objects.create_user( 27 | email=validated_data['email'], 28 | name=validated_data['name'], 29 | password=validated_data['password'] 30 | ) 31 | 32 | return user 33 | 34 | def update(self, instance, validated_data): 35 | """Handle updating user account""" 36 | if 'password' in validated_data: 37 | password = validated_data.pop('password') 38 | instance.set_password(password) 39 | 40 | return super().update(instance, validated_data) 41 | 42 | 43 | class ProfileFeedItemSerializer(serializers.ModelSerializer): 44 | """Serializes profile feed items""" 45 | 46 | class Meta: 47 | model = models.ProfileFeedItem 48 | fields = ('id', 'user_profile', 'status_text', 'created_on') 49 | extra_kwargs = {'user_profile': {'read_only': True}} 50 | -------------------------------------------------------------------------------- /profiles_api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /profiles_api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | from rest_framework.routers import DefaultRouter 4 | 5 | from profiles_api import views 6 | 7 | 8 | router = DefaultRouter() 9 | router.register('hello-viewset', views.HelloViewSet, base_name='hello-viewset') 10 | router.register('profile', views.UserProfileViewSet) 11 | router.register('feed', views.UserProfileFeedViewSet) 12 | 13 | urlpatterns = [ 14 | path('hello-view/', views.HelloApiView.as_view()), 15 | path('login/', views.UserLoginApiView.as_view()), 16 | path('', include(router.urls)) 17 | ] 18 | -------------------------------------------------------------------------------- /profiles_api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.views import APIView 2 | from rest_framework.response import Response 3 | from rest_framework import status 4 | from rest_framework import viewsets 5 | from rest_framework.authentication import TokenAuthentication 6 | from rest_framework import filters 7 | from rest_framework.authtoken.views import ObtainAuthToken 8 | from rest_framework.settings import api_settings 9 | from rest_framework.permissions import IsAuthenticated 10 | 11 | from profiles_api import serializers 12 | from profiles_api import models 13 | from profiles_api import permissions 14 | 15 | 16 | class HelloApiView(APIView): 17 | """Test API View""" 18 | serializer_class = serializers.HelloSerializer 19 | 20 | def get(self, request, format=None): 21 | """Returns a list of APIView features""" 22 | an_apiview = [ 23 | 'Uses HTTP methods as function (get, post, patch, put, delete)', 24 | 'Is similar to a traditional Django View', 25 | 'Gives you the most control over you application logic', 26 | 'Is mapped manually to URLs', 27 | ] 28 | 29 | return Response({'message': 'Hello!', 'an_apiview': an_apiview}) 30 | 31 | def post(self, request): 32 | """Create a hello message with our name""" 33 | serializer = self.serializer_class(data=request.data) 34 | 35 | if serializer.is_valid(): 36 | name = serializer.validated_data.get('name') 37 | message = f'Hello {name}' 38 | return Response({'message': message}) 39 | else: 40 | return Response( 41 | serializer.errors, 42 | status=status.HTTP_400_BAD_REQUEST 43 | ) 44 | 45 | def put(self, request, pk=None): 46 | """Handle updating an object""" 47 | return Response({'method': 'PUT'}) 48 | 49 | def patch(self, request, pk=None): 50 | """Handle a partial update of an object""" 51 | return Response({'method': 'PATCH'}) 52 | 53 | def delete(self, request, pk=None): 54 | """Delete an object""" 55 | return Response({'method': 'DELETE'}) 56 | 57 | 58 | class HelloViewSet(viewsets.ViewSet): 59 | """Test API ViewSet""" 60 | serializer_class = serializers.HelloSerializer 61 | 62 | def list(self, request): 63 | """Return a hello message""" 64 | a_viewset = [ 65 | 'Uses actions (list, create,retrieve, update, partial_update)', 66 | 'Automatically maps to URLs using Routers', 67 | 'Provides more functionality with less code', 68 | ] 69 | 70 | return Response({'message': 'Hello!', 'a_viewset': a_viewset}) 71 | 72 | def create(self, request): 73 | """Create a new hello message""" 74 | serializer = self.serializer_class(data=request.data) 75 | 76 | if serializer.is_valid(): 77 | name = serializer.validated_data.get('name') 78 | message = f'Hello {name}!' 79 | return Response({'message': message}) 80 | else: 81 | return Response( 82 | serializer.errors, 83 | status=status.HTTP_400_BAD_REQUEST 84 | ) 85 | 86 | def retrieve(self, request, pk=None): 87 | """Handle getting an object by its ID""" 88 | return Response({'http_method': 'GET'}) 89 | 90 | def update(self, request, pk=None): 91 | """Handle updating an object""" 92 | return Response({'http_method': 'PUT'}) 93 | 94 | def partial_update(self, request, pk=None): 95 | """Handle updating part of an object""" 96 | return Response({'http_method': 'PATCH'}) 97 | 98 | def destroy(self, request, pk=None): 99 | """Handle removing an object""" 100 | return Response({'http_method': 'DELETE'}) 101 | 102 | 103 | class UserProfileViewSet(viewsets.ModelViewSet): 104 | """Handle creating and updating profiles""" 105 | serializer_class = serializers.UserProfileSerializer 106 | queryset = models.UserProfile.objects.all() 107 | authentication_classes = (TokenAuthentication,) 108 | permission_classes = (permissions.UpdateOwnProfile,) 109 | filter_backends = (filters.SearchFilter,) 110 | search_fields = ('name', 'email',) 111 | 112 | 113 | class UserLoginApiView(ObtainAuthToken): 114 | """Handle creating user authentication tokens""" 115 | renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 116 | 117 | 118 | class UserProfileFeedViewSet(viewsets.ModelViewSet): 119 | """Handles creating, reading and updating profile feed items""" 120 | authentication_classes = (TokenAuthentication,) 121 | serializer_class = serializers.ProfileFeedItemSerializer 122 | queryset = models.ProfileFeedItem.objects.all() 123 | permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated) 124 | 125 | def perform_create(self, serializer): 126 | """Sets the user profile to the logged in user""" 127 | serializer.save(user_profile=self.request.user) 128 | -------------------------------------------------------------------------------- /profiles_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LondonAppDev/profiles-rest-api/e7c4c6c6d34c197eac12e9c4bcb71dad1ade32c1/profiles_project/__init__.py -------------------------------------------------------------------------------- /profiles_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for profiles_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/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.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'l+2fr-rwd31cqz(vct^(!r(thlu#meb5_d0i8jslx8jpn5ltk-' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = bool(int(os.environ.get('DEBUG', 1))) 27 | 28 | ALLOWED_HOSTS = [ 29 | 'ec2-18-212-138-237.compute-1.amazonaws.com', 30 | '127.0.0.1' 31 | ] 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'rest_framework', 44 | 'rest_framework.authtoken', 45 | 'profiles_api', 46 | ] 47 | 48 | MIDDLEWARE = [ 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | 58 | ROOT_URLCONF = 'profiles_project.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'profiles_project.wsgi.application' 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 81 | 82 | DATABASES = { 83 | 'default': { 84 | 'ENGINE': 'django.db.backends.sqlite3', 85 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 86 | } 87 | } 88 | 89 | 90 | # Password validation 91 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 92 | 93 | AUTH_PASSWORD_VALIDATORS = [ 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 105 | }, 106 | ] 107 | 108 | 109 | # Internationalization 110 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 111 | 112 | LANGUAGE_CODE = 'en-us' 113 | 114 | TIME_ZONE = 'UTC' 115 | 116 | USE_I18N = True 117 | 118 | USE_L10N = True 119 | 120 | USE_TZ = True 121 | 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 125 | 126 | STATIC_URL = '/static/' 127 | 128 | AUTH_USER_MODEL = 'profiles_api.UserProfile' 129 | 130 | STATIC_ROOT = 'static/' 131 | -------------------------------------------------------------------------------- /profiles_project/urls.py: -------------------------------------------------------------------------------- 1 | """profiles_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/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('admin/', admin.site.urls), 21 | path('api/', include('profiles_api.urls')) 22 | ] 23 | -------------------------------------------------------------------------------- /profiles_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for profiles_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/2.2/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', 'profiles_project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django==2.2 2 | djangorestframework==3.9.2 3 | --------------------------------------------------------------------------------