├── .env ├── .gitignore ├── .pylintrc ├── README.md ├── bookme ├── bookme │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── bookmeapi │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── documents.py │ ├── helpers.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── schema.py │ ├── search.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── gunicorn.conf ├── manage.py ├── requirements.txt └── tests │ ├── __init__.py │ └── test_script.py ├── logstash.conf └── start_app.sh /.env: -------------------------------------------------------------------------------- 1 | SECRET_KEY=pjza77vfd@pb-3rx$1-o+@yfkp9(@lqp65nlbvkv##msca#u54 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | bower_component/ 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | # 46 | venv 47 | *.pyc 48 | staticfiles 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Mac OS Desktop Store 55 | */.DS_Store 56 | .DS_Store 57 | 58 | # environment files 59 | .env.* 60 | 61 | # Django stuff: 62 | *.log 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | benv 71 | .vscode -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/.pylintrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Project built to demonstrate the power of Python/Django used with various technology. 2 | 3 | Django was invented to meet fast-moving newsroom deadlines, while satisfying the tough requirements of experienced Web developers. [reference](https://www.djangoproject.com/start/overview/) 4 | 5 | This repository should be used as a reference/guide to understand the tutorials I have written to explore several technology and how there can be used with Python/Django 6 | 7 | - Using Django with Elasticsearch, Logstash, and Kibana (ELK Stack)- [Link to tutorial](https://www.codementor.io/samueljames/using-django-with-elasticsearch-logstash-and-kibana-elk-stack-9l4fwx138) 8 | 9 | - Mutation and Query In GraphQL Using Python/Django PART 1. - [Link to tutorial](https://medium.com/@jamesvaresamuel/mutation-and-query-in-graphql-using-python-django-part-1-5bd4bce2b2a3) 10 | 11 | - Mutation and Query In GraphQL Using Python/Django PART 2. - [Link to tutorial](https://medium.com/@jamesvaresamuel/mutation-and-query-in-graphql-using-python-django-part-2-79d9852a1092) 12 | 13 | 14 | -------------------------------------------------------------------------------- /bookme/bookme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/bookme/bookme/__init__.py -------------------------------------------------------------------------------- /bookme/bookme/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for bookme project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'pjza77vfd@pb-3rx$1-o+@yfkp9(@lqp65nlbvkv##msca#u54' 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 | 'bookmeapi.apps.BookmeapiConfig', 41 | 'rest_framework', 42 | 'graphene_django', 43 | 'django_elasticsearch_dsl', 44 | ] 45 | 46 | GRAPHENE = { 47 | 'SCHEMA': 'bookmeapi.schema.schema', # Where your Graphene schema lives 48 | 'MIDDLEWARE': ( 49 | 'graphene_django.debug.DjangoDebugMiddleware', 50 | ) 51 | } 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 = 'bookme.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 | ELASTICSEARCH_DSL={ 82 | 'default': { 83 | 'hosts': 'localhost:9200' 84 | }, 85 | } 86 | 87 | LOGGING = { 88 | 'version': 1, 89 | 'disable_existing_loggers': False, 90 | 'formatters': { 91 | 'simple': { 92 | 'format': '%(levelname)s %(message)s' 93 | }, 94 | }, 95 | 'handlers': { 96 | 'console': { 97 | 'level': 'INFO', 98 | 'class': 'logging.StreamHandler', 99 | 'formatter': 'simple' 100 | }, 101 | 'logstash': { 102 | 'level': 'DEBUG', 103 | 'class': 'logstash.TCPLogstashHandler', 104 | 'host': 'localhost', 105 | 'port': 5959, # Default value: 5959 106 | 'version': 1, # Version of logstash event schema. Default value: 0 (for backward compatibility of the library) 107 | 'message_type': 'django', # 'type' field in logstash message. Default value: 'logstash'. 108 | 'fqdn': False, # Fully qualified domain name. Default value: false. 109 | 'tags': ['django.request'], # list of tags. Default: None. 110 | }, 111 | }, 112 | 'loggers': { 113 | 'django.request': { 114 | 'handlers': ['logstash'], 115 | 'level': 'DEBUG', 116 | 'propagate': True, 117 | }, 118 | 'django': { 119 | 'handlers': ['console'], 120 | 'propagate': True, 121 | }, 122 | } 123 | } 124 | 125 | WSGI_APPLICATION = 'bookme.wsgi.application' 126 | 127 | 128 | # Database 129 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 130 | 131 | DATABASES = { 132 | 'default': { 133 | 'ENGINE': 'django.db.backends.sqlite3', 134 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 135 | } 136 | } 137 | 138 | 139 | # Password validation 140 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 141 | 142 | AUTH_PASSWORD_VALIDATORS = [ 143 | { 144 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 145 | }, 146 | { 147 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 148 | }, 149 | { 150 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 151 | }, 152 | { 153 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 154 | }, 155 | ] 156 | 157 | 158 | # Internationalization 159 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 160 | 161 | LANGUAGE_CODE = 'en-us' 162 | 163 | TIME_ZONE = 'UTC' 164 | 165 | USE_I18N = True 166 | 167 | USE_L10N = True 168 | 169 | USE_TZ = True 170 | 171 | 172 | # Static files (CSS, JavaScript, Images) 173 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 174 | 175 | STATIC_URL = '/static/' 176 | -------------------------------------------------------------------------------- /bookme/bookme/urls.py: -------------------------------------------------------------------------------- 1 | """bookme URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | 19 | from graphene_django.views import GraphQLView 20 | 21 | 22 | urlpatterns = [ 23 | url(r'^admin/', admin.site.urls), 24 | url(r'^api/v1/', include('bookmeapi.urls')), 25 | url(r'^graphql', GraphQLView.as_view(graphiql=True)), 26 | 27 | ] 28 | -------------------------------------------------------------------------------- /bookme/bookme/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for bookme project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/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", "bookme.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /bookme/bookmeapi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/bookme/bookmeapi/__init__.py -------------------------------------------------------------------------------- /bookme/bookmeapi/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | 6 | from .models import Book, Issue 7 | 8 | # Register your models here. 9 | admin.site.register(Book) 10 | admin.site.register(Issue) -------------------------------------------------------------------------------- /bookme/bookmeapi/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class BookmeapiConfig(AppConfig): 8 | name = 'bookmeapi' 9 | -------------------------------------------------------------------------------- /bookme/bookmeapi/documents.py: -------------------------------------------------------------------------------- 1 | # http://elasticsearch-dsl.readthedocs.io/en/stable/search_dsl.html 2 | # https://github.com/sabricot/django-elasticsearch-dsl 3 | # https://github.com/elastic/elasticsearch-dsl-py/blob/master/docs/search_dsl.rst 4 | 5 | from elasticsearch_dsl.connections import connections 6 | from django_elasticsearch_dsl import DocType, Index 7 | from elasticsearch import Elasticsearch 8 | from elasticsearch_dsl import Search 9 | 10 | client = Elasticsearch() 11 | 12 | my_search = Search(using=client) 13 | 14 | from .models import Book 15 | 16 | # Create a connection to ElasticSearch 17 | connections.create_connection() 18 | 19 | book = Index('books') 20 | 21 | book.settings( 22 | number_of_shards=1, 23 | number_of_replicas=0 24 | ) 25 | 26 | 27 | @book.doc_type 28 | class BookDocument(DocType): 29 | 30 | class Meta: 31 | model = Book 32 | fields = ['title', 'isbn', 'category'] 33 | 34 | 35 | # define simple search here 36 | # Simple search function 37 | def search(title): 38 | query = my_search.query("match", title=title) 39 | response = query.execute() 40 | return response 41 | -------------------------------------------------------------------------------- /bookme/bookmeapi/helpers.py: -------------------------------------------------------------------------------- 1 | """Script defined to create helper functions for graphql schema.""" 2 | 3 | from graphql_relay.node.node import from_global_id 4 | 5 | def get_object(object_name, relayId, otherwise=None): 6 | try: 7 | return object_name.objects.get(pk=from_global_id(relayId)[1]) 8 | except: 9 | return otherwise 10 | 11 | def update_create_instance(instance, args, exception=['id']): 12 | if instance: 13 | [setattr(instance, key, value) for key, value in args.items() if key not in exception] 14 | 15 | 16 | # caution if you literally cloned this project, then be sure to have 17 | # elasticsearch running as every saved instance must go through 18 | # elasticsearch according to the way this project is configured. 19 | instance.save() 20 | 21 | return instance 22 | 23 | def get_errors(e): 24 | # transform django errors to redux errors 25 | # django: {"key1": [value1], {"key2": [value2]}} 26 | # redux: ["key1", "value1", "key2", "value2"] 27 | fields = e.message_dict.keys() 28 | messages = ['; '.join(m) for m in e.message_dict.values()] 29 | errors = [i for pair in zip(fields, messages) for i in pair] 30 | return errors 31 | -------------------------------------------------------------------------------- /bookme/bookmeapi/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-06-15 05:09 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Book', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('created_at', models.DateTimeField(auto_now_add=True)), 24 | ('updated_at', models.DateTimeField(auto_now=True)), 25 | ('title', models.CharField(max_length=100)), 26 | ('isbn', models.CharField(max_length=100)), 27 | ('category', models.CharField(max_length=100)), 28 | ], 29 | options={ 30 | 'abstract': False, 31 | }, 32 | ), 33 | migrations.CreateModel( 34 | name='Issue', 35 | fields=[ 36 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 37 | ('created_at', models.DateTimeField(auto_now_add=True)), 38 | ('updated_at', models.DateTimeField(auto_now=True)), 39 | ('approved', models.BooleanField(default=False)), 40 | ('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bookissue', to='bookmeapi.Book')), 41 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 42 | ], 43 | options={ 44 | 'abstract': False, 45 | }, 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /bookme/bookmeapi/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/bookme/bookmeapi/migrations/__init__.py -------------------------------------------------------------------------------- /bookme/bookmeapi/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | # Create your models here. 4 | from django.db import models 5 | from django.contrib.auth.models import User 6 | 7 | 8 | class TimeStamp(models.Model): 9 | """Base class containing all models common information.""" 10 | 11 | created_at = models.DateTimeField(auto_now_add=True) 12 | updated_at = models.DateTimeField(auto_now=True) 13 | 14 | class Meta: 15 | """Define Model as abstract.""" 16 | abstract = True 17 | 18 | 19 | class Book(TimeStamp): 20 | """Book model defined here""" 21 | title = models.CharField(max_length=100) 22 | isbn = models.CharField(max_length=100) 23 | category = models.CharField(max_length=100) 24 | 25 | def __unicode__(self): 26 | return "Book Title: {}" .format(self.title) 27 | 28 | 29 | class Issue(TimeStamp): 30 | book = models.ForeignKey(Book, related_name='bookissue', on_delete=models.CASCADE) 31 | user = models.ForeignKey(User) 32 | approved = models.BooleanField(default=False) 33 | 34 | def __unicode__(self): 35 | return "Issue for Book: {}" .format(self.book.title) 36 | -------------------------------------------------------------------------------- /bookme/bookmeapi/schema.py: -------------------------------------------------------------------------------- 1 | # source: https://github.com/graphql-python/graphene-django/ 2 | # https://facebook.github.io/relay/graphql/mutations.htm 3 | # https://facebook.github.io/relay/graphql/mutations.htm 4 | # http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/ 5 | 6 | import graphene 7 | 8 | from graphene import relay, ObjectType, InputObjectType 9 | from graphene_django import DjangoObjectType 10 | from graphene_django.filter import DjangoFilterConnectionField 11 | 12 | from django.core.exceptions import ValidationError 13 | from django.contrib.auth.models import User 14 | 15 | from bookmeapi.models import Book, Issue 16 | from bookmeapi.helpers import get_object, get_errors, update_create_instance 17 | 18 | 19 | class UserCreateInput(InputObjectType): 20 | username = graphene.String(required=True) 21 | first_name = graphene.String(required=False) 22 | last_name = graphene.String(required=False) 23 | email = graphene.String(required=True) 24 | is_staff = graphene.Boolean(required=False) 25 | is_active = graphene.Boolean(required=False) 26 | password = graphene.String(required=True) 27 | 28 | class BookCreateInput(InputObjectType): 29 | """ 30 | Class created to accept input data 31 | from the interactive graphql console. 32 | """ 33 | 34 | title = graphene.String(required=False) 35 | isbn = graphene.String(required=False) 36 | category = graphene.String(required=False) 37 | 38 | 39 | class UserNode(DjangoObjectType): 40 | class Meta: 41 | model = User 42 | # Allow for some more advanced filtering here 43 | filter_fields = { 44 | 'first_name': ['exact', 'icontains', 'istartswith'], 45 | 'last_name': ['exact', 'icontains', 'istartswith'], 46 | 'username': ['exact'], 47 | } 48 | interfaces = (relay.Node, ) 49 | 50 | 51 | class BookNode(DjangoObjectType): 52 | class Meta: 53 | model = Book 54 | filter_fields = { 55 | 'title': ['exact','istartswith'], 56 | 'isbn':['exact'], 57 | 'category':['exact', 'icontains','istartswith'], 58 | } 59 | interfaces = (relay.Node, ) 60 | 61 | class CreateUser(relay.ClientIDMutation): 62 | 63 | class Input: 64 | user = graphene.Argument(UserCreateInput) 65 | 66 | new_user = graphene.Field(UserNode) 67 | 68 | @classmethod 69 | def mutate_and_get_payload(cls, args, context, info): 70 | 71 | user_data = args.get('user') 72 | # unpack the dict item into the model instance 73 | new_user = User.objects.create(**user_data) 74 | new_user.set_password(user_data.get('password')) 75 | new_user.save() 76 | 77 | return cls(new_user=new_user) 78 | 79 | 80 | class CreateBook(relay.ClientIDMutation): 81 | 82 | class Input: 83 | # BookCreateInput class used as argument here. 84 | book = graphene.Argument(BookCreateInput) 85 | 86 | new_book = graphene.Field(BookNode) 87 | 88 | @classmethod 89 | def mutate_and_get_payload(cls, args, context, info): 90 | 91 | book_data = args.get('book') # get the book input from the args 92 | book = Book() # get an instance of the book model here 93 | new_book = update_create_instance(book, book_data) # use custom function to create book 94 | 95 | return cls(new_book=new_book) # newly created book instance returned. 96 | 97 | 98 | class UpdateBook(relay.ClientIDMutation): 99 | 100 | class Input: 101 | book = graphene.Argument(BookCreateInput) # get the book input from the args 102 | id = graphene.String(required=True) # get the book id 103 | 104 | errors = graphene.List(graphene.String) 105 | updated_book = graphene.Field(BookNode) 106 | 107 | @classmethod 108 | def mutate_and_get_payload(cls, args, context, info): 109 | 110 | try: 111 | book_instance = get_object(Book, args['id']) # get book by id 112 | if book_instance: 113 | # modify and update book model instance 114 | book_data = args.get('book') 115 | updated_book = update_create_instance(book_instance, book_data) 116 | return cls(updated_book=updated_book) 117 | except ValidationError as e: 118 | # return an error if something wrong happens 119 | return cls(updated_book=None, errors=get_errors(e)) 120 | 121 | 122 | class Query(ObjectType): 123 | users = relay.Node.Field(UserNode) # get user by id or by field name 124 | all_users = DjangoFilterConnectionField(UserNode) # get all users 125 | books = relay.Node.Field(BookNode) 126 | all_books = DjangoFilterConnectionField(BookNode) 127 | 128 | def resolve_users(self): 129 | return User.objects.all() 130 | 131 | def resolve_books(self): 132 | return Book.objects.all() 133 | 134 | 135 | class Mutation(ObjectType): 136 | create_user = CreateUser.Field() 137 | create_book = CreateBook.Field() 138 | update_book = UpdateBook.Field() 139 | 140 | 141 | schema = graphene.Schema( 142 | query=Query, 143 | mutation=Mutation, 144 | ) 145 | -------------------------------------------------------------------------------- /bookme/bookmeapi/search.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/bookme/bookmeapi/search.py -------------------------------------------------------------------------------- /bookme/bookmeapi/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | 3 | from rest_framework import serializers 4 | 5 | from .models import Book, Issue 6 | 7 | class IssueSerializer(serializers.ModelSerializer): 8 | 9 | book = serializers.HyperlinkedRelatedField(view_name='book-detail', queryset=Book.objects.all()) 10 | user = serializers.HyperlinkedRelatedField(view_name='user-detail', queryset=User.objects.all()) 11 | 12 | class Meta: 13 | model = Issue 14 | fields = ('book', 'user', 'approved') 15 | 16 | class BookSerializer(serializers.ModelSerializer): 17 | 18 | bookissue = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='book-detail') 19 | 20 | class Meta: 21 | model = Book 22 | fields = ('title', 'isbn', 'category', 'bookissue') 23 | 24 | class UserSerializer(serializers.ModelSerializer): 25 | class Meta: 26 | model = User 27 | fields = ('email', 'first_name', 'last_name', 'username') 28 | -------------------------------------------------------------------------------- /bookme/bookmeapi/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | from bookmeapi import views 3 | 4 | urlpatterns = [ 5 | url(r'books/$', views.BookListCreateView.as_view(), name='apibooks'), 6 | url(r'issues/$', views.IssueListCreateView.as_view(), name='api_issues'), 7 | url(r'users/$', views.UserListCreateView.as_view(), name='api_users'), 8 | url(r'issue-update/(?P[0-9]+)/$', views.IssueUpdateView.as_view(), 9 | name='issue-update'), 10 | url(r'bookdetail/(?P[0-9]+)/$', views.BookDetail.as_view(), 11 | name='book-detail'), 12 | url(r'userdetail/(?P[0-9]+)/$', views.UserDetail.as_view(), 13 | name='user-detail'), 14 | ] -------------------------------------------------------------------------------- /bookme/bookmeapi/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from rest_framework import status 5 | from rest_framework.response import Response 6 | from rest_framework.generics import ListCreateAPIView, UpdateAPIView, GenericAPIView 7 | from rest_framework.decorators import permission_classes 8 | from rest_framework.permissions import IsAuthenticated, IsAdminUser 9 | 10 | from django.shortcuts import render 11 | from django.contrib.auth.models import User 12 | 13 | from .models import Book, Issue 14 | from .serializers import IssueSerializer, BookSerializer, UserSerializer 15 | 16 | class BookListCreateView(ListCreateAPIView): 17 | model = Book 18 | queryset = Book.objects.all() 19 | serializer_class = BookSerializer 20 | 21 | @permission_classes((IsAdminUser,)) 22 | def create(self, request, *args, **kwargs): 23 | return super(BookListCreateView, self).create(request, *args, **kwargs) 24 | 25 | @permission_classes((IsAuthenticated,)) 26 | def list(self, request, *args, **kwargs): 27 | return super(BookListCreateView, self).list(request, *args, **kwargs) 28 | 29 | 30 | class BookDetail(GenericAPIView): 31 | model = Book 32 | serializer_class = BookSerializer() 33 | queryset = Book.objects.all() 34 | 35 | def get(self, request, pk): 36 | book = self.get_object() 37 | serializer = BookSerializer(book, context={'request': request}) 38 | return Response(serializer.data) 39 | 40 | 41 | class UserDetail(GenericAPIView): 42 | model = User 43 | serializer_class = UserSerializer 44 | queryset = User.objects.all() 45 | 46 | def get(self, request, pk): 47 | id_user = self.get_object() 48 | serializer = UserSerializer(id_user, context={'request': request}) 49 | return Response(serializer.data) 50 | 51 | 52 | class IssueListCreateView(ListCreateAPIView): 53 | model = Issue 54 | queryset = Issue.objects.all() 55 | serializer_class = IssueSerializer 56 | permission_classes = (IsAuthenticated,) 57 | 58 | 59 | class IssueUpdateView(UpdateAPIView): 60 | model = Issue 61 | queryset = Issue.objects.all() 62 | serializer_class = IssueSerializer 63 | permission_classes = (IsAdminUser,) 64 | 65 | 66 | class UserListCreateView(ListCreateAPIView): 67 | model = User 68 | queryset = User.objects.all() 69 | serializer_class = UserSerializer 70 | -------------------------------------------------------------------------------- /bookme/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/bookme/db.sqlite3 -------------------------------------------------------------------------------- /bookme/gunicorn.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root, logstash.error, logstash.access 3 | 4 | [handlers] 5 | keys=console , logstash 6 | 7 | [formatters] 8 | keys=generic, access, json 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=console 13 | 14 | [logger_logstash.error] 15 | level=INFO 16 | handlers=logstash 17 | propagate=1 18 | qualname=gunicorn.error 19 | 20 | [logger_logstash.access] 21 | level=INFO 22 | handlers=logstash 23 | propagate=0 24 | qualname=gunicorn.access 25 | 26 | [handler_console] 27 | class=StreamHandler 28 | formatter=generic 29 | args=(sys.stdout, ) 30 | 31 | [handler_logstash] 32 | class=logstash.TCPLogstashHandler 33 | formatter=json 34 | args=('localhost',5959) 35 | 36 | [formatter_generic] 37 | format=%(asctime)s [%(process)d] [%(levelname)s] %(message)s 38 | datefmt=%Y-%m-%d %H:%M:%S 39 | class=logging.Formatter 40 | 41 | [formatter_access] 42 | format=%(message)s 43 | class=logging.Formatter 44 | 45 | [formatter_json] 46 | class=pythonjsonlogger.jsonlogger.JsonFormatter 47 | -------------------------------------------------------------------------------- /bookme/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", "bookme.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /bookme/requirements.txt: -------------------------------------------------------------------------------- 1 | coverage==4.4.1 2 | Django==1.11.2 3 | django-elasticsearch-dsl==0.1.0 4 | django-filter==1.0.4 5 | djangorestframework==3.6.3 6 | elasticsearch==5.4.0 7 | elasticsearch-dsl==5.3.0 8 | graphene==1.4 9 | graphene-django==1.3 10 | graphql-core==1.1 11 | graphql-relay==0.4.5 12 | gunicorn==19.7.1 13 | iso8601==0.1.11 14 | Markdown==2.6.8 15 | promise==2.0.2 16 | python-dateutil==2.6.0 17 | python-json-logger==0.1.7 18 | python-logstash==0.4.6 19 | pytz==2017.2 20 | singledispatch==3.4.0.3 21 | six==1.10.0 22 | typing==3.6.1 23 | urllib3==1.21.1 24 | -------------------------------------------------------------------------------- /bookme/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andela-sjames/Bookmeapi/a1bed9308202ac74dd94b46bfc3fc196a201395f/bookme/tests/__init__.py -------------------------------------------------------------------------------- /bookme/tests/test_script.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from rest_framework.test import APITestCase 5 | from rest_framework import status 6 | 7 | from django.contrib.auth.models import User 8 | from django.core.urlresolvers import reverse 9 | 10 | from bookmeapi.models import Book, Issue 11 | 12 | 13 | class setUpModelInstanceTestCase(APITestCase): 14 | 15 | def setUp(self): 16 | self.admin = User.objects.create( 17 | email='admin_user@test.com', first_name='User', 18 | last_name='Admin', password='pythonistainaction', 19 | username='user_admin', is_superuser=True) 20 | 21 | self.authUser = User.objects.create( 22 | email='auth_user@test.com', first_name='User', 23 | last_name='Auth', password='pythonistainaction', 24 | username='user_auth', is_staff=False) 25 | 26 | self.book_1 = Book.objects.create( 27 | title='King Author', 28 | isbn='1234hjy', 29 | category='History' 30 | ) 31 | 32 | self.book_2 = Book.objects.create( 33 | title='Desert Knights', 34 | isbn='1we232d', 35 | category='Action' 36 | ) 37 | 38 | self.issue_1 = Issue.objects.create( 39 | book = self.book_1, 40 | user = self.authUser, 41 | approved = False 42 | ) 43 | 44 | self.issue_2 = Issue.objects.create( 45 | book = self.book_2, 46 | user = self.admin, 47 | approved = False 48 | ) 49 | 50 | super(setUpModelInstanceTestCase, self).setUp() 51 | 52 | def test_user_can_be_retrieved(self): 53 | 54 | url = reverse('api_users') 55 | response = self.client.get(url) 56 | self.assertEqual(response.status_code, status.HTTP_200_OK) 57 | 58 | data = response.data 59 | self.assertIsInstance(data, list) 60 | 61 | 62 | class BookTestCase(setUpModelInstanceTestCase): 63 | 64 | def test_book_can_be_created(self): 65 | 66 | data = { 67 | 'title':'Desert KINGS', 68 | 'isbn':'JKEXPRESS', 69 | 'category':'Action' 70 | } 71 | url = reverse('apibooks') 72 | self.client.force_authenticate(self.admin) 73 | response = self.client.post(url, data) 74 | 75 | self.assertTrue(status.is_success(response.status_code)) 76 | self.assertEqual(response.status_code, status.HTTP_201_CREATED) 77 | 78 | def test_book_can_be_retrieved_by_auth_user(self): 79 | 80 | url = reverse('apibooks') 81 | self.client.force_authenticate(self.authUser) 82 | response = self.client.get(url) 83 | 84 | self.assertEqual(response.status_code, status.HTTP_200_OK) 85 | 86 | data = response.data 87 | self.assertIsInstance(data, list) 88 | 89 | def test_book_detail_can_be_viewed_by_auth_user(self): 90 | url = reverse('book-detail', kwargs={'pk': 2}) 91 | self.client.force_authenticate(self.authUser) 92 | response = self.client.get(url) 93 | 94 | self.assertEqual(response.status_code, status.HTTP_200_OK) 95 | 96 | 97 | class IssueTestCase(setUpModelInstanceTestCase): 98 | 99 | def test_issues_can_retrieved_by_auth_user(self): 100 | 101 | url = reverse('api_issues') 102 | self.client.force_authenticate(self.authUser) 103 | response = self.client.get(url) 104 | 105 | self.assertEqual(response.status_code, status.HTTP_200_OK) 106 | -------------------------------------------------------------------------------- /logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | port => 5959 4 | codec => json 5 | } 6 | } 7 | output { 8 | elasticsearch { 9 | hosts => ["localhost:9200"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /start_app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | --------------------------------------------------------------------------------