├── .ebextensions ├── 01-main.config ├── 03-ec2.config └── 04_rds.config ├── .gitignore ├── Dockerfile ├── Dockerrun.aws.json ├── README.md ├── application.py ├── authentication ├── __init__.py ├── admin.py ├── api_urls.py ├── api_views.py ├── forms.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── initadmin.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20150529_1716.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── templates │ ├── authentication │ │ ├── detail.html │ │ ├── form.html │ │ └── initialized.html │ └── registration │ │ ├── login.html │ │ ├── logout.html │ │ └── password_change.html ├── tests.py ├── urls.py └── views.py ├── docker-compose.yml ├── fabfile.py ├── local_requirements.txt ├── manage.py ├── myproject ├── __init__.py ├── main │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20150529_1716.py │ │ └── __init__.py │ ├── models.py │ ├── permissions.py │ ├── serializers.py │ ├── templates │ │ └── main │ │ │ ├── about.html │ │ │ ├── index.html │ │ │ └── landing.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── static │ └── js │ │ └── contact_me.js ├── templates │ ├── base.html │ └── robots.txt ├── urls.py └── wsgi.py.bak ├── requirements.txt ├── runserver.sh └── settings ├── __init__.py ├── common.py ├── dev-local.py └── production.py /.ebextensions/01-main.config: -------------------------------------------------------------------------------- 1 | 2 | option_settings: 3 | - option_name: DJANGO_SETTINGS_MODULE 4 | value: settings.production 5 | -------------------------------------------------------------------------------- /.ebextensions/03-ec2.config: -------------------------------------------------------------------------------- 1 | option_settings: 2 | - namespace: aws:autoscaling:launchconfiguration 3 | option_name: EC2KeyName 4 | value: my-ssh-keys 5 | -------------------------------------------------------------------------------- /.ebextensions/04_rds.config: -------------------------------------------------------------------------------- 1 | option_settings: 2 | # Database Config Options 3 | - namespace: aws:rds:dbinstance 4 | option_name: DBDeletionPolicy 5 | value: Delete 6 | - namespace: aws:rds:dbinstance 7 | option_name: DBEngine 8 | value: postgres 9 | - namespace: aws:rds:dbinstance 10 | option_name: DBEngineVersion 11 | value: 9.3 12 | - namespace: aws:rds:dbinstance 13 | option_name: DBUser 14 | value: ebroot 15 | - namespace: aws:rds:dbinstance 16 | option_name: DBPassword 17 | value: eb.Pass.123 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.py[cod] 4 | log 5 | 6 | *.zip 7 | *.log.txt 8 | 9 | # Local Database 10 | *.sqlite3 11 | 12 | # C extensions 13 | *.so 14 | 15 | *.pem 16 | 17 | # Other 18 | .idea/* 19 | .fabfile/* 20 | 21 | # Elastic Beanstalk Files 22 | .elasticbeanstalk/* 23 | !.elasticbeanstalk/*.cfg.yml 24 | !.elasticbeanstalk/*.global.yml 25 | 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.4 2 | 3 | MAINTAINER David Karchmer 4 | 5 | ENV C_FORCE_ROOT 1 6 | 7 | # create unprivileged user 8 | RUN adduser --disabled-password --gecos '' myuser 9 | 10 | # Install PostgreSQL dependencies 11 | RUN apt-get update && \ 12 | apt-get install -y postgresql-client libpq-dev && \ 13 | rm -rf /var/lib/apt/lists/* 14 | 15 | 16 | # Step 1: Install any Python packages 17 | # ---------------------------------------- 18 | 19 | ENV PYTHONUNBUFFERED 1 20 | RUN mkdir /var/app 21 | WORKDIR /var/app 22 | COPY requirements.txt /var/app/requirements.txt 23 | RUN pip install -r requirements.txt 24 | 25 | # Step 2: Copy Django Code 26 | # ---------------------------------------- 27 | 28 | COPY . /var/app/. 29 | 30 | EXPOSE 8080 31 | 32 | CMD ["/var/app/runserver.sh"] 33 | 34 | -------------------------------------------------------------------------------- /Dockerrun.aws.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "AWSEBDockerrunVersion": "1", 4 | 5 | "Ports": [ 6 | { 7 | "ContainerPort": "8080" 8 | } 9 | ], 10 | "Logging": "/var/app/vps/log" 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README # 2 | 3 | This README document an example for how to setup a Docker based Elastic Beanstalk (EB) 4 | running a Django Project with a Postgres RDS instance 5 | 6 | This version uses the Generic Docker Platform in EB 7 | 8 | ## How do I get set up? ## 9 | 10 | * install Python 3.4 11 | * install git 12 | * install pip 13 | * install virtualenv 14 | * make virtualenv 15 | 16 | * pip install -r requirements.txt 17 | * pip install -r local_requirements.txt 18 | 19 | ### Local Setup (Fabric) ### 20 | 21 | * As indicated, you must first install python 2.7/3.4, git, pip and virtualenv 22 | 23 | * fab statics 24 | * to collect statics 25 | * fab test 26 | * to run unit tests 27 | 28 | * fab migrate 29 | * To makemigrations and migrate database 30 | 31 | * fab runserver 32 | * To run local server 33 | 34 | * fab eb_create 35 | * To deploy initial setup to AWS Elastic Beanstalk 36 | 37 | * fab eb_deploy 38 | * To deploy code changes to an existing image (previously created) 39 | 40 | ### Local Setup (Docker compose) ### 41 | 42 | Assuming you install docker-compose (https://docs.docker.com/compose/) 43 | 44 | * docker-compose up -d 45 | * docker-compose build web // To rebuild django server after changes 46 | * docker-compose run --rm web python manage.py migrate 47 | * docker-compose run --rm web python manage.py initadmin 48 | * docker-compose run --rm web python manage.py test --settings=settings.dev-local 49 | 50 | ### AWS Elastic Beanstalk Release ### 51 | 52 | Assuming credentials stored on ~/.aws/credentials (http://boto.readthedocs.org/en/latest/boto_config_tut.html) 53 | 54 | * export AWS_PROFILE='your-profile-name' 55 | * export EB_ENV_NAME='elastic-beanstalk-environment-and-app-name' (we are using env==app names) 56 | * fab eb_create_preconfigured 57 | * Enter db name and password 58 | * It can take as much as ten minutes to finish 59 | * After the site is up, go to the following address to initialize the admin account 60 | * http://.elasticbeanstalk.com/account/init 61 | * Then login and change the password 62 | 63 | ### References ### 64 | 65 | * https://realpython.com/blog/python/deploying-a-django-app-to-aws-elastic-beanstalk/ -------------------------------------------------------------------------------- /application.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Karchmer' 2 | ''' 3 | This file should not be needed, but trying this solution anyway as per 4 | http://stackoverflow.com/questions/27141577/elastic-beanstalk-django-deployment-with-preconfigured-docker-container 5 | ''' 6 | 7 | import os 8 | import sys 9 | 10 | sys.path.insert(0, '/var/app') 11 | 12 | os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.production' 13 | 14 | from django.core.wsgi import get_wsgi_application 15 | application = get_wsgi_application() 16 | -------------------------------------------------------------------------------- /authentication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkarchmer/aws-eb-docker-django/e6b1d7bb66203ed1f42bef98043aa0e52a1e0cb6/authentication/__init__.py -------------------------------------------------------------------------------- /authentication/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from authentication.models import * 3 | 4 | class AccountAdmin(admin.ModelAdmin): 5 | list_display = ('id', 'username', 'email', 'name', 'created_at', 'last_login') 6 | 7 | 8 | """ 9 | Register Admin Pages 10 | """ 11 | admin.site.register(Account, AccountAdmin) 12 | -------------------------------------------------------------------------------- /authentication/api_urls.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | from django.conf.urls import * 4 | from authentication.api_views import APILoginViewSet, APILogoutViewSet, APITokenViewSet, APIUserInfoViewSet 5 | 6 | urlpatterns = patterns('', 7 | url(r'^login$', APILoginViewSet.as_view(), name='api-login'), 8 | url(r'^logout$', APILogoutViewSet.as_view(), name='api-logout'), 9 | url(r'^token$', APITokenViewSet.as_view(), name='api-token'), 10 | url(r'^user-info$', APIUserInfoViewSet.as_view(), name='api-user-info'), 11 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), 12 | url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token', name='api-token-auth'), 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /authentication/api_views.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | import json 4 | 5 | from django.contrib.auth import authenticate, login, logout 6 | from django.http import HttpResponse 7 | from django.views.decorators.csrf import csrf_exempt 8 | 9 | from rest_framework.views import APIView 10 | from rest_framework import views 11 | from rest_framework import permissions, viewsets 12 | from rest_framework.response import Response 13 | from rest_framework import status 14 | from rest_framework.parsers import JSONParser 15 | from rest_framework.renderers import JSONRenderer 16 | from rest_framework.authtoken.models import Token 17 | from rest_framework.decorators import detail_route 18 | 19 | from django.conf import settings 20 | 21 | from authentication.models import Account 22 | from authentication.permissions import IsAccountOwner 23 | from authentication.serializers import * 24 | 25 | class JSONResponse(HttpResponse): 26 | """ 27 | An HttpResponse that renders its content into JSON. 28 | """ 29 | def __init__(self, data, **kwargs): 30 | content = JSONRenderer().render(data) 31 | kwargs['content_type'] = 'application/json' 32 | super(JSONResponse, self).__init__(content, **kwargs) 33 | 34 | class APITokenViewSet(APIView): 35 | """ 36 | View to get User's token 37 | """ 38 | 39 | def get(self, request, format=None): 40 | """ 41 | Update thumbnail and tiny file field 42 | """ 43 | if request.user.is_anonymous(): 44 | # User most login before they can get a token 45 | # This not only ensures the user has registered, and has an account 46 | # but that the account is active 47 | return JSONResponse('User not recognized.', status=status.HTTP_403_FORBIDDEN) 48 | 49 | data_dic = {} 50 | 51 | try: 52 | token = Token.objects.get(user=request.user) 53 | mystatus = status.HTTP_200_OK 54 | except: 55 | token = Token.objects.create(user=request.user) 56 | mystatus = status.HTTP_201_CREATED 57 | 58 | data_dic['token'] = token.key 59 | return JSONResponse(data_dic, status=mystatus) 60 | 61 | class AccountViewSet(viewsets.ModelViewSet): 62 | lookup_field = 'username' 63 | queryset = Account.objects.all() 64 | serializer_class = AccountSerializer 65 | 66 | def get_permissions(self): 67 | if self.request.method in permissions.SAFE_METHODS: 68 | return (permissions.AllowAny(),) 69 | 70 | if self.request.method == 'POST': 71 | return (permissions.AllowAny(),) 72 | 73 | return (permissions.IsAuthenticated(), IsAccountOwner(),) 74 | 75 | @csrf_exempt 76 | def create(self, request): 77 | ''' 78 | When you create an object using the serializer's .save() method, the 79 | object's attributes are set literally. This means that a user registering with 80 | the password 'password' will have their password stored as 'password'. This is bad 81 | for a couple of reasons: 1) Storing passwords in plain text is a massive security 82 | issue. 2) Django hashes and salts passwords before comparing them, so the user 83 | wouldn't be able to log in using 'password' as their password. 84 | 85 | We solve this problem by overriding the .create() method for this viewset and 86 | using Account.objects.create_user() to create the Account object. 87 | ''' 88 | 89 | serializer = self.serializer_class(data=request.data) 90 | 91 | if serializer.is_valid(): 92 | password = serializer.validated_data['password'] 93 | confirm_password = serializer.validated_data['confirm_password'] 94 | 95 | if password and confirm_password and password == confirm_password: 96 | 97 | # Note that for now, Accounts default to is_active=False 98 | # which means that we need to manually active them 99 | # This is to keep the site secure until we go live 100 | account = Account.objects.create_user(**serializer.validated_data) 101 | 102 | account.set_password(serializer.validated_data['password']) 103 | account.save() 104 | 105 | 106 | return Response(serializer.validated_data, status=status.HTTP_201_CREATED) 107 | 108 | return Response({'status': 'Bad request', 109 | 'message': 'Account could not be created with received data.' 110 | }, status=status.HTTP_400_BAD_REQUEST) 111 | 112 | @detail_route(methods=['post']) 113 | def set_password(self, request, username=None): 114 | account = self.get_object() 115 | serializer = PasswordCustomSerializer(data=request.data) 116 | if serializer.is_valid(): 117 | account.set_password(serializer.data['password']) 118 | account.save() 119 | 120 | return Response({'status': 'password set'}, status=status.HTTP_201_CREATED) 121 | else: 122 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 123 | 124 | 125 | class APILoginViewSet(APIView): 126 | """ 127 | View to list all users in the system. 128 | 129 | * Requires token authentication. 130 | * Only admin users are able to access this view. 131 | """ 132 | #permission_classes = () 133 | 134 | @csrf_exempt 135 | def post(self, request, format=None): 136 | """ 137 | Update thumbnail and tiny file field 138 | """ 139 | data = JSONParser().parse(request) 140 | serializer = LoginCustomSerializer(data=data) 141 | 142 | if serializer.is_valid(): 143 | email = serializer.data.get('email') 144 | password = serializer.data.get('password') 145 | 146 | if not request.user.is_anonymous(): 147 | return JSONResponse('Already Logged-in', status=status.HTTP_403_FORBIDDEN) 148 | 149 | account = authenticate(email=email, password=password) 150 | 151 | if account is not None: 152 | if account.is_active: 153 | login(request, account) 154 | 155 | serialized = AccountSerializer(account) 156 | data = serialized.data 157 | 158 | # Add the token to the return serialization 159 | try: 160 | token = Token.objects.get(user=account) 161 | except: 162 | token = Token.objects.create(user=account) 163 | 164 | data['token'] = token.key 165 | 166 | 167 | return Response(data) 168 | else: 169 | return JSONResponse('This account is not Active.', status=status.HTTP_401_UNAUTHORIZED) 170 | else: 171 | return JSONResponse('Username/password combination invalid.', status=status.HTTP_401_UNAUTHORIZED) 172 | 173 | return JSONResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 174 | 175 | def get(self, request, format=None): 176 | """ 177 | Update thumbnail and tiny file field 178 | """ 179 | 180 | data_dic = {"Error":"GET not supported for this command"} 181 | 182 | logout(request) 183 | mystatus = status.HTTP_400_BAD_REQUEST 184 | 185 | return JSONResponse(data_dic, status=mystatus) 186 | 187 | 188 | class APILogoutViewSet(APIView): 189 | """ 190 | View to get User's token 191 | """ 192 | 193 | def get(self, request, format=None): 194 | """ 195 | Update thumbnail and tiny file field 196 | """ 197 | if request.user.is_anonymous(): 198 | return JSONResponse('User not recognized.', status=status.HTTP_403_FORBIDDEN) 199 | 200 | 201 | data_dic = {} 202 | 203 | logout(request) 204 | mystatus = status.HTTP_200_OK 205 | 206 | return JSONResponse(data_dic, status=mystatus) 207 | 208 | 209 | class APIUserInfoViewSet(APIView): 210 | """ 211 | View to list all users in the system. 212 | 213 | * Requires token authentication. 214 | * Only admin users are able to access this view. 215 | """ 216 | #permission_classes = () 217 | 218 | def get(self, request, format=None): 219 | """ 220 | Update thumbnail and tiny file field 221 | """ 222 | if request.user.is_anonymous(): 223 | # User most login before they can get a token 224 | # This not only ensures the user has registered, and has an account 225 | # but that the account is active 226 | return JSONResponse('User not recognized.', status=status.HTTP_403_FORBIDDEN) 227 | 228 | account = request.user 229 | 230 | serialized = AccountSerializer(account) 231 | data = serialized.data 232 | 233 | # Add the token to the return serialization 234 | try: 235 | token = Token.objects.get(user=account) 236 | except: 237 | token = Token.objects.create(user=account) 238 | 239 | data['token'] = token.key 240 | 241 | return Response(data) 242 | 243 | -------------------------------------------------------------------------------- /authentication/forms.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | from django import forms as forms 4 | from django.forms import ModelForm 5 | from authentication.models import Account 6 | 7 | class AccountUpdateForm(ModelForm): 8 | class Meta: 9 | model = Account 10 | fields = ['email', 'name', 'tagline'] 11 | 12 | def __init__(self, *args, **kwargs): 13 | super(AccountUpdateForm, self).__init__(*args, **kwargs) 14 | self.fields['email'].required = True 15 | 16 | class AvatarUpdateForm(ModelForm): 17 | class Meta: 18 | model = Account 19 | fields = ['avatar_original'] 20 | 21 | def __init__(self, *args, **kwargs): 22 | super(AvatarUpdateForm, self).__init__(*args, **kwargs) 23 | 24 | class AvatarForm(forms.Form): 25 | avatar_url = forms.CharField(max_length=200) 26 | profile_id = forms.CharField(max_length=20) 27 | 28 | -------------------------------------------------------------------------------- /authentication/management/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dkarchmer' 2 | -------------------------------------------------------------------------------- /authentication/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dkarchmer' 2 | -------------------------------------------------------------------------------- /authentication/management/commands/initadmin.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dkarchmer@gmail.com' 2 | 3 | from django.conf import settings 4 | from django.core.management.base import BaseCommand 5 | from authentication.models import Account 6 | 7 | class Command(BaseCommand): 8 | 9 | def handle(self, *args, **options): 10 | if Account.objects.count() == 0: 11 | for user in settings.ADMINS: 12 | username = user[0].replace(' ', '') 13 | email = user[1] 14 | password = 'admin' 15 | print('Creating account for %s (%s)' % (username, email)) 16 | admin = Account.objects.create_superuser(email=email, username=username, password=password) 17 | admin.is_active = True 18 | admin.is_admin = True 19 | admin.save() 20 | else: 21 | print('Admin accounts can only be initialized if no Accounts exist') 22 | -------------------------------------------------------------------------------- /authentication/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Account', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('password', models.CharField(max_length=128, verbose_name='password')), 18 | ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), 19 | ('email', models.EmailField(unique=True, max_length=254)), 20 | ('username', models.CharField(unique=True, max_length=40)), 21 | ('slug', models.SlugField(max_length=60)), 22 | ('name', models.CharField(max_length=120, verbose_name=b'Full Name', blank=True)), 23 | ('tagline', models.CharField(max_length=260, blank=True)), 24 | ('avatar_original', models.CharField(max_length=260, null=True, blank=True)), 25 | ('is_staff', models.BooleanField(default=False)), 26 | ('is_active', models.BooleanField(default=False)), 27 | ('is_admin', models.BooleanField(default=False)), 28 | ('created_at', models.DateTimeField(auto_now_add=True)), 29 | ('updated_at', models.DateTimeField(auto_now=True)), 30 | ], 31 | options={ 32 | 'abstract': False, 33 | }, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /authentication/migrations/0002_auto_20150529_1716.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('authentication', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='account', 16 | name='name', 17 | field=models.CharField(blank=True, verbose_name='Full Name', max_length=120), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /authentication/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkarchmer/aws-eb-docker-django/e6b1d7bb66203ed1f42bef98043aa0e52a1e0cb6/authentication/migrations/__init__.py -------------------------------------------------------------------------------- /authentication/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.contrib.auth.models import AbstractBaseUser 3 | from django.db import models 4 | from django.conf import settings 5 | from django.contrib.auth.models import BaseUserManager 6 | from django.template.defaultfilters import slugify 7 | # import code for encoding urls and generating md5 hashes 8 | import hashlib 9 | try: 10 | # Python 3.4 11 | import urllib.parse 12 | except ImportError: 13 | # Python 2.7 14 | import urllib 15 | 16 | from django.templatetags.static import static 17 | from django.utils.encoding import python_2_unicode_compatible 18 | 19 | 20 | class AccountManager(BaseUserManager): 21 | def create_user(self, email, password=None, **kwargs): 22 | if not email: 23 | raise ValueError('Users must have a valid email address.') 24 | 25 | if not kwargs.get('username'): 26 | raise ValueError('Users must have a valid username.') 27 | 28 | account = self.model( 29 | email=self.normalize_email(email), username=kwargs.get('username') 30 | ) 31 | 32 | account.set_password(password) 33 | account.save() 34 | 35 | return account 36 | 37 | def create_superuser(self, email, password, **kwargs): 38 | account = self.create_user(email, password, **kwargs) 39 | 40 | account.is_admin = True 41 | account.is_active = True 42 | account.is_staff = True 43 | account.save() 44 | 45 | return account 46 | 47 | @python_2_unicode_compatible 48 | class Account(AbstractBaseUser): 49 | email = models.EmailField(unique=True) 50 | username = models.CharField(max_length=40, unique=True) 51 | slug = models.SlugField(max_length=60) 52 | 53 | name = models.CharField(verbose_name='Full Name', max_length=120, blank=True) 54 | tagline = models.CharField(max_length=260, blank=True) 55 | 56 | avatar_original = models.CharField(max_length=260, blank=True, null=True) 57 | 58 | is_staff = models.BooleanField(default=False) 59 | is_active = models.BooleanField(default=False) 60 | is_admin = models.BooleanField(default=False) 61 | 62 | created_at = models.DateTimeField(auto_now_add=True) 63 | updated_at = models.DateTimeField(auto_now=True) 64 | 65 | objects = AccountManager() 66 | 67 | USERNAME_FIELD = 'email' 68 | REQUIRED_FIELDS = ['username'] 69 | 70 | def save(self, *args, **kwargs): 71 | 72 | self.slug = slugify(self.username) 73 | 74 | super(Account, self).save(*args, **kwargs) 75 | 76 | def __str__(self): 77 | return self.email 78 | 79 | def get_full_name(self): 80 | return self.name 81 | 82 | def get_short_name(self): 83 | if (self.name != ''): 84 | # Try to extract the first name 85 | names = self.name.split() 86 | first_name = names[0] 87 | return first_name 88 | return self.username 89 | 90 | def has_perm(self, perm, obj=None): 91 | return True 92 | 93 | def has_perms(perm_list, obj=None): 94 | print(str(perm_list)) 95 | return True 96 | 97 | def has_module_perms(self, app_label): 98 | return True 99 | 100 | # Custom Methods 101 | # -------------- 102 | def get_absolute_url(self): 103 | return '/account/%s/' % self.username 104 | 105 | def get_edit_url(self): 106 | return '%sedit/' % self.get_absolute_url() 107 | 108 | def get_thumbnail_url(self): 109 | #key = bucket.new_key(self.avatar) 110 | #url = key.generate_url(expires_in=0, query_auth=False) 111 | file_name = None 112 | return self.get_gravatar_thumbnail_url() 113 | 114 | def get_picture_upload_url(self): 115 | return '%savatar/upload/' % self.get_absolute_url() 116 | 117 | def get_avatar_file_name(self): 118 | return 'avatar-%s' % self.username 119 | 120 | def get_gravatar_thumbnail_url(self, size=100): 121 | # Set your variables here 122 | email = self.email 123 | default = 'identicon' 124 | 125 | # construct the url 126 | gravatar_url = "https://secure.gravatar.com/avatar/" + hashlib.md5(email.lower().encode('utf-8')).hexdigest() + "?" 127 | try: 128 | # Python 3.4 129 | gravatar_url += urllib.parse.urlencode({'d':default, 's':str(size)}) 130 | except: 131 | # Python 2.7 132 | gravatar_url += urllib.urlencode({'d':default, 's':str(size)}) 133 | 134 | return gravatar_url -------------------------------------------------------------------------------- /authentication/permissions.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | from rest_framework import permissions 4 | 5 | 6 | class IsAccountOwner(permissions.BasePermission): 7 | def has_object_permission(self, request, view, account): 8 | if request.user: 9 | return account == request.user 10 | return False 11 | -------------------------------------------------------------------------------- /authentication/serializers.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | from django.contrib.auth import update_session_auth_hash 4 | from rest_framework import serializers 5 | from authentication.models import Account 6 | from rest_framework.authtoken.models import Token 7 | 8 | 9 | class AccountSerializer(serializers.ModelSerializer): 10 | password = serializers.CharField(write_only=True, required=False) 11 | confirm_password = serializers.CharField(write_only=True, required=False) 12 | tiny_url = serializers.CharField(source='get_tiny_url', read_only=True) 13 | 14 | class Meta: 15 | model = Account 16 | fields = ('id', 'email', 'username', 'created_at', 'updated_at', 17 | 'name', 'tagline', 'tiny_url', 'password', 18 | 'confirm_password',) 19 | read_only_fields = ('created_at', 'updated_at',) 20 | 21 | def create(self, validated_data): 22 | return Account.objects.create(**validated_data) 23 | 24 | def update(self, instance, validated_data): 25 | instance.username = validated_data.get('username', instance.username) 26 | instance.tagline = validated_data.get('tagline', instance.tagline) 27 | 28 | instance.save() 29 | 30 | password = validated_data.get('password', None) 31 | confirm_password = validated_data.get('confirm_password', None) 32 | 33 | if password and confirm_password and password == confirm_password: 34 | instance.set_password(password) 35 | instance.is_active = True 36 | instance.save() 37 | 38 | update_session_auth_hash(self.context.get('request'), instance) 39 | 40 | return instance 41 | 42 | def to_representation(self, obj): 43 | data = super(AccountSerializer, self).to_representation(obj) 44 | 45 | return data 46 | 47 | class LoginCustomSerializer(serializers.Serializer): 48 | email = serializers.EmailField(max_length=200) 49 | password = serializers.CharField(max_length=200) 50 | 51 | class PasswordCustomSerializer(serializers.Serializer): 52 | password = serializers.CharField(max_length=200) 53 | 54 | -------------------------------------------------------------------------------- /authentication/templates/authentication/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |

