Skip to content

Commit

Permalink
Merge pull request #1 from andela-cmutembei/develop
Browse files Browse the repository at this point in the history
Finishes checkpoint III
  • Loading branch information
collinmutembei committed Feb 22, 2016
2 parents 52f6c88 + 2b92914 commit e8bc5a7
Show file tree
Hide file tree
Showing 58 changed files with 2,818 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

[report]
omit =
*virtualenv*
*envs*
*api/migrations/*
*/python?.?/*
*__init__*
*/tests/*
*manage.py
*settings.py*
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ docs/_build/

# PyBuilder
target/

# coveralls local config
.coveralls.yml
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
language: python
python:
- "2.7"
- "3.5"
install: pip install -r requirements.txt
env:
global:
- TRAVIS_BUILD=true
script:
- coverage run --append manage.py test
branches:
only:
- master
- develop
after_success:
- coveralls
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn blst.wsgi --log-file -
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[![Build Status](https://travis-ci.org/andela-cmutembei/III.svg)](https://travis-ci.org/andela-cmutembei/III)
[![Coverage Status](https://coveralls.io/repos/andela-cmutembei/III/badge.svg?branch=master&service=github)](https://coveralls.io/github/andela-cmutembei/III?branch=master)
[![Code Issues](https://www.quantifiedcode.com/api/v1/project/f3b027bfc00949219f46c6aa0cf5da3a/snapshot/origin:master:HEAD/badge.svg)](https://www.quantifiedcode.com/app/project/f3b027bfc00949219f46c6aa0cf5da3a)

## [BLST](http://blst-api.herokuapp.com/)
BLST is a RESTful API service for managing bucket lists and their constituent items. It is built using [Django](https://www.djangoproject.com/) and [Django Rest Framework](http://www.django-rest-framework.org/) and uses JSON objects for information interchange.

#### Project requirements
- [Python](https://www.python.org/downloads/)
- [Virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/install.html)
- [Postgresql](http://www.postgresql.org/download/)

#### Documentation
The API documentation can be found [here](https://blst-api.herokuapp.com/docs)
#### Installation
To run blst locally configure [environment variables](https://github.com/andela-cmutembei/III/wiki) and do the following:
```shell
$ git clone https://github.com/andela-cmutembei/III.git && cd III

$ workon III-env

(III-env)$ pip install -r requirements.txt

(III-env)$ python manage.py migrate

(III-env)$ python manage.py runserver
```
Alternatively, you can deploy your own instance of BLST on [Heroku](https;//dashboard.heroku.com)

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/andela-cmutembei/III/tree/develop)

#### Running tests
To run unit tests for blst
```shell
(III-env)$ python manage.py test
```
#### License
Copyright © 2016 - Collin Mutembei

This project is licensed under the terms of the [MIT license.](https://github.com/andela-cmutembei/III/blob/develop/LICENSE)
Empty file added api/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin
from api.models import Bucketlist, Item


admin.site.register(Bucketlist)
admin.site.register(Item)
44 changes: 44 additions & 0 deletions api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
from django.conf import settings


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Bucketlist',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=45)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Item',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=45)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('done', models.BooleanField(default=False)),
('parent_bucketlist', models.ForeignKey(related_name='items', to='api.Bucketlist')),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('avatar', models.TextField()),
('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file added api/migrations/__init__.py
Empty file.
38 changes: 38 additions & 0 deletions api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import unicode_literals

from django.db import models
from django.contrib.auth.models import User


class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True, related_name='profile')
avatar = models.TextField()


class Bucketlist(models.Model):
"""Bucketlist model"""

name = models.CharField(blank=False, max_length=45, unique=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)

def __str__(self):
return "{0} - {1}".format(self.id, self.name)


class Item(models.Model):
"""Items model"""

name = models.CharField(blank=False, max_length=45, unique=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
done = models.BooleanField(default=False)
parent_bucketlist = models.ForeignKey(
Bucketlist,
on_delete=models.CASCADE,
related_name="items"
)

def __str__(self):
return "{0} - {1}".format(self.id, self.name)
13 changes: 13 additions & 0 deletions api/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework import permissions
from api.models import Item


class IsOwner(permissions.BasePermission):
"""
custom permissions for owners
"""

def has_object_permission(self, request, view, obj):
if isinstance(obj, Item):
return obj.parent_bucketlist.created_by == request.user
return obj.created_by == request.user
13 changes: 13 additions & 0 deletions api/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from api.models import UserProfile


def get_profile_picture(strategy, user, response, is_new=False, *args, **kwargs):
img_url = None
backend = kwargs['backend']

if backend.name == 'twitter':
img_url = response.get('profile_image_url', '').replace('_normal', '')

profile = UserProfile.objects.get_or_create(user=user)[0]
profile.avatar = img_url
profile.save()
31 changes: 31 additions & 0 deletions api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from rest_framework import serializers
from api.models import Bucketlist, Item


date_created = serializers.DateTimeField()
date_modified = serializers.DateTimeField()


class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'name', 'date_created', 'date_modified', 'done', 'parent_bucketlist')

def get_fields(self, *args, **kwargs):
fields = super(ItemSerializer, self).get_fields(*args, **kwargs)
if self.context:
user = self.context['request'].user
bucketlists = Bucketlist.objects.filter(
created_by=user.id).values_list('id', flat=True)
fields['parent_bucketlist'].queryset = fields['parent_bucketlist'].queryset.filter(
id__in=bucketlists).all()
return fields


class BucketlistSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
created_by = serializers.ReadOnlyField(source='created_by.username')

class Meta:
model = Bucketlist
fields = ('id', 'name', 'items', 'date_created', 'date_modified', 'created_by')
29 changes: 29 additions & 0 deletions api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.conf.urls import url, include
from rest_framework_extensions.routers import ExtendedSimpleRouter
from api import viewsets
from api.views import root_route, landing, dashboard, logout

router = ExtendedSimpleRouter()

(
router.register(r'bucketlists', viewsets.BucketlistViewset)
.register(
r'items',
viewsets.ItemViewset,
base_name='bucketlists-item',
parents_query_lookups=['parent_bucketlist']
)
)

urlpatterns = [
url(r'^$', landing),
url(r'^dashboard/', dashboard),
url(r'^logout/$', logout),
url(r'^api/', include(router.urls)),
url(r'^api/$', root_route),
url(r'^auth/login/', 'rest_framework_jwt.views.obtain_jwt_token'),
url(r'^blst/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^docs/', include('rest_framework_swagger.urls')),
]

url.handler404 = 'api.views.custom_404'
38 changes: 38 additions & 0 deletions api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django.shortcuts import render
from django.contrib import auth
from django.http import HttpResponseRedirect


@api_view(('GET',))
def root_route(request, format=None):
return Response({
'bucketlists': reverse(
'bucketlist-list',
request=request,
format=format
),
})


def custom_404(request):
return render(request, '404.html')


def landing(request):
if request.user.is_authenticated():
return render(request, 'dashboard.html')
return render(request, 'landing.html')


def dashboard(request):
if request.user.is_authenticated():
return render(request, 'dashboard.html')
return HttpResponseRedirect("/")


def logout(request):
auth.logout(request)
return HttpResponseRedirect("/")
25 changes: 25 additions & 0 deletions api/viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from rest_framework import viewsets, permissions
from rest_framework_extensions.mixins import NestedViewSetMixin
from api.models import Bucketlist, Item
from api.permissions import IsOwner
from api.serializers import BucketlistSerializer, ItemSerializer


class BucketlistViewset(NestedViewSetMixin, viewsets.ModelViewSet):
queryset = Bucketlist.objects.all()
serializer_class = BucketlistSerializer
permission_classes = (IsOwner, permissions.IsAuthenticated,)

def perform_create(self, serializer):
serializer.save(created_by=self.request.user)

def get_queryset(self):
user = self.request.user
if user.is_active:
return Bucketlist.objects.filter(created_by=user.id)


class ItemViewset(NestedViewSetMixin, viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
permission_classes = (IsOwner, permissions.IsAuthenticated,)
37 changes: 37 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "BLST",
"description": "Bucketlist API service for perfectionists with a life.",
"keywords": [
"productivity",
"Django",
"Django Rest Framework",
"Bucketlist"
],
"website": "https://blst-api.herokuapp.com/",
"repository": "https://github.com/andela-cmutembei/III",
"logo": "https://blst-api.herokuapp.com/static/images/logo.png",
"env": {
"SECRET": {
"description": "A secret key for ensuring the integrity of sessions, password and tokens.",
"generator": "secret"
},
"DISABLE_COLLECTSTATIC": {
"description": "Disable colleting static files for project on deployment",
"value": "1"
},
"SOCIAL_AUTH_TWITTER_KEY": {
"description": "Twitter consumer key, get it at https://apps.twitter.com",
"value": ""
},
"SOCIAL_AUTH_TWITTER_SECRET": {
"description": "Twitter secret key, get it at https://apps.twitter.com",
"value": ""
}
},
"addons": [
"heroku-postgresql:hobby-dev"
],
"scripts": {
"postdeploy": "python manage.py migrate"
}
}
Empty file added blst/__init__.py
Empty file.

0 comments on commit e8bc5a7

Please sign in to comment.