├── app ├── __init__.py ├── tests │ ├── __init__.py │ ├── integration.py │ └── unit.py ├── routers.py ├── assets │ ├── toaster.js │ ├── githubCorners.css │ ├── index.css │ ├── toaster.css │ ├── googleAnalytics.js │ └── index.js ├── views.py └── templates │ └── index.html ├── core ├── __init__.py ├── wsgi.py ├── urls.py ├── tests.py ├── utils.py └── settings.py ├── runtime.txt ├── Procfile ├── requirements.txt ├── .idea ├── codeStyles │ └── codeStyleConfig.xml ├── libraries │ └── Google_App_Engine_SDK.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── modules.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── webResources.xml └── djskgen.iml ├── compose.sh ├── Dockerfile ├── .coveragerc ├── .editorconfig ├── .env.example ├── docker-compose.dev.yml ├── .dockerignore ├── docker-compose.yml ├── .travis.yml ├── README.md ├── .gitignore └── manage.py /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.9 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn core.wsgi --log-file - 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.0.13 2 | djangorestframework==3.9.1 3 | gunicorn==19.7.1 4 | whitenoise==3.3.1 5 | -------------------------------------------------------------------------------- /app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .integration import TestIndexAPI, TestSecretKeysViewAPI 2 | from .unit import TestIndexView, TestSecretKeysView 3 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /compose.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${1}" == 'dev' ]; then 4 | shift 5 | docker-compose -f docker-compose.yml -f docker-compose.dev.yml ${@} 6 | else 7 | docker-compose ${@} 8 | fi 9 | -------------------------------------------------------------------------------- /.idea/libraries/Google_App_Engine_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 6 | 7 | application = get_wsgi_application() 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/routers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.routers import DefaultRouter 2 | 3 | from app.views import SecretKeyView 4 | 5 | router = DefaultRouter() 6 | router.register('secret_key', SecretKeyView, base_name='secret_key') 7 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6.9 2 | LABEL maintainer="Jourdan Rodrigues " 3 | 4 | WORKDIR /app/ 5 | 6 | COPY requirements.txt . 7 | 8 | RUN pip install --no-cache-dir -q -r requirements.txt 9 | 10 | COPY . . 11 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | pragma: no cover 4 | def __unicode__ 5 | def __repr__ 6 | def __str__ 7 | AssertionError 8 | NotImplementedError 9 | 10 | omit = 11 | manage.py 12 | app/*/migrations/* 13 | 14 | show_missing = True 15 | -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | 3 | from app.routers import router 4 | from app.views import IndexView 5 | 6 | urlpatterns = [ 7 | url(r'^$', IndexView.as_view(), name='index'), 8 | url(r'^api/', include(router.urls)), 9 | ] 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [{*.{html,js,yml,css,sh},.coveragerc}] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SECRET_KEY={Will be automatically generated when not in PRODUCTION; required} 2 | PRODUCTION={Boolean as 0 (false) or 1 (true); false by default} 3 | DEBUG={Boolean as 0 (false) or 1 (true); false if in PRODUCTION, else true} 4 | ALLOWED_HOSTS={Lists of hosts separated by comma} 5 | GOOGLE_ANALYTICS_ID={} 6 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | # Do not use this config file alone 4 | # Instead, do "./compose.sh dev [compose commands]" 5 | 6 | services: 7 | 8 | server: 9 | command: python manage.py runserver 0.0.0.0:8000 10 | environment: 11 | - PRODUCTION=0 12 | volumes: 13 | - .:/app 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Files 2 | app.yaml 3 | compose.sh 4 | .coveragerc 5 | Dockerfile 6 | .dockerignore 7 | docker-compose.yml 8 | docker-compose.dev.yml 9 | .editorconfig 10 | .env 11 | .env.example 12 | .gitignore 13 | Procfile 14 | README.md 15 | runtime.txt 16 | .travis.yml 17 | 18 | # Directories 19 | .git/ 20 | .idea/ 21 | media/ 22 | static/ 23 | [v]env/ 24 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | server: 6 | container_name: djskgen_server 7 | build: . 8 | command: gunicorn -b 0.0.0.0 core.wsgi --log-file - 9 | env_file: 10 | - .env 11 | environment: 12 | - PRODUCTION=1 13 | # ALLOWED_HOSTS=* # Must set the allowed host(s) in the production environment through ".env" file 14 | ports: 15 | - "8000:8000" 16 | -------------------------------------------------------------------------------- /.idea/webResources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /core/tests.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from unittest import TestCase 3 | from wsgiref.simple_server import make_server 4 | 5 | from core.wsgi import application 6 | 7 | 8 | class TestServer(TestCase): 9 | def test_that_server_turns_on_ok_through_wsgi(self): 10 | httpd = make_server('', 8912, application) 11 | Thread(target=httpd.serve_forever).start() # Start in a thread because it blocks the execution 12 | httpd.shutdown() 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.6" 5 | 6 | install: "pip install -r requirements.txt coverage" 7 | 8 | env: 9 | global: 10 | - SECRET_KEY="u@)7349fg!_dzzk^ok(!jyf7hb7jpagnkdpy$zjniz3=2=7snh" 11 | matrix: 12 | - DEBUG=0 PRODUCTION=0 13 | - DEBUG=0 PRODUCTION=1 14 | - DEBUG=1 PRODUCTION=1 15 | - DEBUG=1 PRODUCTION=0 16 | 17 | script: 18 | - coverage run manage.py test 19 | 20 | after_success: 21 | - bash <(curl -s https://codecov.io/bash) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Django Secret Key Generator 4 | 5 | [![Build Status][travis_badge]][travis_link] 6 | [![codecov][codecov_badge]][codecov_link] 7 | 8 | Live on [https://djskgen.herokuapp.com/](https://djskgen.herokuapp.com/). 9 |
10 | 11 | [travis_badge]: https://travis-ci.org/jlhg/djskgen.svg?branch=master 12 | [travis_link]: https://travis-ci.org/jlhg/djskgen 13 | [codecov_badge]: https://codecov.io/gh/jlhg/djskgen/branch/master/graph/badge.svg 14 | [codecov_link]: https://codecov.io/gh/jlhg/djskgen 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files 2 | *.egg* 3 | .env 4 | *.py[cod] 5 | 6 | # Directories 7 | media/ 8 | static/ 9 | [v]env/ 10 | 11 | # JetBrains settings: https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore 12 | # User-specific stuff 13 | .idea/**/workspace.xml 14 | .idea/**/tasks.xml 15 | .idea/**/dictionaries 16 | .idea/**/shelf 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | 26 | -------------------------------------------------------------------------------- /app/assets/toaster.js: -------------------------------------------------------------------------------- 1 | // https://www.w3schools.com/howto/howto_js_snackbar.asp 2 | 3 | window.showToaster = (function () { 4 | return function _showToaster(message) { 5 | var toaster = document.querySelector('.toaster'); 6 | 7 | if (/show/.test(toaster.classList)) { 8 | return 9 | } 10 | 11 | toaster.innerText = message; 12 | toaster.className += ' show'; 13 | 14 | setTimeout(function _hideToaster() { 15 | toaster.innerText = message; 16 | toaster.className = toaster.className.replace(' show', ''); 17 | }, 3000); 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | message = ( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | 'available on your PYTHONPATH environment variable? Did you ' 13 | 'forget to activate a virtual environment?' 14 | ) 15 | raise ImportError(message) from exc 16 | execute_from_command_line(sys.argv) 17 | -------------------------------------------------------------------------------- /app/tests/integration.py: -------------------------------------------------------------------------------- 1 | from django.test import SimpleTestCase 2 | from rest_framework import status 3 | from rest_framework.test import APIClient 4 | 5 | 6 | class APITestCase(SimpleTestCase): 7 | client = APIClient() 8 | 9 | 10 | class TestIndexAPI(APITestCase): 11 | def test_that_it_responds_with_ok_status(self): 12 | response = self.client.get('/') 13 | 14 | self.assertEqual(response.status_code, status.HTTP_200_OK) 15 | 16 | 17 | class TestSecretKeysViewAPI(APITestCase): 18 | def test_that_it_responds_with_ok_status(self): 19 | response = self.client.get('/api/secret_key/') 20 | 21 | self.assertEqual(response.status_code, status.HTTP_200_OK) 22 | -------------------------------------------------------------------------------- /app/assets/githubCorners.css: -------------------------------------------------------------------------------- 1 | .github-corner:hover .octo-arm { 2 | animation: octocat-wave 560ms ease-in-out 3 | } 4 | 5 | .github-corner svg { 6 | top: 0; 7 | right: 0; 8 | border: 0; 9 | color: #fff; 10 | fill: #151513; 11 | position: absolute; 12 | } 13 | 14 | .github-corner .octo-arm { 15 | transform-origin: 130px 106px; 16 | } 17 | 18 | @keyframes octocat-wave { 19 | 0%, 100% { 20 | transform: rotate(0) 21 | } 22 | 20%, 60% { 23 | transform: rotate(-25deg) 24 | } 25 | 40%, 80% { 26 | transform: rotate(10deg) 27 | } 28 | } 29 | 30 | @media (max-width: 500px) { 31 | .github-corner:hover .octo-arm { 32 | animation: none 33 | } 34 | 35 | .github-corner .octo-arm { 36 | animation: octocat-wave 560ms ease-in-out 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/assets/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | outline: none; 3 | } 4 | 5 | body { 6 | display: flex; 7 | align-items: center; 8 | flex-direction: column; 9 | } 10 | 11 | .secret-key { 12 | display: flex; 13 | margin-top: 1em; 14 | min-height: 6em; 15 | align-items: center; 16 | flex-direction: column; 17 | justify-content: space-between; 18 | } 19 | 20 | .secret-key input { 21 | padding: 0; 22 | height: 2em; 23 | cursor: pointer; 24 | font-size: 12pt; 25 | border-radius: 1em; 26 | text-align: center; 27 | border: 1px solid gray; 28 | font-family: Courier, sans-serif; 29 | -webkit-tap-highlight-color: transparent; 30 | } 31 | 32 | .secret-key button { 33 | height: 2em; 34 | font-size: 12pt; 35 | cursor: pointer; 36 | padding: 0 1em; 37 | text-align: center; 38 | border-radius: 1em; 39 | border: 1px solid gray; 40 | background-color: transparent; 41 | } 42 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.views.generic import TemplateView 3 | from rest_framework.response import Response 4 | from rest_framework.viewsets import GenericViewSet, mixins 5 | 6 | from core.utils import generate_secret_key 7 | 8 | 9 | class IndexView(TemplateView): 10 | template_name = 'index.html' 11 | 12 | def get_context_data(self, **kwargs): 13 | context = super().get_context_data(**kwargs) 14 | 15 | analytics = bool(int(self.request.GET.get('analytics', 1))) 16 | if analytics: 17 | context['GOOGLE_ANALYTICS_ID'] = settings.GOOGLE_ANALYTICS_ID 18 | return context 19 | 20 | 21 | class SecretKeyView(GenericViewSet, mixins.ListModelMixin): 22 | def get_queryset(self): 23 | pass # pragma: no cover 24 | 25 | def list(self, request, *args, **kwargs): 26 | return Response({'secret_key': generate_secret_key()}) 27 | -------------------------------------------------------------------------------- /app/assets/toaster.css: -------------------------------------------------------------------------------- 1 | /* https://www.w3schools.com/howto/howto_js_snackbar.asp */ 2 | .toaster { 3 | left: 50%; 4 | z-index: 1; 5 | color: #fff; 6 | bottom: 30px; 7 | padding: 16px; 8 | position: fixed; 9 | min-width: 250px; 10 | visibility: hidden; 11 | text-align: center; 12 | border-radius: 2px; 13 | margin-left: -125px; 14 | background-color: #333; 15 | } 16 | 17 | .toaster.show { 18 | visibility: visible; 19 | animation: fadeIn .5s, fadeOut .5s 2.5s; 20 | } 21 | 22 | @-webkit-keyframes fadeIn { 23 | from { 24 | bottom: 0; 25 | opacity: 0; 26 | } 27 | to { 28 | bottom: 30px; 29 | opacity: 1; 30 | } 31 | } 32 | 33 | @keyframes fadeIn { 34 | from { 35 | bottom: 0; 36 | opacity: 0; 37 | } 38 | to { 39 | bottom: 30px; 40 | opacity: 1; 41 | } 42 | } 43 | 44 | @-webkit-keyframes fadeOut { 45 | from { 46 | bottom: 30px; 47 | opacity: 1; 48 | } 49 | to { 50 | bottom: 0; 51 | opacity: 0; 52 | } 53 | } 54 | 55 | @keyframes fadeOut { 56 | from { 57 | bottom: 30px; 58 | opacity: 1; 59 | } 60 | to { 61 | bottom: 0; 62 | opacity: 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from random import SystemRandom 4 | 5 | 6 | def generate_secret_key(): 7 | system_random = SystemRandom() 8 | allowed_chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' 9 | return ''.join([system_random.choice(allowed_chars) for _ in range(50)]) 10 | 11 | 12 | class EnvHelper: 13 | def __init__(self, env_file: str): 14 | self.env_file = env_file 15 | 16 | def read_env_file(self) -> None: # pragma: no cover 17 | try: 18 | with open(self.env_file) as f: 19 | content = f.read() 20 | except IOError: 21 | pass 22 | else: 23 | for line in content.splitlines(): 24 | match = re.match(r'\A(?P[A-Za-z_0-9]+)=(?P.*)\Z', 25 | re.sub(r'( +)?#(.+)?', '', line)) 26 | if match: 27 | os.environ.setdefault(*match.groupdict().values()) 28 | 29 | def set_secret_key(self) -> None: # pragma: no cover 30 | secret_key = generate_secret_key() 31 | os.environ.setdefault('SECRET_KEY', secret_key) 32 | 33 | with open(self.env_file, 'ab') as env: # pragma: no cover 34 | env.write(b'SECRET_KEY=' + secret_key.encode()) 35 | -------------------------------------------------------------------------------- /app/assets/googleAnalytics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.gtag = (function () { 4 | var gaId = _getParams('googleAnalytics')['id']; 5 | _loadGoogleAnalytics(gaId); 6 | 7 | window.dataLayer = window.dataLayer || []; 8 | 9 | function _gtag() { 10 | dataLayer.push(arguments); 11 | } 12 | 13 | _gtag('js', new Date()); 14 | _gtag('config', gaId); 15 | 16 | return _gtag; 17 | 18 | function _loadGoogleAnalytics(googleAnalyticsId) { 19 | var script = document.createElement('script'); 20 | script.src = '//www.googletagmanager.com/gtag/js?id=' + googleAnalyticsId; 21 | document.head.appendChild(script); 22 | } 23 | 24 | function _getParams(script_name) { 25 | var scripts = document.getElementsByTagName('script'); 26 | 27 | for (var i = 0; i < scripts.length; i++) { 28 | var regexp = new RegExp('/' + script_name + '(\\.[a-zA-Z0-9]{12})?\\.js'); 29 | var src = scripts[i].src; 30 | 31 | if (regexp.test(src)) { 32 | var queryParams = src.split('?')[1].split('&'); 33 | return queryParams.reduce(_parseParams, {}); 34 | } 35 | } 36 | 37 | return {}; 38 | } 39 | 40 | function _parseParams(params, value) { 41 | var keyValue = value.split('='); 42 | var _params = JSON.parse(JSON.stringify(params)); // Make a copy of params 43 | _params[keyValue[0]] = keyValue[1]; 44 | return _params 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /app/assets/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.generateSecretKey = (function () { 4 | return function _generateSecretKeys() { 5 | _getSecretKeysRequest(_listener); 6 | }; 7 | 8 | function _listener() { 9 | var _secretKeyWrapper = document.querySelector('.secret-key'); 10 | 11 | if (this.status === 200) { 12 | /** 13 | * @type {{secretKey}} 14 | */ 15 | var camelCaseResponse = _objectAttrsToCamelCase(JSON.parse(this.responseText)); 16 | _secretKeyWrapper.querySelector('input').value = '\'' + camelCaseResponse.secretKey + '\''; 17 | } 18 | } 19 | 20 | function _getSecretKeysRequest(listener) { 21 | var xhr = new XMLHttpRequest(); 22 | xhr.onload = listener; 23 | xhr.open('get', '/api/secret_key/', true); 24 | xhr.setRequestHeader('Content-Type', 'application/json'); 25 | xhr.send(); 26 | } 27 | 28 | function _objectAttrsToCamelCase(object) { 29 | var _object = {}; 30 | 31 | for (var key in object) { 32 | if (object.hasOwnProperty(key)) { 33 | var camelCaseKey = key.replace(/_([a-z])/g, _innerToUpperCase); 34 | _object[camelCaseKey] = object[key]; 35 | } 36 | } 37 | 38 | return _object; 39 | } 40 | 41 | function _innerToUpperCase(_, char) { 42 | return char.toUpperCase(); 43 | } 44 | })(); 45 | 46 | window.copySecretKey = (function () { 47 | return function _copySecretKey() { 48 | var input = document.querySelector('.secret-key input'); 49 | input.select(); 50 | if (document.queryCommandSupported('copy')) { 51 | document.execCommand('copy'); 52 | window.showToaster('Secret key copied to your clipboard!'); 53 | } 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /.idea/djskgen.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 40 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% load static %} 5 | 6 | 7 | Django secret key generator 8 | 9 | 10 | 11 | {% if GOOGLE_ANALYTICS_ID %} 12 | 13 | {% endif %} 14 | 15 | 16 | 17 | 18 |

Django Secret Key Generator

19 | 20 | 24 | 25 |
26 | 27 | 28 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/tests/unit.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from django.test import SimpleTestCase, RequestFactory 4 | from django.conf import settings 5 | 6 | from app.views import IndexView, SecretKeyView 7 | 8 | 9 | class TestIndexView(SimpleTestCase): 10 | def test_that_it_returns_ok_status_on_get(self): 11 | request = RequestFactory().get('/') 12 | 13 | response = IndexView(request=request).get(request) 14 | 15 | self.assertEqual(response.status_code, 200) 16 | 17 | def test_when_analytics_is_set_then_render_with_analytics(self): 18 | ga_id = 'google-analytics-id' 19 | request = RequestFactory().get('/') 20 | 21 | with mock.patch.object(settings, 'GOOGLE_ANALYTICS_ID', ga_id): 22 | response = IndexView(request=request).get(request) 23 | 24 | response.render() 25 | self.assertIn(ga_id, response.rendered_content) 26 | 27 | def test_when_analytics_is_set_and_query_param_disables_it_then_render_without_analytics(self): 28 | ga_id = 'google-analytics-id' 29 | request = RequestFactory().get('/', {'analytics': '0'}) 30 | 31 | with mock.patch.object(settings, 'GOOGLE_ANALYTICS_ID', ga_id): 32 | response = IndexView(request=request).get(request) 33 | 34 | response.render() 35 | self.assertNotIn(ga_id, response.rendered_content) 36 | 37 | 38 | class TestSecretKeysView(SimpleTestCase): 39 | def test_that_it_returns_secret_key_in_response(self): 40 | request = RequestFactory().post('/') 41 | 42 | response = SecretKeyView().list(request) 43 | 44 | self.assertIn('secret_key', response.data) 45 | 46 | def test_that_it_returns_a_secret_key_with_fifty_characters(self): 47 | request = RequestFactory().post('/') 48 | 49 | response = SecretKeyView().list(request) 50 | 51 | self.assertEqual(len(response.data['secret_key']), 50) 52 | 53 | def test_that_it_returns_a_secret_key_as_string(self): 54 | request = RequestFactory().post('/') 55 | 56 | response = SecretKeyView().list(request) 57 | 58 | self.assertIsInstance(response.data['secret_key'], str) 59 | -------------------------------------------------------------------------------- /core/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from core.utils import EnvHelper 5 | 6 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 | 8 | env_helper = EnvHelper(env_file=os.path.join(BASE_DIR, '.env')) 9 | env_helper.read_env_file() 10 | 11 | TEST = 'test' in sys.argv 12 | 13 | PRODUCTION = bool(int(os.getenv('PRODUCTION', 0))) 14 | DEBUG = bool(int(os.getenv('DEBUG', 0 if PRODUCTION else 1))) 15 | 16 | SECRET_KEY = os.getenv('SECRET_KEY') 17 | 18 | # If in production and SECRET_KEY is not set, let it fail 19 | if not SECRET_KEY and PRODUCTION: 20 | env_helper.set_secret_key() # pragma: no cover 21 | 22 | DJANGO_APPS = [ 23 | 'django.contrib.auth', 24 | 'django.contrib.contenttypes', 25 | 'django.contrib.staticfiles', 26 | ] 27 | 28 | THIRD_PARTY_APPS = [ 29 | 'rest_framework', 30 | ] 31 | 32 | LOCAL_APPS = [ 33 | 'app', 34 | ] 35 | 36 | INSTALLED_APPS = LOCAL_APPS + THIRD_PARTY_APPS + DJANGO_APPS 37 | 38 | ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '*' if not PRODUCTION else '').split(',') 39 | 40 | TIME_ZONE = 'UTC' 41 | 42 | LANGUAGE_CODE = 'en-us' 43 | 44 | USE_I18N = True 45 | USE_L10N = True 46 | USE_TZ = False 47 | 48 | STATIC_URL = '/static/' 49 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 50 | STATICFILES_DIRS = [ 51 | os.path.join(BASE_DIR, 'app', 'assets'), 52 | ] 53 | 54 | if not TEST: # pragma: no cover 55 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 56 | 57 | MIDDLEWARE = ( 58 | 'django.middleware.security.SecurityMiddleware', 59 | 'whitenoise.middleware.WhiteNoiseMiddleware', 60 | 'django.contrib.sessions.middleware.SessionMiddleware', 61 | 'django.middleware.common.CommonMiddleware', 62 | 'django.middleware.csrf.CsrfViewMiddleware', 63 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 64 | 'django.contrib.messages.middleware.MessageMiddleware', 65 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 66 | ) 67 | 68 | ROOT_URLCONF = 'core.urls' 69 | 70 | WSGI_APPLICATION = 'core.wsgi.application' 71 | 72 | TEMPLATES = [ 73 | { 74 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 75 | 'DIRS': [os.path.join(BASE_DIR, 'app', 'templates')], 76 | 'APP_DIRS': True, 77 | 'OPTIONS': {}, 78 | }, 79 | ] 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.sqlite3', 84 | 'NAME': ':memory:', 85 | } 86 | } 87 | 88 | GOOGLE_ANALYTICS_ID = os.getenv('GOOGLE_ANALYTICS_ID') if PRODUCTION and not TEST else None 89 | --------------------------------------------------------------------------------