├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── README.md ├── app_dir ├── __init__.py ├── core │ ├── __init__.py │ ├── loading.py │ └── pagination.py ├── factories │ ├── __init__.py │ ├── module.py │ └── user.py ├── module │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── test │ │ ├── __init__.py │ │ └── test_module_api.py │ └── urls.py └── user │ ├── __init__.py │ ├── admin.py │ ├── api │ ├── __init__.py │ ├── serializers.py │ ├── urls.py │ └── views.py │ ├── apps.py │ ├── migrations │ └── __init__.py │ ├── models.py │ ├── test │ ├── __init__.py │ ├── test_user_api.py │ ├── test_user_model.py │ └── test_user_views.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── configurations ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── docker-compose.yml ├── manage.py └── requirements.txt /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: 4mMTkXwXXW4XMfliTZJbVT91Ub6avHuFh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Python modules 2 | *.pyo 3 | *.pyc 4 | 5 | # SQLite 6 | *.db 7 | *.s3db 8 | *.sqlite3 9 | 10 | # Emacs save files 11 | *~ 12 | \#* 13 | .\#* 14 | 15 | # Vim n co 16 | *.swp 17 | 18 | # cached files 19 | configuration/__pycache__/ 20 | app_dir/__pycache__/ 21 | app_dir/api/__pycache__/ 22 | loglib/__pycache__/ 23 | /app_dir/static/dist/* 24 | utilities/__pycache__/ 25 | */__pycache__/* 26 | 27 | 28 | log 29 | coverage/ 30 | .idea/ 31 | .idea/workspace.xml 32 | .DS_Store 33 | 34 | # Static files in Vagrant environment 35 | /static 36 | /django_extensions 37 | /debug_toolbar 38 | 39 | # ignore virtualenv 40 | .venv 41 | .elasticbeanstalk/config.yml 42 | log/* 43 | 44 | *.pyc 45 | __pycache__/ 46 | *.cpython-36.pyc 47 | .sass-cache/* 48 | .sass-cache 49 | 50 | 51 | .env 52 | update-env.py 53 | /venv 54 | 55 | 56 | *.log 57 | *.retry 58 | #Coverage 59 | .coverage 60 | 61 | #vscode ide 62 | .vscode 63 | 64 | /app_dir/*/migrations/* 65 | !/app_dir/*/migrations/__init__.py 66 | 67 | !.gitignore 68 | !.travis.yml 69 | 70 | */migrations/* 71 | !*/migrations/__init__.py 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5.2" 4 | # command to install dependencies 5 | install: 6 | - pip install -r requirements.txt 7 | - pip install coveralls 8 | # command to run tests 9 | script: 10 | - python manage.py makemigrations 11 | - python manage.py migrate 12 | - coverage run --source=app_dir manage.py test 13 | 14 | after_success: 15 | - coveralls -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | 3 | RUN mkdir /app 4 | WORKDIR /app 5 | 6 | ADD . /app 7 | 8 | RUN pip install -r requirements.txt 9 | 10 | EXPOSE 8096 11 | EXPOSE 80 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## django-rest-framework-boilerplate 2 | Simple boilerplate for django & django rest framework. 3 | 4 | [![Build Status](https://travis-ci.org/p8ul/stackoverflow-lite-client.svg?branch=develop)](https://travis-ci.org/p8ul/stackoverflow-lite-client) 5 | [![Coverage Status](https://coveralls.io/repos/github/p8ul/django-rest-framework-boilerplate/badge.svg?branch=master)](https://coveralls.io/github/p8ul/django-rest-framework-boilerplate?branch=master) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/e066442f75f4bc3f5269/maintainability)](https://codeclimate.com/github/p8ul/django-rest-framework-boilerplate/maintainability) 7 | 8 | ### Tasks list 9 | - [x] Users api CRUD endpoints 10 | - [x] DRF JWT Authentication 11 | - [x] Add docker configurations 12 | - [ ] Document folder structure 13 | - [ ] Configure Static/media & templates 14 | - [ ] Integrate material ui & react js on templates 15 | 16 | #### Jwt token endpoint 17 | Method | Endpoint | Functionanlity 18 | --- | --- | --- 19 | POST | `/api-token-auth` | Request jwt token 20 | 21 | #### User Endpoints 22 | 23 | Method | Endpoint | Functionality 24 | --- | --- | --- 25 | GET | `/api/user` | List users 26 | POST | `/api/user/create` | Creates a user 27 | GET | `/api/user/profile/{pk}` | Retrieve a user 28 | PUT | `/api/user/update/{pk}` | Edit a user 29 | DELETE | `/api/user/destroy/{pk}` | Delete a user 30 | 31 | 32 | ### Installation 33 | If you wish to run your own build, you two options 34 | 1. User Docker compose. 35 | 36 | `$ git clone https://github.com/p8ul/django-rest-framework-boilerplate` 37 | 38 | `$ cd django-rest-framework-boilerplate` 39 | `$ docker-compose up` 40 | 41 | 2. Without docker 42 | 43 | First ensure you have python globally installed in your computer. If not, you can get python [here](python.org). 44 | 45 | After doing this, confirm that you have installed virtualenv globally as well. If not, run this: 46 | 47 | $ pip install virtualenv 48 | Then, Git clone this repo to your PC 49 | 50 | $ git clone https://github.com/p8ul/django-rest-framework-boilerplate 51 | $ cd django-rest-framework-boilerplate 52 | Create a virtual environment 53 | 54 | $ virtualenv .venv && source .venv/bin/activate 55 | Install dependancies 56 | 57 | $ pip install -r requirements.txt 58 | Make migrations & migrate 59 | 60 | $ python manage.py makemigrations && python manage.py migrate 61 | Create Super user 62 | 63 | $ python manage.py createsuperuser 64 | 65 | ### Launching the app 66 | $ python manage.py runserver 67 | 68 | ### Run Tests 69 | $ python manage.py test 70 | 71 | -------------------------------------------------------------------------------- /app_dir/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/__init__.py -------------------------------------------------------------------------------- /app_dir/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/core/__init__.py -------------------------------------------------------------------------------- /app_dir/core/loading.py: -------------------------------------------------------------------------------- 1 | from django.apps import apps 2 | from django.apps.config import MODELS_MODULE_NAME 3 | from django.conf import settings 4 | from django.core.exceptions import AppRegistryNotReady 5 | from importlib import import_module 6 | 7 | 8 | def get_model(app_label, model_name): 9 | """ 10 | Fetches a Django model using the app registry. 11 | This doesn't require that an app with the given app label exists, 12 | which makes it safe to call when the registry is being populated. 13 | All other methods to access models might raise an exception about the 14 | registry not being ready yet. 15 | Raises LookupError if model isn't found. 16 | """ 17 | try: 18 | return apps.get_model(app_label, model_name) 19 | except AppRegistryNotReady: 20 | if apps.apps_ready and not apps.models_ready: 21 | # If this function is called while `apps.populate()` is 22 | # loading models, ensure that the module that defines the 23 | # target model has been imported and try looking the model up 24 | # in the app registry. This effectively emulates 25 | # `from path.to.app.models import Model` where we use 26 | # `Model = get_model('app', 'Model')` instead. 27 | app_config = apps.get_app_config(app_label) 28 | # `app_config.import_models()` cannot be used here because it 29 | # would interfere with `apps.populate()`. 30 | import_module('%s.%s' % (app_config.name, MODELS_MODULE_NAME)) 31 | # In order to account for case-insensitivity of model_name, 32 | # look up the model through a private API of the app registry. 33 | return apps.get_registered_model(app_label, model_name) 34 | else: 35 | # This must be a different case (e.g. the model really doesn't 36 | # exist). We just re-raise the exception. 37 | raise 38 | -------------------------------------------------------------------------------- /app_dir/core/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.response import Response 2 | from rest_framework.pagination import ( 3 | LimitOffsetPagination, 4 | PageNumberPagination, 5 | ) 6 | 7 | 8 | class PostLimitOffsetPagination(PageNumberPagination, LimitOffsetPagination): 9 | 10 | def get_paginated_response(self, data): 11 | return Response({ 12 | 'links': { 13 | 'next': self.get_next_link(), 14 | 'previous': self.get_previous_link() 15 | }, 16 | 'count': self.page.paginator.count, 17 | 'total_pages': self.page.paginator.num_pages, 18 | 'results': data 19 | }) 20 | -------------------------------------------------------------------------------- /app_dir/factories/__init__.py: -------------------------------------------------------------------------------- 1 | from .user import * 2 | from .module import * 3 | -------------------------------------------------------------------------------- /app_dir/factories/module.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from faker import Factory 3 | from ..core.loading import get_model 4 | 5 | faker = Factory.create() 6 | 7 | 8 | class ModuleFactory(factory.DjangoModelFactory): 9 | class Meta: 10 | model = get_model('module', 'Module') 11 | 12 | name = faker.name() 13 | description = faker.text() 14 | -------------------------------------------------------------------------------- /app_dir/factories/user.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from faker import Factory 3 | from django.contrib.auth import get_user_model 4 | 5 | faker = Factory.create() 6 | 7 | 8 | class UserFactory(factory.DjangoModelFactory): 9 | class Meta: 10 | model = get_user_model() 11 | 12 | username = faker.first_name() 13 | email = faker.email() 14 | password = faker.password() 15 | -------------------------------------------------------------------------------- /app_dir/module/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/module/__init__.py -------------------------------------------------------------------------------- /app_dir/module/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/module/api/__init__.py -------------------------------------------------------------------------------- /app_dir/module/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from ...core.loading import get_model 3 | 4 | TABLE = get_model('module', 'Module') 5 | APP = 'module_api' 6 | fields = ('name', 'description') 7 | 8 | 9 | class ModuleSerializer(serializers.ModelSerializer): 10 | update_url = serializers.HyperlinkedIdentityField(view_name=APP + ':update') 11 | delete_url = serializers.HyperlinkedIdentityField(view_name=APP + ':delete') 12 | 13 | class Meta: 14 | model = TABLE 15 | 16 | fields = fields + ('update_url', 'delete_url') 17 | 18 | def update(self, instance, validated_data): 19 | instance.name = validated_data.get('name', instance.name) 20 | instance.description = validated_data.get('description', instance.description) 21 | instance.save() 22 | 23 | return instance 24 | 25 | 26 | class ModuleCreateSerializer(serializers.ModelSerializer): 27 | class Meta: 28 | model = TABLE 29 | 30 | fields = fields 31 | 32 | def create(self, validated_data): 33 | TABLE.objects.create(**validated_data) 34 | return validated_data 35 | 36 | -------------------------------------------------------------------------------- /app_dir/module/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ( 3 | ModuleCreateAPIView, 4 | ModuleListAPIView, 5 | ModuleDeleteAPIView, 6 | ModuleDetailAPIView, 7 | ModuleUpdateAPIView 8 | ) 9 | 10 | urlpatterns = [ 11 | path('', ModuleListAPIView.as_view(), name='list'), 12 | path('create', ModuleCreateAPIView.as_view(), name='create'), 13 | path('delete//', ModuleDeleteAPIView.as_view(), name='delete'), 14 | path('detail//', ModuleDetailAPIView.as_view(), name='detail'), 15 | path('update//', ModuleUpdateAPIView.as_view(), name='update') 16 | ] 17 | -------------------------------------------------------------------------------- /app_dir/module/api/views.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Q 2 | from rest_framework.generics import ( 3 | ListAPIView, CreateAPIView, 4 | RetrieveUpdateAPIView, 5 | RetrieveAPIView, 6 | DestroyAPIView 7 | ) 8 | from rest_framework import pagination 9 | from rest_framework.permissions import ( 10 | IsAuthenticatedOrReadOnly 11 | ) 12 | from ...core.pagination import PostLimitOffsetPagination 13 | from .serializers import TABLE, ModuleSerializer, ModuleCreateSerializer 14 | 15 | 16 | class ModuleListAPIView(ListAPIView): 17 | permission_classes = [IsAuthenticatedOrReadOnly] 18 | serializer_class = ModuleSerializer 19 | pagination_class = PostLimitOffsetPagination 20 | 21 | def get_queryset(self, *args, **kwargs): 22 | queryset_list = TABLE.objects.all() 23 | 24 | page_size_key = 'page_size' 25 | page_size = self.request.GET.get(page_size_key) 26 | query = self.request.GET.get('q') 27 | pagination.PageNumberPagination.page_size = page_size if page_size else 10 28 | 29 | if query: 30 | queryset_list = queryset_list.filter( 31 | Q(name__icontains=query) | 32 | Q(description__icontains=query) 33 | ) 34 | 35 | return queryset_list.order_by('-id') 36 | 37 | 38 | class ModuleCreateAPIView(CreateAPIView): 39 | serializer_class = ModuleCreateSerializer 40 | permission_classes = [IsAuthenticatedOrReadOnly] 41 | queryset = TABLE.objects.all() 42 | 43 | 44 | class ModuleDetailAPIView(RetrieveAPIView): 45 | queryset = TABLE.objects.all() 46 | serializer_class = ModuleSerializer 47 | 48 | 49 | class ModuleDeleteAPIView(DestroyAPIView): 50 | queryset = TABLE.objects.all() 51 | permission_classes = [IsAuthenticatedOrReadOnly] 52 | serializer_class = ModuleSerializer 53 | 54 | 55 | class ModuleUpdateAPIView(RetrieveUpdateAPIView): 56 | permission_classes = [IsAuthenticatedOrReadOnly] 57 | queryset = TABLE.objects.all() 58 | serializer_class = ModuleSerializer 59 | -------------------------------------------------------------------------------- /app_dir/module/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ModuleConfig(AppConfig): 5 | name = 'module' 6 | -------------------------------------------------------------------------------- /app_dir/module/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/module/migrations/__init__.py -------------------------------------------------------------------------------- /app_dir/module/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import pgettext_lazy 3 | from django.utils.timezone import now 4 | 5 | 6 | class Module(models.Model): 7 | name = models.CharField( 8 | pgettext_lazy('Module field', 'name'), 9 | unique=True, 10 | max_length=128 11 | ) 12 | description = models.TextField( 13 | pgettext_lazy('Module Field', 'description'), 14 | blank=True, 15 | null=True 16 | ) 17 | created_at = models.DateTimeField( 18 | pgettext_lazy('Module field', 'created at'), 19 | default=now, 20 | editable=False 21 | ) 22 | updated_at = models.DateTimeField( 23 | pgettext_lazy('Module field', 'updated at'), 24 | default=now 25 | ) 26 | 27 | class Meta: 28 | app_label = 'module' 29 | 30 | def __str__(self): 31 | return self.name 32 | -------------------------------------------------------------------------------- /app_dir/module/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/module/test/__init__.py -------------------------------------------------------------------------------- /app_dir/module/test/test_module_api.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.urls import reverse 3 | from rest_framework.test import APIClient 4 | from faker import Factory 5 | from app_dir.factories import ModuleFactory, UserFactory 6 | 7 | faker = Factory.create() 8 | 9 | 10 | class ModuleApiTest(TestCase): 11 | def setUp(self): 12 | self.user = UserFactory() 13 | self.module = ModuleFactory() 14 | self.client = APIClient() 15 | self.client.force_authenticate(user=self.user) 16 | 17 | self.namespace = 'module_api' 18 | self.body = { 19 | 'name': faker.word(), 20 | 'description': faker.text() 21 | } 22 | self.create_url = reverse(self.namespace + ':create') 23 | self.list_url = reverse(self.namespace + ':list') 24 | self.update_url = reverse(self.namespace + ':update', kwargs={'pk': self.module.id}) 25 | 26 | def test_create_module_api(self): 27 | response = self.client.post(self.create_url, self.body, format='json') 28 | self.assertEqual(201, response.status_code) 29 | 30 | def test_list_modules_api_without_parameters(self): 31 | response = self.client.get(self.list_url) 32 | self.assertContains(response, self.module) 33 | 34 | def test_listing_module_api_with_parameters(self): 35 | response = self.client.get(self.list_url + '?page_size=10&q=' + self.module.name) 36 | self.assertContains(response, self.module) 37 | 38 | def test_update_module_api(self): 39 | response = self.client.put(self.update_url, self.body) 40 | self.assertEqual(200, response.status_code) 41 | -------------------------------------------------------------------------------- /app_dir/module/urls.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/module/urls.py -------------------------------------------------------------------------------- /app_dir/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/user/__init__.py -------------------------------------------------------------------------------- /app_dir/user/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app_dir/user/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/user/api/__init__.py -------------------------------------------------------------------------------- /app_dir/user/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | 7 | class UserSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = User 10 | fields = [ 11 | 'username', 12 | 'email', 13 | 'password', 14 | 'is_staff', 15 | ] 16 | extra_kwargs = {'password': {'write_only': True}} 17 | 18 | def create(self, validated_data): 19 | username = validated_data['username'] 20 | email = validated_data['email'] 21 | password = validated_data['password'] 22 | user_obj = User( 23 | username=username, 24 | email=email, 25 | is_staff=True 26 | ) 27 | user_obj.set_password(password) 28 | user_obj.save() 29 | return validated_data 30 | 31 | def update(self, instance, validated_data): 32 | instance.username = validated_data.get('username', instance.username) 33 | instance.email = validated_data.get('email', instance.email) 34 | instance.save() 35 | 36 | return instance 37 | -------------------------------------------------------------------------------- /app_dir/user/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ( 4 | UserListAPIView, UserCreateAPIView, 5 | UserDetailAPIView, UserDeleteAPIView, 6 | UpdateAPIView 7 | ) 8 | 9 | 10 | urlpatterns = [ 11 | path('', UserListAPIView.as_view(), name='user-list'), 12 | path('create', UserCreateAPIView.as_view(), name='user-creator'), 13 | path('profile//', UserDetailAPIView.as_view(), name='user-profile'), 14 | path('delete//', UserDeleteAPIView.as_view(), name='user-destroyer'), 15 | path('update//', UpdateAPIView.as_view(), name='user-updater') 16 | ] 17 | -------------------------------------------------------------------------------- /app_dir/user/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ( 2 | ListAPIView, CreateAPIView, RetrieveUpdateAPIView, 3 | RetrieveAPIView, DestroyAPIView 4 | ) 5 | from django.db.models import Q 6 | from rest_framework import pagination 7 | from rest_framework.permissions import (IsAuthenticatedOrReadOnly, IsAuthenticated) 8 | from .serializers import UserSerializer, User 9 | from ...core.pagination import PostLimitOffsetPagination 10 | 11 | 12 | class UserListAPIView(ListAPIView): 13 | permission_classes = [IsAuthenticatedOrReadOnly] 14 | serializer_class = UserSerializer 15 | pagination_class = PostLimitOffsetPagination 16 | 17 | def get_queryset(self, *args, **kwargs): 18 | queryset_list = User.objects.all() 19 | 20 | page_size = 'page_size' 21 | if self.request.GET.get(page_size): 22 | pagination.PageNumberPagination.page_size = self.request.GET.get(page_size) 23 | else: 24 | pagination.PageNumberPagination.page_size = 10 25 | query = self.request.GET.get('q') 26 | if query: 27 | queryset_list = queryset_list.filter( 28 | Q(email__icontains=query) | 29 | Q(username__icontains=query) 30 | ) 31 | 32 | return queryset_list.order_by('-id') 33 | 34 | 35 | class UserCreateAPIView(CreateAPIView): 36 | serializer_class = UserSerializer 37 | permission_classes = [IsAuthenticatedOrReadOnly] 38 | queryset = User.objects.all() 39 | 40 | 41 | class UserDetailAPIView(RetrieveAPIView): 42 | queryset = User.objects.all() 43 | serializer_class = UserSerializer 44 | 45 | 46 | class UserDeleteAPIView(DestroyAPIView): 47 | queryset = User.objects.all() 48 | permission_classes = [IsAuthenticated] 49 | serializer_class = UserSerializer 50 | 51 | 52 | class UpdateAPIView(RetrieveUpdateAPIView): 53 | permission_classes = [IsAuthenticatedOrReadOnly] 54 | queryset = User.objects.all() 55 | serializer_class = UserSerializer 56 | 57 | def perform_update(self, serializer): 58 | serializer.save(user=self.request.user) 59 | -------------------------------------------------------------------------------- /app_dir/user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | name = 'user' 6 | -------------------------------------------------------------------------------- /app_dir/user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/user/migrations/__init__.py -------------------------------------------------------------------------------- /app_dir/user/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /app_dir/user/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/app_dir/user/test/__init__.py -------------------------------------------------------------------------------- /app_dir/user/test/test_user_api.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.urls import reverse 3 | from rest_framework.test import APIClient 4 | from faker import Factory 5 | from app_dir.factories import UserFactory 6 | 7 | faker = Factory.create() 8 | 9 | 10 | class UserTest(TestCase): 11 | def setUp(self): 12 | self.user = UserFactory() 13 | self.client = APIClient() 14 | self.client.force_authenticate(user=self.user) 15 | 16 | self.namespace = 'user_api' 17 | self.body = { 18 | 'username': faker.first_name(), 19 | 'email': faker.email(), 20 | 'password': faker.password() 21 | } 22 | self.create_url = reverse(self.namespace + ':user-creator') 23 | self.list_url = reverse(self.namespace + ':user-list') 24 | self.update_url = reverse(self.namespace + ':user-updater', kwargs={'pk': self.user.id}) 25 | 26 | def test_create_user_api(self): 27 | response = self.client.post(self.create_url, self.body, format='json') 28 | self.assertEqual(201, response.status_code) 29 | 30 | def test_listing_user_api_without_parameters(self): 31 | response = self.client.get(self.list_url) 32 | self.assertContains(response, self.user) 33 | 34 | def test_listing_user_api_with_parameters(self): 35 | response = self.client.get(self.list_url + '?page_size=10&q=' + self.user.username) 36 | self.assertContains(response, self.user) 37 | 38 | def test_update_user_api(self): 39 | response = self.client.put(self.update_url, self.body, format='json') 40 | self.assertEqual(200, response.status_code) 41 | 42 | -------------------------------------------------------------------------------- /app_dir/user/test/test_user_model.py: -------------------------------------------------------------------------------- 1 | 2 | from django.test import TestCase 3 | from django.contrib.auth import get_user_model 4 | 5 | User = get_user_model() 6 | 7 | 8 | class StudentTest(TestCase): 9 | def setUp(self): 10 | self.data = { 11 | 'username': 'Peter', 12 | 'email': 'peter@example.com' 13 | } 14 | self.instance = User(**self.data) 15 | 16 | def test_model_can_create_instance(self): 17 | """ Test if the model can create an instance.""" 18 | old_count = User.objects.count() 19 | self.instance.save() 20 | new_count = User.objects.count() 21 | 22 | self.assertNotEqual(old_count, new_count) 23 | self.assertEqual(self.instance.username, self.data.get('username')) 24 | -------------------------------------------------------------------------------- /app_dir/user/test/test_user_views.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.urls import reverse 3 | from rest_framework.test import APIClient 4 | from ...factories import UserFactory 5 | 6 | 7 | class UserViewTest(TestCase): 8 | def setUp(self): 9 | self.client = APIClient() 10 | self.user = UserFactory() 11 | self.client.force_authenticate(user=self.user) 12 | 13 | self.namespace = 'user' 14 | self.url = reverse(self.namespace + ':index') 15 | 16 | def test_user_index_view(self): 17 | response = self.client.get(self.url) 18 | self.assertEqual(200, response.status_code) 19 | -------------------------------------------------------------------------------- /app_dir/user/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app_dir/user/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path('', views.index, name='index'), 7 | ] -------------------------------------------------------------------------------- /app_dir/user/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | 3 | 4 | def index(request): 5 | return HttpResponse("Hello, world. You're at the polls index.") 6 | -------------------------------------------------------------------------------- /configurations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p8ul/django-rest-framework-boilerplate/a29b6b9d08568ca9119ac79b4fa34dedd7962411/configurations/__init__.py -------------------------------------------------------------------------------- /configurations/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for configurations project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/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.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'v%^q7%_uf+k9@_6zh=z1-0&4%9vagw&yn$m_lad=&yb3(m7o)x' 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 | DEFAULT_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 | ) 41 | 42 | EXTERNAL_APPS = ( 43 | 'rest_framework', 44 | ) 45 | 46 | LOCAL_APPS = ( 47 | 'app_dir.user', 48 | 'app_dir.module', 49 | ) 50 | 51 | INSTALLED_APPS = DEFAULT_APPS + EXTERNAL_APPS + LOCAL_APPS 52 | 53 | MIDDLEWARE = [ 54 | 'django.middleware.security.SecurityMiddleware', 55 | 'django.contrib.sessions.middleware.SessionMiddleware', 56 | 'django.middleware.common.CommonMiddleware', 57 | 'django.middleware.csrf.CsrfViewMiddleware', 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'django.contrib.messages.middleware.MessageMiddleware', 60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 61 | ] 62 | 63 | ROOT_URLCONF = 'configurations.urls' 64 | 65 | TEMPLATES = [ 66 | { 67 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 68 | 'DIRS': [], 69 | 'APP_DIRS': True, 70 | 'OPTIONS': { 71 | 'context_processors': [ 72 | 'django.template.context_processors.debug', 73 | 'django.template.context_processors.request', 74 | 'django.contrib.auth.context_processors.auth', 75 | 'django.contrib.messages.context_processors.messages', 76 | ], 77 | }, 78 | }, 79 | ] 80 | 81 | WSGI_APPLICATION = 'configurations.wsgi.application' 82 | 83 | 84 | # Database 85 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 86 | 87 | DATABASES = { 88 | 'default': { 89 | 'ENGINE': 'django.db.backends.sqlite3', 90 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 91 | } 92 | } 93 | 94 | 95 | # Password validation 96 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 97 | 98 | AUTH_PASSWORD_VALIDATORS = [ 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 110 | }, 111 | ] 112 | 113 | 114 | # Internationalization 115 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 116 | 117 | LANGUAGE_CODE = 'en-us' 118 | 119 | TIME_ZONE = 'UTC' 120 | 121 | USE_I18N = True 122 | 123 | USE_L10N = True 124 | 125 | USE_TZ = True 126 | 127 | 128 | # Static files (CSS, JavaScript, Images) 129 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 130 | 131 | STATIC_URL = '/static/' 132 | 133 | REST_FRAMEWORK = { 134 | 'DEFAULT_PERMISSION_CLASSES': ( 135 | 'rest_framework.permissions.IsAuthenticated', 136 | ), 137 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 138 | 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 139 | 'rest_framework.authentication.SessionAuthentication', 140 | 'rest_framework.authentication.BasicAuthentication', 141 | ), 142 | } 143 | 144 | -------------------------------------------------------------------------------- /configurations/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from rest_framework_jwt.views import obtain_jwt_token 4 | 5 | urlpatterns = [ 6 | path('admin/', admin.site.urls), 7 | path('api-auth/', include('rest_framework.urls', namespace='rest')), 8 | path('api-token-auth', obtain_jwt_token), 9 | path('user/', include(('app_dir.user.urls', 'user'), namespace='user')), 10 | path('api/user/', include(('app_dir.user.api.urls', 'user_api'), namespace='user_api')), 11 | path('api/module/', include(('app_dir.module.api.urls', 'module_api'), namespace='module_api')) 12 | ] 13 | 14 | -------------------------------------------------------------------------------- /configurations/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for configurations 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.1/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', 'configurations.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | image: postgres 5 | web: 6 | build: . 7 | volumes: 8 | - .:/app 9 | links: 10 | - redis 11 | command: python manage.py runserver 0.0.0.0:5000 12 | ports: 13 | - "5000:5000" 14 | depends_on: 15 | - db 16 | redis: 17 | image: redis 18 | volumes: 19 | logvolume01: {} -------------------------------------------------------------------------------- /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', 'configurations.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 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2018.8.24 2 | chardet==3.0.4 3 | coverage==4.5.1 4 | coveralls==1.5.1 5 | Django==2.2.10 6 | django-cors-middleware==1.2.0 7 | django-extensions==1.7.1 8 | django-filter==2.0.0 9 | djangorestframework==3.9.1 10 | djangorestframework-jwt==1.11.0 11 | docopt==0.6.2 12 | factory-boy==2.11.1 13 | Faker==0.9.2 14 | idna==2.7 15 | Markdown==3.0.1 16 | PyJWT==1.6.4 17 | python-dateutil==2.7.3 18 | pytz==2018.5 19 | requests==2.20.0 20 | six==1.10.0 21 | text-unidecode==1.2 22 | urllib3==1.24.2 23 | --------------------------------------------------------------------------------