@{{ object.username }}

7 | 8 | 9 |

{{ object.get_full_name }}

10 | {{ object.created_at|date:"D d M Y" }} 11 |

12 | Email: {{ object.email }} 13 |

14 | 15 | {% if object.tagline %} 16 |

{{ object.tagline }}

17 | {% endif %} 18 |
19 | 20 | 21 | {% endblock %} -------------------------------------------------------------------------------- /authentication/templates/authentication/form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {# Load CSS and JavaScript #} 5 | 6 | {% bootstrap_css %} 7 | {% bootstrap_javascript %} 8 | 9 | {# Display django.contrib.messages as Bootstrap alerts } 10 | {% bootstrap_messages %} 11 | 12 | 13 | 14 | {% block content %} 15 | 16 |

User @{{ user.username }}

17 | 18 |
19 |
20 | 21 | 22 |

A valid email address is required

23 | 24 |
25 | {% csrf_token %} 26 | {% form %} 27 | 30 |
31 | 32 |
33 | 34 |
35 |
36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /authentication/templates/authentication/initialized.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block navigation %} 5 | 6 |
  • 7 | Login 8 |
  • 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 |
    15 |

    Account Initialization

    16 | {% if initialized %} 17 |

    Initialized

    18 | {% else %} 19 |

    Accounts exist already

    20 | {% endif %} 21 |
    22 | 23 | {% endblock %} -------------------------------------------------------------------------------- /authentication/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% load bootstrap3 %} 5 | 6 | {# Load CSS and JavaScript #} 7 | 8 | {% bootstrap_css %} 9 | {% bootstrap_javascript %} 10 | 11 | {# Display django.contrib.messages as Bootstrap alerts } 12 | {% bootstrap_messages %} 13 | 14 | 15 | 16 | {% block content %} 17 | 18 |
    19 |
    20 | 21 | 24 |
    25 | 26 | 27 | 28 | 29 |
    30 | {% csrf_token %} 31 | {% bootstrap_form form %} 32 | {% buttons %} 33 | 36 | {% endbuttons %} 37 |
    38 | 39 | 40 |
    41 | 42 |
    43 |
    44 | 45 | {% endblock %} 46 | 47 | -------------------------------------------------------------------------------- /authentication/templates/registration/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
    6 |

    Login Again

    7 |
    8 | 9 | {% endblock %} 10 | 11 | -------------------------------------------------------------------------------- /authentication/templates/registration/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% load bootstrap3 %} 5 | 6 | {# Load CSS and JavaScript #} 7 | 8 | {% bootstrap_css %} 9 | {% bootstrap_javascript %} 10 | 11 | {# Display django.contrib.messages as Bootstrap alerts } 12 | {% bootstrap_messages %} 13 | 14 | 15 | 16 | {% block content %} 17 | 18 |
    19 |
    20 | 21 |

    Change your password

    22 |
    23 | 24 | 25 | 26 | 27 |
    28 | {% csrf_token %} 29 | {% bootstrap_form form %} 30 | {% buttons %} 31 | 34 | {% endbuttons %} 35 |
    36 | 37 | 38 |
    39 |
    40 | 41 | {% endblock %} 42 | 43 | -------------------------------------------------------------------------------- /authentication/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase, Client 2 | from django.core import mail 3 | import json 4 | 5 | from rest_framework.test import APIRequestFactory, APIClient 6 | from rest_framework.authtoken.models import Token 7 | from rest_framework.reverse import reverse 8 | from rest_framework import status 9 | 10 | from authentication.models import * 11 | from authentication.serializers import AccountSerializer 12 | 13 | from django.contrib.auth import get_user_model 14 | user_model = get_user_model() 15 | 16 | class MainTestCase(TestCase): 17 | """ 18 | Fixure includes: 19 | """ 20 | #fixtures = ['testdb_main.json'] 21 | 22 | def setUp(self): 23 | self.u1 = user_model.objects.create_superuser(username='user1', email='user1@foo.com', password='pass') 24 | self.u1.name = 'User One' 25 | self.u1.is_active = True; 26 | self.u1.save() 27 | self.u2 = user_model.objects.create_user(username='user2', email='user2@foo.com', password='pass') 28 | self.u2.name = 'User G Two' 29 | self.u2.save() 30 | self.u3 = user_model.objects.create_user(username='user3', email='user3@foo.com', password='pass') 31 | self.token1 = Token.objects.create(user=self.u1) 32 | self.token2 = Token.objects.create(user=self.u2) 33 | 34 | def tearDown(self): 35 | user_model.objects.all().delete() 36 | Token.objects.all().delete() 37 | 38 | def test_full_short_names(self): 39 | self.assertEqual(self.u3.get_full_name(), u'') 40 | self.assertEqual(self.u3.get_short_name(), self.u3.username) 41 | self.assertEqual(self.u2.get_full_name(), self.u2.name) 42 | self.assertEqual(self.u2.get_short_name(), u'User') 43 | 44 | def test_api_token(self): 45 | 46 | url = reverse('api-token') 47 | u4 = user_model.objects.create_user(username='User4', email='user4@foo.com', password='pass') 48 | u4.is_active = True 49 | u4.save() 50 | 51 | try: 52 | token = Token.objects.get(user=u4) 53 | # There should be no token for u3 54 | self.assertEqual(1, 0) 55 | except: 56 | pass 57 | 58 | resp = self.client.get(url, data={'format': 'json'}) 59 | self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) 60 | ok = self.client.login(email='user4@foo.com', password='pass') 61 | self.assertTrue(ok) 62 | resp = self.client.get(url, data={'format': 'json'}) 63 | self.assertEqual(resp.status_code, status.HTTP_201_CREATED) 64 | deserialized = json.loads(resp.content.decode()) 65 | self.assertEqual(len(deserialized), 1) 66 | token = Token.objects.get(user=u4) 67 | self.assertEqual(deserialized['token'], token.key) 68 | resp = self.client.get(url, data={'format': 'json'}) 69 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 70 | 71 | def test_serializer(self): 72 | account = Account.objects.latest('created_at') 73 | serialized_account = AccountSerializer(account) 74 | email = serialized_account.data.get('email') 75 | username = serialized_account.data.get('username') 76 | self.assertEqual(username, 'user3') 77 | self.assertEqual(email, 'user3@foo.com') 78 | 79 | from rest_framework.test import APITestCase 80 | class AccountAPITests(APITestCase): 81 | 82 | def setUp(self): 83 | self.u1 = user_model.objects.create_superuser(username='user1', email='user1@foo.com', password='pass') 84 | self.u1.is_active = True; 85 | self.u1.name = 'User One' 86 | self.u1.save() 87 | self.u2 = user_model.objects.create_user(username='user2', email='user2@foo.com', password='pass') 88 | self.u3 = user_model.objects.create_user(username='user3', email='user3@foo.com', password='pass') 89 | self.token1 = Token.objects.create(user=self.u1) 90 | self.token2 = Token.objects.create(user=self.u2) 91 | 92 | def tearDown(self): 93 | user_model.objects.all().delete() 94 | Token.objects.all().delete() 95 | 96 | 97 | def test_create_account(self): 98 | """ 99 | Ensure we can create a new account object. 100 | """ 101 | url = reverse('account-list') 102 | data = {'username':'user5', 103 | 'email':'user5@foo.com', 104 | 'password':'pass', 105 | 'confirm_password':'pass'} 106 | response = self.client.post(url, data, format='json') 107 | self.assertEqual(response.status_code, status.HTTP_201_CREATED) 108 | self.assertEqual(response.data, data) 109 | 110 | response = self.client.get(url, data={'format': 'json'}) 111 | self.assertEqual(response.status_code, status.HTTP_200_OK) 112 | deserialized = json.loads((response.content).decode()) 113 | self.assertEqual(deserialized['count'], 4) 114 | 115 | def test_GET_Account(self): 116 | 117 | url = reverse('account-detail', kwargs={'username':'user1'}) 118 | resp = self.client.get(url, data={'format': 'json'}) 119 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 120 | 121 | self.assertEqual(resp.data['id'], 1) 122 | self.assertEqual(resp.data['username'], u'user1') 123 | self.assertEqual(resp.data['email'], u'user1@foo.com') 124 | self.assertFalse('token' in resp.data) 125 | 126 | def test_PATCH_Account(self): 127 | 128 | url = reverse('account-detail', kwargs={'username':'user1'}) 129 | 130 | ok = self.client.login(email='user1@foo.com', password='pass') 131 | self.assertTrue(ok) 132 | 133 | resp = self.client.get(url, data={'format': 'json'}) 134 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 135 | self.assertEqual(resp.data['id'], 1) 136 | self.assertEqual(resp.data['username'], u'user1') 137 | self.assertEqual(resp.data['email'], u'user1@foo.com') 138 | self.assertEqual(resp.data['name'], u'User One') 139 | self.assertEqual(resp.data['tagline'], u'') 140 | 141 | new_tagline = 'Awesome' 142 | data = {'tagline':new_tagline} 143 | 144 | resp = self.client.patch(url, data=data, format='json') 145 | 146 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 147 | 148 | resp = self.client.get(url, data={'format': 'json'}) 149 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 150 | self.assertEqual(resp.data['tagline'], new_tagline) 151 | 152 | def test_GET_Accounts(self): 153 | 154 | url = reverse('account-list') 155 | resp = self.client.get(url, data={'format': 'json'}) 156 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 157 | deserialized = json.loads(resp.content.decode()) 158 | self.assertEqual(deserialized['count'], 3) 159 | 160 | self.assertEqual([obj['id'] for obj in deserialized['results']], [1,2,3]) 161 | self.assertEqual([obj['username'] for obj in deserialized['results']], [u'user1', u'user2', u'user3']) 162 | self.assertEqual([obj['email'] for obj in deserialized['results']], [u'user1@foo.com', 163 | u'user2@foo.com', 164 | u'user3@foo.com']) 165 | self.assertFalse('token' in deserialized['results'][0]) 166 | 167 | def test_basic_POST_Account(self): 168 | 169 | 170 | url = reverse('account-list') 171 | resp = self.client.post(url, {'username':'user4', 172 | 'email':'user4@foo.com', 173 | 'password':'pass', 174 | 'confirm_password':'pass'}, format='json') 175 | self.assertEqual(resp.status_code, status.HTTP_201_CREATED) 176 | 177 | resp = self.client.get(url, data={'format': 'json'}) 178 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 179 | deserialized = json.loads(resp.content.decode()) 180 | self.assertEqual(deserialized['count'], 4) 181 | 182 | # No duplicates 183 | data = {'username':'user4', 184 | 'email':'user4@foo.com', 185 | 'password':'pass', 186 | 'confirm_password':'pass'} 187 | 188 | resp = self.client.post(url, data=data, format='json') 189 | self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) 190 | 191 | data['username'] = 'user5' 192 | resp = self.client.post(url, data=data, format='json') 193 | self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) 194 | 195 | data['username'] = 'user4' 196 | data['email'] = 'user5@foo.com' 197 | resp = self.client.post(url, data=data, format='json') 198 | self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) 199 | 200 | data['username'] = 'user5' 201 | resp = self.client.post(url, data=data, format='json') 202 | self.assertEqual(resp.status_code, status.HTTP_201_CREATED) 203 | 204 | data['username'] = 'user6' 205 | data['email'] = 'user6@foo.com' 206 | data['confirm_password'] = 'pass1' 207 | resp = self.client.post(url, data=data, format='json') 208 | self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) 209 | 210 | data['tagline'] = 'Awesome' 211 | data['name'] = 'User One' 212 | data['confirm_password'] = 'pass' 213 | resp = self.client.post(url, data=data, format='json') 214 | self.assertEqual(resp.status_code, status.HTTP_201_CREATED) 215 | 216 | def test_login_api(self): 217 | 218 | url = reverse('api-login') 219 | client = self.client 220 | 221 | resp = client.post(url, {'email':'user1@foo.com', 'password':'pass'}, format='json') 222 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 223 | self.assertTrue('token' in resp.data) 224 | self.assertEqual(resp.data['token'], self.token1.key) 225 | 226 | client.logout() 227 | 228 | resp = client.post(url, {'email':'user1@foo.com'}, format='json') 229 | self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) 230 | 231 | resp = client.post(url, {'email':'user101@foo.com', 'password':'pass'}, format='json') 232 | self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) 233 | 234 | # Test that we cannot login if not Active 235 | u5 = user_model.objects.create_user(username='user5', email='user5@foo.com', password='pass') 236 | u5.is_active=False 237 | u5.save() 238 | ok = client.login(email='user5@foo.com', password='pass') 239 | self.assertFalse(ok) 240 | resp = client.post(url, {'email':'user5@foo.com', 'password':'pass'}, format='json') 241 | self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) 242 | 243 | def test_user_info_api(self): 244 | 245 | url = reverse('api-user-info') 246 | 247 | resp = self.client.get(url, data={'format': 'json'}) 248 | self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) 249 | 250 | ok = self.client.login(email='user1@foo.com', password='pass') 251 | self.assertTrue(ok) 252 | 253 | resp = self.client.get(url, data={'format': 'json'}) 254 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 255 | 256 | self.assertEqual(resp.data['tagline'], '') 257 | self.assertEqual(resp.data['name'], 'User One') 258 | self.assertEqual(resp.data['username'], 'user1') 259 | self.assertEqual(resp.data['email'], 'user1@foo.com') 260 | 261 | def test_PUT_Account(self): 262 | 263 | url = reverse('account-detail', kwargs={'username':'user1'}) 264 | 265 | ok = self.client.login(email='user1@foo.com', password='pass') 266 | self.assertTrue(ok) 267 | 268 | resp = self.client.get(url, data={'format': 'json'}) 269 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 270 | self.assertEqual(resp.data['id'], 1) 271 | self.assertEqual(resp.data['username'], u'user1') 272 | self.assertEqual(resp.data['email'], u'user1@foo.com') 273 | self.assertEqual(resp.data['name'], u'User One') 274 | self.assertEqual(resp.data['tagline'], u'') 275 | 276 | new_tagline = 'Awesome' 277 | data = {'email':self.u1.email, 278 | 'username':self.u1.username, 279 | 'name':'User One', 280 | 'tagline':new_tagline} 281 | 282 | resp = self.client.put(url, data=data, format='json') 283 | 284 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 285 | 286 | resp = self.client.get(url, data={'format': 'json'}) 287 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 288 | self.assertEqual(resp.data['tagline'], data['tagline']) 289 | self.assertEqual(resp.data['username'], data['username']) 290 | self.assertEqual(resp.data['name'], data['name']) 291 | self.assertEqual(resp.data['email'], data['email']) 292 | 293 | def test_logout_api(self): 294 | 295 | url = reverse('api-login') 296 | client = self.client 297 | 298 | resp = client.post(url, {'email':'user1@foo.com', 'password':'pass'}, format='json') 299 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 300 | self.assertTrue('token' in resp.data) 301 | self.assertEqual(resp.data['token'], self.token1.key) 302 | 303 | url = reverse('api-logout') 304 | resp = client.get(url, format='json') 305 | self.assertEqual(resp.status_code, status.HTTP_200_OK) 306 | 307 | -------------------------------------------------------------------------------- /authentication/urls.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Karchmer' 2 | 3 | from django.conf.urls import * 4 | from django.views.generic import RedirectView, TemplateView 5 | from django.core.urlresolvers import reverse_lazy 6 | 7 | from authentication.views import * 8 | 9 | urlpatterns = patterns('', 10 | 11 | url(r'^$', AccountRedirectView.as_view(), name='account_redirect'), 12 | url(r'^init/?$', AccountInitView.as_view(), name='account_init'), 13 | 14 | url( 15 | r'^login/$','django.contrib.auth.views.login', 16 | dict( 17 | template_name = 'registration/login.html', 18 | ), 19 | name='login', 20 | ), 21 | url( 22 | r'^logout/$','django.contrib.auth.views.logout', 23 | dict( 24 | next_page = '/', 25 | ), 26 | name='logout', 27 | ), 28 | url(r'^(?P\w+)/edit/?$', AccountUpdateView.as_view(), name='account_edit'), 29 | url(r'^(?P\w+)/?$', AccountDetailView.as_view(), name='account_detail'), 30 | 31 | ) 32 | -------------------------------------------------------------------------------- /authentication/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.shortcuts import get_object_or_404 3 | from django.views.generic.edit import UpdateView, FormView 4 | from django.views.generic import DetailView, TemplateView, RedirectView 5 | from django.utils.decorators import method_decorator 6 | from django.contrib.auth.decorators import login_required 7 | from django.core.exceptions import ObjectDoesNotExist 8 | from django.http import HttpResponse, HttpResponseRedirect 9 | from django.views.generic import View 10 | from django.core.urlresolvers import reverse 11 | from django.conf import settings 12 | from django.contrib import messages 13 | from django.views.decorators.csrf import csrf_exempt 14 | from django.shortcuts import render_to_response 15 | from django.template import RequestContext 16 | 17 | from authentication.models import Account 18 | from authentication.forms import * 19 | 20 | class AccountRedirectView(View): 21 | @method_decorator(login_required) 22 | def get(self, request): 23 | user = request.user 24 | 25 | return HttpResponseRedirect(reverse('account_detail', args=(user.username,))) 26 | 27 | 28 | class AccountDetailView(DetailView): 29 | model = Account 30 | template_name = 'authentication/detail.html' 31 | 32 | def get_context_data(self, **kwargs): 33 | context = super(AccountDetailView, self).get_context_data(**kwargs) 34 | return context 35 | 36 | @method_decorator(login_required) 37 | def dispatch(self, request, *args, **kwargs): 38 | return super(AccountDetailView, self).dispatch(request, *args, **kwargs) 39 | 40 | class AccountUpdateView(UpdateView): 41 | model = Account 42 | form_class = AccountUpdateForm 43 | template_name = 'authentication/form.html' 44 | 45 | def form_valid(self, form): 46 | self.object = form.save(commit=False) 47 | self.object.save() 48 | 49 | return HttpResponseRedirect(self.get_success_url()) 50 | 51 | @method_decorator(login_required) 52 | def dispatch(self, request, *args, **kwargs): 53 | return super(AccountUpdateView, self).dispatch(request, *args, **kwargs) 54 | 55 | class AccountInitView(View): 56 | def get(self, request): 57 | template = 'authentication/initialized.html' 58 | if Account.objects.count() == 0: 59 | print('Created Admin accoount') 60 | admin = Account.objects.create_superuser(email=settings.ADMIN_EMAIL, 61 | username=settings.ADMIN_USERNAME, 62 | password=settings.ADMIN_INITIAL_PASSWORD) 63 | admin.is_active = True 64 | admin.is_admin = True 65 | admin.save() 66 | initialized = True 67 | else: 68 | print('Init ignored. Accounts already exit') 69 | initialized = False 70 | 71 | return render_to_response(template, locals(), context_instance = RequestContext(request)) 72 | 73 | 74 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: postgres:9.3 3 | web: 4 | build: . 5 | volumes: 6 | - .:/var/app 7 | ports: 8 | - "8080:8080" 9 | links: 10 | - db:db 11 | environment: 12 | - DJANGO_SETTINGS_MODULE=settings.production 13 | - RDS_DB_NAME=postgres 14 | - RDS_USERNAME=postgres 15 | - RDS_PASSWORD='' 16 | - RDS_HOSTNAME=db 17 | - RDS_PORT=5432 18 | 19 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | import re 4 | import os, time, json 5 | import requests 6 | from fabric.api import local, env, lcd 7 | from fabric.colors import green as _green, yellow as _yellow 8 | from fabric.colors import red as _red, blue as _blue 9 | 10 | import boto 11 | from boto import sqs 12 | 13 | AWS_PROFILE=os.environ['AWS_PROFILE'] 14 | EB_ENV_NAME=os.environ['EB_ENV_NAME'] 15 | DOMAIN_NAME = EB_ENV_NAME + '.elasticbeanstalk.com ' 16 | BASE_URL = 'http://' + EB_ENV_NAME + '.' + DOMAIN_NAME 17 | AWS_REGION = 'us-east-1' 18 | ADMIN_USERNAME = 'admin' 19 | ADMIN_EMAIL = 'admin@mysite.com' 20 | ADMIN_INITIAL_PASSWORD = 'admin' # To be changed after first login by admin 21 | 22 | 23 | authorization_token = None 24 | 25 | def create_local_admin(): 26 | local("python manage.py createsuperuser --username=admin --email=admin@test.com --settings=settings.dev-local") 27 | 28 | def statics(): 29 | local("python manage.py collectstatic --noinput --settings=settings.dev-local") 30 | 31 | def migrate(): 32 | local("python manage.py makemigrations --settings=settings.dev-local") 33 | local("python manage.py migrate --noinput --settings=settings.dev-local") 34 | 35 | def test(app=''): 36 | local('export AWS_PROFILE=%s' % AWS_PROFILE) 37 | cmd = "python manage.py test %s --settings=settings.dev-local" % app 38 | local(cmd) 39 | 40 | def runserver(): 41 | migrate() 42 | local("python manage.py runserver --settings=settings.dev-local") 43 | 44 | def eb_deploy(): 45 | local("eb deploy --timeout=10") 46 | 47 | def eb_create(name=EB_ENV_NAME): 48 | 49 | local('eb init -p docker --profile %s %s' % (AWS_PROFILE, name)) 50 | local('eb create -db -s --timeout=20 --profile %s -c %s %s ' % (AWS_PROFILE, name, name)) 51 | 52 | def get_db_info(): 53 | 54 | rds_conn = boto.connect_rds2(profile_name=AWS_PROFILE) 55 | if not rds_conn: 56 | print(_red('Cannot connect to AWS.RDS')) 57 | return 58 | 59 | instances = rds_conn.describe_db_instances() 60 | if not instances: 61 | print(_red('No instances found')) 62 | return 63 | 64 | count = len(instances['DescribeDBInstancesResponse']['DescribeDBInstancesResult']['DBInstances']) 65 | for i in range(0, count): 66 | inst = instances['DescribeDBInstancesResponse']['DescribeDBInstancesResult']['DBInstances'][i] 67 | 68 | #print(str(inst)) 69 | 70 | dbinfo = {} 71 | endpoint = inst['Endpoint'] 72 | dbinfo['VPCSecurityGroupId'] = inst['VpcSecurityGroups'][0]['VpcSecurityGroupId'] 73 | dbinfo['dbSecurityGroupName'] = inst['DBSecurityGroups'][0]['DBSecurityGroupName'] 74 | dbinfo['host'] = endpoint['Address'] 75 | dbinfo['port'] = endpoint['Port'] 76 | dbinfo['user'] = inst['MasterUsername'] 77 | dbinfo['name'] = inst['DBName'] 78 | dbinfo['instanceClass'] = inst['DBInstanceClass'] 79 | dbinfo['dbID'] = inst['DBInstanceIdentifier'] 80 | dbinfo['Engine'] = inst['Engine'] 81 | dbinfo['EngineVersion'] = inst['EngineVersion'] 82 | 83 | print('') 84 | print(_blue('db Info %d ===========>\n' % i)) 85 | for item in dbinfo: 86 | print(_green('%20s : %s' % (item, dbinfo[item]))) 87 | 88 | -------------------------------------------------------------------------------- /local_requirements.txt: -------------------------------------------------------------------------------- 1 | awscli==1.7.22 2 | awsebcli==3.2.2 3 | Fabric==1.10.1 4 | docker-compose==1.2.0rc4 5 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | 7 | 8 | sys.path.insert(0, '/var/app') 9 | 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.production") 11 | 12 | from django.core.management import execute_from_command_line 13 | 14 | execute_from_command_line(sys.argv) 15 | -------------------------------------------------------------------------------- /myproject/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dkarchmer' 2 | -------------------------------------------------------------------------------- /myproject/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkarchmer/aws-eb-docker-django/e6b1d7bb66203ed1f42bef98043aa0e52a1e0cb6/myproject/main/__init__.py -------------------------------------------------------------------------------- /myproject/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from myproject.main.models import * 3 | 4 | -------------------------------------------------------------------------------- /myproject/main/api_views.py: -------------------------------------------------------------------------------- 1 | import json 2 | from django.http import HttpResponse, Http404 3 | from django.core.exceptions import PermissionDenied 4 | from django.contrib.auth.models import User 5 | from rest_framework import generics 6 | from rest_framework.permissions import IsAdminUser 7 | from rest_framework import viewsets 8 | from rest_framework import mixins 9 | from rest_framework.views import APIView 10 | from rest_framework import status 11 | from rest_framework.response import Response 12 | from rest_framework.parsers import JSONParser 13 | from rest_framework.renderers import JSONRenderer 14 | from django.core.mail import mail_admins 15 | from django.shortcuts import get_object_or_404 16 | from django.conf import settings 17 | 18 | from myproject.main.models import ContactMessage 19 | from myproject.main.serializers import ContactMessageSerializer 20 | from myproject.main.permissions import ContactMessagePermission 21 | 22 | class APIMessageViewSet(mixins.CreateModelMixin, 23 | mixins.ListModelMixin, 24 | mixins.RetrieveModelMixin, 25 | viewsets.GenericViewSet): 26 | """ 27 | This viewset automatically provides `list`, `create`, `retrieve`, 28 | `update` and `destroy` actions. 29 | 30 | Additionally we also provide an extra `highlight` action. 31 | """ 32 | queryset = ContactMessage.objects.all() 33 | serializer_class = ContactMessageSerializer 34 | permission_classes = (ContactMessagePermission,) 35 | 36 | 37 | def get_queryset(self): 38 | """ 39 | This view should return a list of all records 40 | for the currently authenticated user. 41 | """ 42 | #user = self.request.user 43 | return ContactMessage.objects.all() 44 | 45 | def perform_create(self, serializer): 46 | # Include the owner attribute directly, rather than from request data. 47 | instance = serializer.save() 48 | 49 | # Schedule a task to send email 50 | #send_contact_me_notification(instance) 51 | 52 | class JSONResponse(HttpResponse): 53 | """ 54 | An HttpResponse that renders its content into JSON. 55 | """ 56 | def __init__(self, data, **kwargs): 57 | content = JSONRenderer().render(data) 58 | kwargs['content_type'] = 'application/json' 59 | super(JSONResponse, self).__init__(content, **kwargs) 60 | 61 | -------------------------------------------------------------------------------- /myproject/main/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms as forms 2 | 3 | from django import forms as forms 4 | 5 | -------------------------------------------------------------------------------- /myproject/main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='ContactMessage', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('name', models.CharField(max_length=50)), 18 | ('email', models.EmailField(max_length=60)), 19 | ('phone', models.CharField(max_length=50, blank=True)), 20 | ('message', models.TextField()), 21 | ('created_on', models.DateTimeField(auto_now_add=True, verbose_name=b'created_on')), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /myproject/main/migrations/0002_auto_20150529_1716.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='contactmessage', 16 | name='created_on', 17 | field=models.DateTimeField(verbose_name='created_on', auto_now_add=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /myproject/main/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkarchmer/aws-eb-docker-django/e6b1d7bb66203ed1f42bef98043aa0e52a1e0cb6/myproject/main/migrations/__init__.py -------------------------------------------------------------------------------- /myproject/main/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | import mimetypes 3 | from django.db import models 4 | from django.utils.encoding import python_2_unicode_compatible 5 | 6 | @python_2_unicode_compatible 7 | class ContactMessage(models.Model): 8 | 9 | name = models.CharField(max_length=50) 10 | email = models.EmailField(max_length=60) 11 | phone = models.CharField(max_length=50, blank=True) 12 | message = models.TextField() 13 | 14 | created_on = models.DateTimeField('created_on', auto_now_add=True) 15 | 16 | def __str__(self): 17 | return self.name 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /myproject/main/permissions.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | from rest_framework import permissions 4 | 5 | 6 | class ContactMessagePermission(permissions.BasePermission): 7 | """ 8 | Custom permission to only allow owners of an object to access (read/write) 9 | """ 10 | 11 | def has_permission(self, request, view): 12 | if request.method == 'POST': 13 | return True 14 | else: 15 | return request.user.is_staff 16 | 17 | def has_object_permission(self, request, view, obj): 18 | # Everybody can submit 19 | return request.user.is_staff 20 | -------------------------------------------------------------------------------- /myproject/main/serializers.py: -------------------------------------------------------------------------------- 1 | __author__ = 'david' 2 | 3 | from rest_framework import serializers 4 | 5 | from myproject.main.models import ContactMessage 6 | 7 | class ContactMessageSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = ContactMessage 10 | fields = ('id', 'name', 'email', 'phone', 'message', 'created_on') 11 | read_only_fields = ('created_on') 12 | 13 | -------------------------------------------------------------------------------- /myproject/main/templates/main/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block navigation %} 5 | 6 |
  • 7 | Login 8 |
  • 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 |
    15 |

    About this site

    16 |
    17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /myproject/main/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block navigation %} 5 | 6 |
  • 7 | Profile 8 |
  • 9 |
  • 10 | Logout 11 |
  • 12 | 13 | {% endblock %} 14 | 15 | {% block content %} 16 | 17 |
    18 |

    You are in!

    19 |
    20 | 21 | {% endblock %} -------------------------------------------------------------------------------- /myproject/main/templates/main/landing.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block js %} 5 | 6 | 7 | 8 | {% endblock %} 9 | 10 | 11 | {% block navigation %} 12 | 13 |
  • 14 | Init 15 |
  • 16 |
  • 17 | Login 18 |
  • 19 | 20 | {% endblock %} 21 | 22 | {% block content %} 23 | 24 |
    25 |

    MySite

    26 |

    The next big thing!

    27 |
    28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /myproject/main/tests.py: -------------------------------------------------------------------------------- 1 | #import unittest 2 | from django.test import TestCase, Client 3 | from django.core import mail 4 | import json 5 | 6 | from rest_framework.test import APIRequestFactory, APIClient 7 | from rest_framework.reverse import reverse 8 | from rest_framework import status 9 | 10 | from myproject.main.models import * 11 | 12 | from django.contrib.auth import get_user_model 13 | user_model = get_user_model() 14 | 15 | class MainTestCase(TestCase): 16 | """ 17 | Fixure includes: 18 | """ 19 | #fixtures = ['testdb_main.json'] 20 | 21 | def setUp(self): 22 | self.u1 = user_model.objects.create_superuser(username='user1', email='user1@foo.com', password='pass') 23 | self.u1.is_active = True 24 | self.u1.save() 25 | self.u2 = user_model.objects.create_user(username='user2', email='user2@foo.com', password='pass') 26 | self.u2.is_active = True 27 | self.u2.save() 28 | self.u3 = user_model.objects.create_user(username='user3', email='user3@foo.com', password='pass') 29 | self.u3.is_active = True 30 | self.u3.save() 31 | return 32 | 33 | def tearDown(self): 34 | user_model.objects.all().delete() 35 | 36 | def testPages(self): 37 | response = self.client.get('/') 38 | self.failUnlessEqual(response.status_code, status.HTTP_200_OK) 39 | response = self.client.get('/about') 40 | self.failUnlessEqual(response.status_code, status.HTTP_200_OK) 41 | response = self.client.get('/robots.txt') 42 | self.failUnlessEqual(response.status_code, status.HTTP_200_OK) 43 | response = self.client.login(username='user1', password='pass') 44 | response = self.client.get('/', {}) 45 | self.failUnlessEqual(response.status_code, status.HTTP_200_OK) 46 | self.client.logout() 47 | 48 | -------------------------------------------------------------------------------- /myproject/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import * 2 | from myproject.main import views 3 | from django.views.generic import RedirectView, TemplateView 4 | from django.core.urlresolvers import reverse_lazy 5 | from django.contrib.auth.decorators import login_required 6 | 7 | 8 | urlpatterns = patterns('', 9 | url(r'^$', views.HomeView.as_view(), name='home'), 10 | # The /about URL is used for healthchecks 11 | url(r'^about/?$', TemplateView.as_view(template_name="main/about.html"), name='about'), 12 | 13 | url(r'^jsi18n', 'myproject.main.views.i18n_javascript'), 14 | url(r'^admin/jsi18n', 'myproject.main.views.i18n_javascript'), 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /myproject/main/views.py: -------------------------------------------------------------------------------- 1 | 2 | from django.shortcuts import render_to_response 3 | from django.template import RequestContext 4 | from django.views.generic import DetailView, TemplateView 5 | from django.core.urlresolvers import reverse 6 | from django.utils.decorators import method_decorator 7 | from django.contrib import admin 8 | from django.conf import settings 9 | from django.views.generic import View 10 | from django.contrib import messages 11 | from django.views.decorators.csrf import ensure_csrf_cookie 12 | 13 | from myproject.main.models import * 14 | from myproject.main.forms import * 15 | 16 | AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') 17 | 18 | 19 | class HomeView(View): 20 | def get(self, request): 21 | production = settings.PRODUCTION 22 | user = request.user 23 | 24 | if user.is_authenticated(): 25 | template = 'main/index.html' 26 | else: 27 | template = 'main/landing.html' 28 | 29 | return render_to_response(template, locals(), context_instance = RequestContext(request)) 30 | 31 | def i18n_javascript(request): 32 | return admin.site.i18n_javascript(request) 33 | 34 | -------------------------------------------------------------------------------- /myproject/static/js/contact_me.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $("input,textarea").jqBootstrapValidation({ 4 | preventSubmit: true, 5 | submitError: function($form, event, errors) { 6 | // additional error messages or events 7 | }, 8 | submitSuccess: function($form, event) { 9 | event.preventDefault(); // prevent default submit behaviour 10 | // get values from FORM 11 | var name = $("input#name").val(); 12 | var email = $("input#email").val(); 13 | var message = $("textarea#message").val(); 14 | var firstName = name; // For Success/Failure Message 15 | // Check for white space in name for Success/Fail message 16 | if (firstName.indexOf(' ') >= 0) { 17 | firstName = name.split(' ').slice(0, -1).join(' '); 18 | } 19 | $.ajax({ 20 | url: "/api/v1/message", 21 | type: "POST", 22 | data: { 23 | name: name, 24 | email: email, 25 | message: message, 26 | csrfmiddlewaretoken: '{{ csrf_token }}' 27 | }, 28 | cache: false, 29 | success: function() { 30 | // Success message 31 | $('#success').html("
    "); 32 | $('#success > .alert-success').html(""); 34 | $('#success > .alert-success') 35 | .append("Your message has been sent. "); 36 | $('#success > .alert-success') 37 | .append('
    '); 38 | 39 | //clear all fields 40 | $('#contactForm').trigger("reset"); 41 | }, 42 | error: function() { 43 | // Fail message 44 | $('#success').html("
    "); 45 | $('#success > .alert-danger').html(""); 47 | $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that something is wrong. Please try again later!"); 48 | $('#success > .alert-danger').append('
    '); 49 | //clear all fields 50 | $('#contactForm').trigger("reset"); 51 | }, 52 | }) 53 | }, 54 | filter: function() { 55 | return $(this).is(":visible"); 56 | }, 57 | }); 58 | 59 | $("a[data-toggle=\"tab\"]").click(function(e) { 60 | e.preventDefault(); 61 | $(this).tab("show"); 62 | }); 63 | }); 64 | 65 | 66 | /*When clicking on Full hide fail/success boxes */ 67 | $('#name').focus(function() { 68 | $('#success').html(''); 69 | }); 70 | -------------------------------------------------------------------------------- /myproject/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load bootstrap3 %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | MySite 16 | 17 | {% bootstrap_css %} 18 | 19 | 20 | {% block media %} {% endblock %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 56 | 57 | {% block content %} 58 | {% endblock %} 59 | 60 | 61 |
    62 | 66 | 75 |
    76 | 77 | 78 |
    79 | 80 | 81 | 82 |
    83 | 84 | 85 | 86 | {% bootstrap_javascript %} 87 | 88 | {% block js %} {% endblock %} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /myproject/templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | 3 | 4 | Disallow: /static/ 5 | 6 | Disallow: /api/ 7 | Disallow: /api-auth-token/ 8 | 9 | -------------------------------------------------------------------------------- /myproject/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.views.generic import TemplateView 3 | 4 | from django.contrib import admin 5 | 6 | from myproject.main.api_views import APIMessageViewSet 7 | from authentication.api_views import AccountViewSet 8 | 9 | 10 | # Rest APIs 11 | # ========= 12 | from rest_framework import routers 13 | 14 | # Routers provide an easy way of automatically determining the URL conf 15 | v1_api_router = routers.DefaultRouter(trailing_slash=False) 16 | v1_api_router.register(r'message', APIMessageViewSet) 17 | v1_api_router.register(r'account', AccountViewSet) 18 | 19 | admin.autodiscover() 20 | 21 | urlpatterns = patterns('', 22 | # Examples: 23 | 24 | url(r'^', include('myproject.main.urls')), 25 | url(r'^account/', include('authentication.urls')), 26 | url(r'^admin/', include(admin.site.urls)), 27 | 28 | url(r'^api/v1/', include(v1_api_router.urls)), 29 | url(r'^api/v1/auth/', include('authentication.api_urls')), 30 | 31 | url('^robots.txt$', TemplateView.as_view(template_name="robots.txt")), 32 | 33 | ) 34 | -------------------------------------------------------------------------------- /myproject/wsgi.py.bak: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for foobar 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.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | import sys 12 | 13 | #sys.path.insert(0, '/var/app') 14 | 15 | from django.core.wsgi import get_wsgi_application 16 | 17 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.dev-local" 18 | "") 19 | 20 | application = get_wsgi_application() 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.8.1 2 | boto==2.38.0 3 | django-filter==0.9.2 4 | django-bootstrap3==5.4.0 5 | djangorestframework==3.1.1 6 | django-cors-headers==1.0.0 7 | requests==2.6.0 8 | pytz==2015.2 9 | psycopg2==2.6 10 | -------------------------------------------------------------------------------- /runserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /var/app 4 | export PYTHONPATH=/var/app;$PYTHONPATH 5 | 6 | python manage.py migrate --noinput 7 | python manage.py initadmin 8 | python manage.py runserver 0.0.0.0:8080 -------------------------------------------------------------------------------- /settings/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dkarchmer' 2 | -------------------------------------------------------------------------------- /settings/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for MySite project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | import sys 14 | import socket 15 | 16 | SITE_ID = 1 17 | 18 | # Assumes 19 | # - base 20 | # --- myproject 21 | # -------- urls.py 22 | # -------- etc. 23 | # --- server 24 | # -------- settings 25 | # ------------- prod.py 26 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 27 | BASE_PROJECT_DIR = os.path.join(BASE_DIR, 'myproject') 28 | print('BASE_PROJECT_DIR = ' + BASE_PROJECT_DIR) 29 | 30 | ADMINS = ( 31 | # ('Your Name', 'your_email@domain.com'), 32 | ('admin', 'admin@mysite.com'), 33 | ) 34 | ADMIN_USERNAME = 'admin' 35 | ADMIN_EMAIL = 'admin@mysite.com' 36 | ADMIN_INITIAL_PASSWORD = 'admin' # To be changed after first login by admin 37 | 38 | 39 | # Quick-start development settings - unsuitable for production 40 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 41 | 42 | # SECURITY WARNING: keep the secret key used in production secret! 43 | SECRET_KEY = 'gp-kui+1mi6d!^u$*wvfoo^ostr1+(pns*=q3p%=fsoppe5123' 44 | 45 | FIXTURE_DIRS = ( 46 | #os.path.join(BASE_PROJECT_DIR, 'fixtures'), 47 | ) 48 | 49 | 50 | # Application definition 51 | 52 | DJANGO_APPS = ( 53 | 'django.contrib.auth', 54 | 'django.contrib.contenttypes', 55 | 'django.contrib.sessions', 56 | 'django.contrib.sites', 57 | 'django.contrib.messages', 58 | 'django.contrib.staticfiles', 59 | 60 | # Admin panel and documentation: 61 | 'django.contrib.admin', 62 | # 'django.contrib.admindocs', 63 | ) 64 | 65 | THIRD_PARTY_APPS = ( 66 | 'bootstrap3', 67 | 'rest_framework', 68 | 'rest_framework.authtoken', 69 | ) 70 | 71 | # Apps specific for this project go here. 72 | LOCAL_APPS = ( 73 | 'myproject.main', 74 | ) 75 | 76 | INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS 77 | 78 | MIDDLEWARE_CLASSES = ( 79 | 'django.contrib.sessions.middleware.SessionMiddleware', 80 | 'django.middleware.common.CommonMiddleware', 81 | 'django.middleware.csrf.CsrfViewMiddleware', 82 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 83 | 'django.contrib.messages.middleware.MessageMiddleware', 84 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 85 | 'django.middleware.locale.LocaleMiddleware', 86 | ) 87 | 88 | ROOT_URLCONF = 'myproject.urls' 89 | 90 | #WSGI_APPLICATION = 'myproject.wsgi.application' 91 | WSGI_APPLICATION = 'application.application' 92 | 93 | TEST_RUNNER = 'django.test.runner.DiscoverRunner' 94 | 95 | local_ip = str(socket.gethostbyname(socket.gethostname())) 96 | 97 | print ('hostname: ' + socket.gethostname()) 98 | print ('hostbyname: ' + local_ip) 99 | 100 | # Internationalization 101 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 102 | 103 | LANGUAGE_CODE = 'en-us' 104 | 105 | TIME_ZONE = 'UTC' 106 | 107 | USE_I18N = True 108 | 109 | USE_L10N = True 110 | 111 | USE_TZ = True 112 | 113 | # List of callables that know how to import templates from various sources. 114 | TEMPLATE_LOADERS = ( 115 | 'django.template.loaders.filesystem.Loader', 116 | 'django.template.loaders.app_directories.Loader', 117 | ) 118 | 119 | template_path = os.path.join(BASE_PROJECT_DIR, 'templates') 120 | print('template_path = ' + template_path) 121 | 122 | TEMPLATE_DIRS = ( 123 | template_path, 124 | ) 125 | 126 | 127 | 128 | 129 | # Static files (CSS, JavaScript, Images) 130 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 131 | 132 | static_path = os.path.join(BASE_PROJECT_DIR, 'static') 133 | print('static_path = ' + static_path) 134 | STATICFILES_DIRS = ( 135 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 136 | # Always use forward slashes, even on Windows. 137 | # Don't forget to use absolute paths, not relative paths. 138 | ('images', os.path.join(static_path, 'images')), 139 | ('js', os.path.join(static_path, 'js')), 140 | ('css', os.path.join(static_path, 'css')), 141 | ) 142 | STATIC_ROOT = 'staticfiles' 143 | STATIC_URL = '/static/' 144 | 145 | STATICFILES_FINDERS = ( 146 | 'django.contrib.staticfiles.finders.FileSystemFinder', 147 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 148 | ) 149 | 150 | 151 | # Boto S3 Configuration 152 | DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' 153 | 154 | 155 | REST_FRAMEWORK = { 156 | # Use hyperlinked styles by default. 157 | # Only used if the `serializer_class` attribute is not set on a view. 158 | 'DEFAULT_MODEL_SERIALIZER_CLASS': 159 | 'rest_framework.serializers.HyperlinkedModelSerializer', 160 | 161 | # Use Django's standard `django.contrib.auth` permissions, 162 | # or allow read-only access for unauthenticated users. 163 | 'DEFAULT_PERMISSION_CLASSES': ( 164 | 'rest_framework.permissions.AllowAny', 165 | ), 166 | 167 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 168 | 'rest_framework.authentication.TokenAuthentication', 169 | 'rest_framework.authentication.SessionAuthentication', 170 | 'rest_framework.authentication.BasicAuthentication', 171 | ), 172 | 173 | 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',), 174 | 175 | 'PAGINATE_BY': 10, 176 | 'PAGINATE_BY_PARAM': 'page_size', 177 | 'MAX_PAGINATE_BY': 100, 178 | } 179 | 180 | # one email per XX seconds 181 | LOGLIMIT_RATE = 10 182 | 183 | # uses keys to detect which errors are the same 184 | LOGLIMIT_MAX_KEYS = 100 185 | 186 | # uses cache if it's available 187 | LOGLIMIT_CACHE_PREFIX = 'LOGLIMIT' 188 | 189 | LOGGING = { 190 | 'version': 1, 191 | 'disable_existing_loggers': False, 192 | 'handlers': { 193 | 'mail_admins': { 194 | 'level': 'ERROR', 195 | 'class': 'django.utils.log.AdminEmailHandler', 196 | }, 197 | 'null': { 198 | 'level': 'ERROR', 199 | 'class': 'logging.NullHandler', 200 | }, 201 | }, 202 | 'loggers': { 203 | 'django.request':{ 204 | 'handlers': ['mail_admins'], 205 | 'level': 'ERROR', 206 | 'propagate': False, 207 | }, 208 | 'django.security.DisallowedHost': { 209 | 'handlers': ['null'], 210 | 'propagate': False, 211 | }, 212 | 'django.security': { 213 | 'handlers': ['mail_admins'], 214 | 'level': 'ERROR', 215 | 'propagate': False, 216 | }, 217 | }, 218 | } 219 | 220 | BOOTSTRAP3 = { 221 | 222 | # The URL to the jQuery JavaScript file 223 | 'jquery_url': '//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js', 224 | 225 | # The Bootstrap base URL 226 | 'base_url': '//netdna.bootstrapcdn.com/bootstrap/3.3.1/', 227 | 228 | # The complete URL to the Bootstrap CSS file (None means derive it from base_url) 229 | 'css_url': None, 230 | 231 | # The complete URL to the Bootstrap CSS file (None means no theme) 232 | 'theme_url': '//maxcdn.bootstrapcdn.com/bootswatch/3.3.1/flatly/bootstrap.min.css', 233 | 234 | # The complete URL to the Bootstrap JavaScript file (None means derive it from base_url) 235 | 'javascript_url': None, 236 | 237 | # Put JavaScript in the HEAD section of the HTML document (only relevant if you use bootstrap3.html) 238 | 'javascript_in_head': False, 239 | 240 | # Include jQuery with Bootstrap JavaScript (affects django-bootstrap3 template tags) 241 | 'include_jquery': True, 242 | 243 | # Label class to use in horizontal forms 244 | 'horizontal_label_class': 'col-md-2', 245 | 246 | # Field class to use in horiozntal forms 247 | 'horizontal_field_class': 'col-md-4', 248 | 249 | # Set HTML required attribute on required fields 250 | 'set_required': True, 251 | 252 | # Set placeholder attributes to label if no placeholder is provided 253 | 'set_placeholder': True, 254 | 255 | # Class to indicate required (better to set this in your Django form) 256 | 'required_css_class': '', 257 | 258 | # Class to indicate error (better to set this in your Django form) 259 | 'error_css_class': 'has-error', 260 | 261 | # Class to indicate success, meaning the field has valid input (better to set this in your Django form) 262 | 'success_css_class': 'has-success', 263 | 264 | # Renderers (only set these if you have studied the source and understand the inner workings) 265 | 'formset_renderers':{ 266 | 'default': 'bootstrap3.renderers.FormsetRenderer', 267 | }, 268 | 'form_renderers': { 269 | 'default': 'bootstrap3.renderers.FormRenderer', 270 | }, 271 | 'field_renderers': { 272 | 'default': 'bootstrap3.renderers.FieldRenderer', 273 | 'inline': 'bootstrap3.renderers.InlineFieldRenderer', 274 | }, 275 | } 276 | 277 | 278 | 279 | # Overwrite User Model with 'Authentication.Account' 280 | # 281 | INSTALLED_APPS += ('authentication',) 282 | AUTH_USER_MODEL = 'authentication.Account' 283 | LOGIN_URL = '/account/login/' 284 | LOGIN_REDIRECT_URL = '/' 285 | LOGIN_ERROR_URL = '/account/login-error/' 286 | 287 | -------------------------------------------------------------------------------- /settings/dev-local.py: -------------------------------------------------------------------------------- 1 | from settings.common import * 2 | 3 | PRODUCTION = False 4 | 5 | # SECURITY WARNING: don't run with debug turned on in production! 6 | DEBUG = True 7 | 8 | TEMPLATE_DEBUG = True 9 | 10 | ALLOWED_HOSTS=['*'] 11 | 12 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 13 | 14 | print('Using native server (sqlite3)') 15 | 16 | DATABASES = { 17 | 'default': { 18 | 'ENGINE': 'django.db.backends.sqlite3', 19 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 20 | 'USER': '', # Not used with sqlite3. 21 | 'PASSWORD': '', # Not used with sqlite3. 22 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 23 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /settings/production.py: -------------------------------------------------------------------------------- 1 | from settings.common import * 2 | 3 | PRODUCTION = True 4 | 5 | # SECURITY WARNING: don't run with debug turned on in production! 6 | DEBUG = False 7 | 8 | TEMPLATE_DEBUG = False 9 | 10 | 11 | 12 | ''' 13 | For Production, we assume 14 | 1.- AWS Elastic Beanstack (EB) 15 | 2.- WebFactions 16 | ''' 17 | 18 | ''' 19 | The following comes from: 20 | http://rickchristianson.wordpress.com/2013/10/31/getting-a-django-app-to-use-https-on-aws-elastic-beanstalk/ 21 | ''' 22 | #SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 23 | #SESSION_COOKIE_SECURE = True 24 | #CSRF_COOKIE_SECURE = True 25 | 26 | ''' 27 | The following comes from: 28 | http://security.stackexchange.com/questions/8964/trying-to-make-a-django-based-site-use-https-only-not-sure-if-its-secure/8970#comment80472_8970 29 | ''' 30 | #os.environ['HTTPS'] = "on" 31 | 32 | ALLOWED_HOSTS=['*' ] 33 | DATABASES = { 34 | 'default': { 35 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 36 | #'ENGINE': 'django.db.backends.mysql', 37 | 'NAME': os.environ['RDS_DB_NAME'], 38 | 'USER': os.environ['RDS_USERNAME'], 39 | 'PASSWORD': os.environ['RDS_PASSWORD'], 40 | 'HOST': os.environ['RDS_HOSTNAME'], 41 | 'PORT': os.environ['RDS_PORT'], 42 | } 43 | } 44 | 45 | # BOTO and django-storages 46 | STATIC_DIRECTORY = 'static/' 47 | #STATIC_URL = S3_STATIC_URL + STATIC_DIRECTORY 48 | 49 | # https://github.com/ottoyiu/django-cors-headers/ 50 | INSTALLED_APPS += ('corsheaders',) 51 | MIDDLEWARE_CLASSES = ('corsheaders.middleware.CorsMiddleware',) + MIDDLEWARE_CLASSES 52 | CORS_ALLOW_CREDENTIALS = True 53 | CORS_ORIGIN_ALLOW_ALL = True 54 | CORS_URLS_REGEX = r'^/api.*$' 55 | #CORS_ORIGIN_WHITELIST = ('*',) 56 | 57 | 58 | print('DONE with settings') --------------------------------------------------------------------------------