@ -0,0 +1 @@ | |||
__pycache__/ |
@ -0,0 +1,3 @@ | |||
from django.contrib import admin | |||
# Register your models here. |
@ -0,0 +1,5 @@ | |||
from django import forms | |||
class EmailForm(forms.Form): | |||
email = forms.EmailField() |
@ -0,0 +1,3 @@ | |||
from django.db import models | |||
# Create your models here. |
@ -0,0 +1,3 @@ | |||
from django.test import TestCase | |||
# Create your tests here. |
@ -0,0 +1,9 @@ | |||
from django.conf.urls import url | |||
from hc.accounts import views | |||
urlpatterns = [ | |||
url(r'^login/$', views.login, name="hc-login"), | |||
url(r'^login_link_sent/$', views.login_link_sent, name="hc-login-link-sent"), | |||
url(r'^check_token/([\w-]+)/$', views.check_token, name="hc-check-token"), | |||
] |
@ -0,0 +1,49 @@ | |||
import uuid | |||
from django.conf import settings | |||
from django.contrib.auth.models import User | |||
from django.core.mail import send_mail | |||
from django.core.urlresolvers import reverse | |||
from django.shortcuts import redirect, render | |||
from hc.accounts.forms import EmailForm | |||
def login(request): | |||
if request.method == 'POST': | |||
# create a form instance and populate it with data from the request: | |||
form = EmailForm(request.POST) | |||
# check whether it's valid: | |||
if form.is_valid(): | |||
email = form.cleaned_data["email"] | |||
user = User.objects.get(email=email) | |||
token = str(uuid.uuid4()) | |||
user.set_password(token) | |||
user.save() | |||
login_link = reverse("hc-check-token", args=[token]) | |||
login_link = settings.SITE_ROOT + login_link | |||
body = "login link: %s" % login_link | |||
send_mail('Log In', body, '[email protected]', [email], fail_silently=False) | |||
# FIXME send login token here | |||
return redirect("hc-login-link-sent") | |||
else: | |||
form = EmailForm() | |||
ctx = { | |||
"form": form | |||
} | |||
return render(request, "accounts/login.html", ctx) | |||
def login_link_sent(request): | |||
return render(request, "accounts/login_link_sent.html") | |||
def check_token(request): | |||
return render(request, "accounts/login_link_sent.html") |
@ -0,0 +1,8 @@ | |||
from django.contrib import admin | |||
from hc.checks.models import Canary | |||
@admin.register(Canary) | |||
class CanaryAdmin(admin.ModelAdmin): | |||
list_display = ("id", "code", "user", "last_ping") |
@ -0,0 +1,24 @@ | |||
# -*- coding: utf-8 -*- | |||
from __future__ import unicode_literals | |||
from django.db import models, migrations | |||
import uuid | |||
from django.conf import settings | |||
class Migration(migrations.Migration): | |||
dependencies = [ | |||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | |||
] | |||
operations = [ | |||
migrations.CreateModel( | |||
name='Canary', | |||
fields=[ | |||
('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), | |||
('code', models.UUIDField(default=uuid.uuid4, editable=False)), | |||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), | |||
], | |||
), | |||
] |
@ -0,0 +1,23 @@ | |||
# -*- coding: utf-8 -*- | |||
from __future__ import unicode_literals | |||
from django.db import models, migrations | |||
class Migration(migrations.Migration): | |||
dependencies = [ | |||
('checks', '0001_initial'), | |||
] | |||
operations = [ | |||
migrations.AlterModelOptions( | |||
name='canary', | |||
options={'verbose_name_plural': 'canaries'}, | |||
), | |||
migrations.AddField( | |||
model_name='canary', | |||
name='last_ping', | |||
field=models.DateTimeField(null=True, blank=True), | |||
), | |||
] |
@ -0,0 +1,13 @@ | |||
import uuid | |||
from django.contrib.auth.models import User | |||
from django.db import models | |||
class Canary(models.Model): | |||
class Meta: | |||
verbose_name_plural = "canaries" | |||
code = models.UUIDField(default=uuid.uuid4, editable=False) | |||
user = models.ForeignKey(User) | |||
last_ping = models.DateTimeField(null=True, blank=True) |
@ -0,0 +1,3 @@ | |||
from django.test import TestCase | |||
# Create your tests here. |
@ -0,0 +1,7 @@ | |||
from django.conf.urls import url | |||
from hc.checks import views | |||
urlpatterns = [ | |||
url(r'^ping/([\w-]+)/$', views.ping, name="hc-ping"), | |||
] |
@ -0,0 +1,16 @@ | |||
from django.http import HttpResponse, HttpResponseBadRequest | |||
from django.utils import timezone | |||
from hc.checks.models import Canary | |||
def ping(request, code): | |||
try: | |||
canary = Canary.objects.get(code=code) | |||
except Canary.DoesNotExist: | |||
return HttpResponseBadRequest() | |||
canary.last_ping = timezone.now() | |||
canary.save() | |||
return HttpResponse() |
@ -0,0 +1,111 @@ | |||
""" | |||
Django settings for hc project. | |||
Generated by 'django-admin startproject' using Django 1.8.2. | |||
For more information on this file, see | |||
https://docs.djangoproject.com/en/1.8/topics/settings/ | |||
For the full list of settings and their values, see | |||
https://docs.djangoproject.com/en/1.8/ref/settings/ | |||
""" | |||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) | |||
import os | |||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |||
# Quick-start development settings - unsuitable for production | |||
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ | |||
# SECURITY WARNING: keep the secret key used in production secret! | |||
SECRET_KEY = '9)gjy!j50uvbcy#y9ifoh@9pf%3m5^-9h0jv@+3^ln$%az8e(7' | |||
# SECURITY WARNING: don't run with debug turned on in production! | |||
DEBUG = True | |||
ALLOWED_HOSTS = [] | |||
# Application definition | |||
INSTALLED_APPS = ( | |||
'django.contrib.admin', | |||
'django.contrib.auth', | |||
'django.contrib.contenttypes', | |||
'django.contrib.sessions', | |||
'django.contrib.messages', | |||
'django.contrib.staticfiles', | |||
'bootstrap3', | |||
'hc.accounts', | |||
'hc.checks' | |||
) | |||
MIDDLEWARE_CLASSES = ( | |||
'django.contrib.sessions.middleware.SessionMiddleware', | |||
'django.middleware.common.CommonMiddleware', | |||
'django.middleware.csrf.CsrfViewMiddleware', | |||
'django.contrib.auth.middleware.AuthenticationMiddleware', | |||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', | |||
'django.contrib.messages.middleware.MessageMiddleware', | |||
'django.middleware.clickjacking.XFrameOptionsMiddleware', | |||
'django.middleware.security.SecurityMiddleware', | |||
) | |||
ROOT_URLCONF = 'hc.urls' | |||
TEMPLATES = [ | |||
{ | |||
'BACKEND': 'django.template.backends.django.DjangoTemplates', | |||
'DIRS': [os.path.join(BASE_DIR, 'templates')], | |||
'APP_DIRS': True, | |||
'OPTIONS': { | |||
'context_processors': [ | |||
'django.template.context_processors.debug', | |||
'django.template.context_processors.request', | |||
'django.contrib.auth.context_processors.auth', | |||
'django.contrib.messages.context_processors.messages', | |||
], | |||
}, | |||
}, | |||
] | |||
WSGI_APPLICATION = 'hc.wsgi.application' | |||
# Database | |||
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases | |||
DATABASES = { | |||
'default': { | |||
'ENGINE': 'django.db.backends.postgresql_psycopg2', | |||
'NAME': 'hc', | |||
'USER': 'hc', | |||
'PASSWORD': '', | |||
'HOST': '192.168.1.111', | |||
'PORT': 5432, | |||
'TEST': {'CHARSET': 'UTF8'} | |||
} | |||
} | |||
# Internationalization | |||
# https://docs.djangoproject.com/en/1.8/topics/i18n/ | |||
LANGUAGE_CODE = 'en-us' | |||
TIME_ZONE = 'UTC' | |||
USE_I18N = True | |||
USE_L10N = True | |||
USE_TZ = True | |||
SITE_ROOT = "http://localhost:8000" | |||
STATIC_URL = '/static/' | |||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] |
@ -0,0 +1,8 @@ | |||
from django.conf.urls import include, url | |||
from django.contrib import admin | |||
urlpatterns = [ | |||
url(r'^admin/', include(admin.site.urls)), | |||
url(r'^accounts/', include('hc.accounts.urls')), | |||
url(r'^', include('hc.checks.urls')), | |||
] |
@ -0,0 +1,16 @@ | |||
""" | |||
WSGI config for hc project. | |||
It exposes the WSGI callable as a module-level variable named ``application``. | |||
For more information on this file, see | |||
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ | |||
""" | |||
import os | |||
from django.core.wsgi import get_wsgi_application | |||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hc.settings") | |||
application = get_wsgi_application() |
@ -0,0 +1,10 @@ | |||
#!/usr/bin/env python | |||
import os | |||
import sys | |||
if __name__ == "__main__": | |||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hc.settings") | |||
from django.core.management import execute_from_command_line | |||
execute_from_command_line(sys.argv) |
@ -0,0 +1,3 @@ | |||
Django==1.8.2 | |||
django-bootstrap3 | |||
psycopg2==2.6 |
@ -0,0 +1,476 @@ | |||
/*! | |||
* Start Bootstrap - Creative Bootstrap Theme (http://startbootstrap.com) | |||
* Code licensed under the Apache License v2.0. | |||
* For details, see http://www.apache.org/licenses/LICENSE-2.0. | |||
*/ | |||
html, | |||
body { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
body { | |||
font-family: Merriweather,'Helvetica Neue',Arial,sans-serif; | |||
} | |||
hr { | |||
max-width: 50px; | |||
border-color: #f05f40; | |||
border-width: 3px; | |||
} | |||
hr.light { | |||
border-color: #fff; | |||
} | |||
a { | |||
color: #f05f40; | |||
-webkit-transition: all .35s; | |||
-moz-transition: all .35s; | |||
transition: all .35s; | |||
} | |||
a:hover, | |||
a:focus { | |||
color: #eb3812; | |||
} | |||
h1, | |||
h2, | |||
h3, | |||
h4, | |||
h5, | |||
h6 { | |||
font-family: 'Open Sans','Helvetica Neue',Arial,sans-serif; | |||
} | |||
p { | |||
margin-bottom: 20px; | |||
font-size: 16px; | |||
line-height: 1.5; | |||
} | |||
.bg-primary { | |||
background-color: #f05f40; | |||
} | |||
.bg-dark { | |||
color: #fff; | |||
background-color: #222; | |||
} | |||
.text-faded { | |||
color: rgba(255,255,255,.7); | |||
} | |||
section { | |||
padding: 100px 0; | |||
} | |||
aside { | |||
padding: 50px 0; | |||
} | |||
.no-padding { | |||
padding: 0; | |||
} | |||
.navbar-default { | |||
border-color: rgba(34,34,34,.05); | |||
font-family: 'Open Sans','Helvetica Neue',Arial,sans-serif; | |||
background-color: #fff; | |||
-webkit-transition: all .35s; | |||
-moz-transition: all .35s; | |||
transition: all .35s; | |||
} | |||
.navbar-default .navbar-header .navbar-brand { | |||
text-transform: uppercase; | |||
font-family: 'Open Sans','Helvetica Neue',Arial,sans-serif; | |||
font-weight: 700; | |||
color: #f05f40; | |||
} | |||
.navbar-default .navbar-header .navbar-brand:hover, | |||
.navbar-default .navbar-header .navbar-brand:focus { | |||
color: #eb3812; | |||
} | |||
.navbar-default .nav > li>a, | |||
.navbar-default .nav>li>a:focus { | |||
text-transform: uppercase; | |||
font-size: 13px; | |||
font-weight: 700; | |||
color: #222; | |||
} | |||
.navbar-default .nav > li>a:hover, | |||
.navbar-default .nav>li>a:focus:hover { | |||
color: #f05f40; | |||
} | |||
.navbar-default .nav > li.active>a, | |||
.navbar-default .nav>li.active>a:focus { | |||
color: #f05f40!important; | |||
background-color: transparent; | |||
} | |||
.navbar-default .nav > li.active>a:hover, | |||
.navbar-default .nav>li.active>a:focus:hover { | |||
background-color: transparent; | |||
} | |||
@media(min-width:768px) { | |||
.navbar-default { | |||
border-color: rgba(255,255,255,.3); | |||
background-color: transparent; | |||
} | |||
.navbar-default .navbar-header .navbar-brand { | |||
color: rgba(255,255,255,.7); | |||
} | |||
.navbar-default .navbar-header .navbar-brand:hover, | |||
.navbar-default .navbar-header .navbar-brand:focus { | |||
color: #fff; | |||
} | |||
.navbar-default .nav > li>a, | |||
.navbar-default .nav>li>a:focus { | |||
color: rgba(255,255,255,.7); | |||
} | |||
.navbar-default .nav > li>a:hover, | |||
.navbar-default .nav>li>a:focus:hover { | |||
color: #fff; | |||
} | |||
.navbar-default.affix { | |||
border-color: rgba(34,34,34,.05); | |||
background-color: #fff; | |||
} | |||
.navbar-default.affix .navbar-header .navbar-brand { | |||
font-size: 14px; | |||
color: #f05f40; | |||
} | |||
.navbar-default.affix .navbar-header .navbar-brand:hover, | |||
.navbar-default.affix .navbar-header .navbar-brand:focus { | |||
color: #eb3812; | |||
} | |||
.navbar-default.affix .nav > li>a, | |||
.navbar-default.affix .nav>li>a:focus { | |||
color: #222; | |||
} | |||
.navbar-default.affix .nav > li>a:hover, | |||
.navbar-default.affix .nav>li>a:focus:hover { | |||
color: #f05f40; | |||
} | |||
} | |||
header { | |||
position: relative; | |||
width: 100%; | |||
min-height: auto; | |||
text-align: center; | |||
color: #fff; | |||
background-image: url(../img/header.jpg); | |||
background-position: center; | |||
-webkit-background-size: cover; | |||
-moz-background-size: cover; | |||
background-size: cover; | |||
-o-background-size: cover; | |||
} | |||
header .header-content { | |||
position: relative; | |||
width: 100%; | |||
padding: 100px 15px; | |||
text-align: center; | |||
} | |||
header .header-content .header-content-inner h1 { | |||
margin-top: 0; | |||
margin-bottom: 0; | |||
text-transform: uppercase; | |||
font-weight: 700; | |||
} | |||
header .header-content .header-content-inner hr { | |||
margin: 30px auto; | |||
} | |||
header .header-content .header-content-inner p { | |||
margin-bottom: 50px; | |||
font-size: 16px; | |||
font-weight: 300; | |||
color: rgba(255,255,255,.7); | |||
} | |||
@media(min-width:768px) { | |||
header { | |||
min-height: 100%; | |||
} | |||
header .header-content { | |||
position: absolute; | |||
top: 50%; | |||
padding: 0 50px; | |||
-webkit-transform: translateY(-50%); | |||
-ms-transform: translateY(-50%); | |||
transform: translateY(-50%); | |||
} | |||
header .header-content .header-content-inner { | |||
margin-right: auto; | |||
margin-left: auto; | |||
max-width: 1000px; | |||
} | |||
header .header-content .header-content-inner p { | |||
margin-right: auto; | |||
margin-left: auto; | |||
max-width: 80%; | |||
font-size: 18px; | |||
} | |||
} | |||
.section-heading { | |||
margin-top: 0; | |||
} | |||
.service-box { | |||
margin: 50px auto 0; | |||
max-width: 400px; | |||
} | |||
@media(min-width:992px) { | |||
.service-box { | |||
margin: 20px auto 0; | |||
} | |||
} | |||
.service-box p { | |||
margin-bottom: 0; | |||
} | |||
.portfolio-box { | |||
display: block; | |||
position: relative; | |||
margin: 0 auto; | |||
max-width: 650px; | |||
} | |||
.portfolio-box .portfolio-box-caption { | |||
display: block; | |||
position: absolute; | |||
bottom: 0; | |||
width: 100%; | |||
height: 100%; | |||
text-align: center; | |||
color: #fff; | |||
opacity: 0; | |||
background: rgba(240,95,64,.9); | |||
-webkit-transition: all .35s; | |||
-moz-transition: all .35s; | |||
transition: all .35s; | |||
} | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content { | |||
position: absolute; | |||
top: 50%; | |||
width: 100%; | |||
text-align: center; | |||
transform: translateY(-50%); | |||
} | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-category, | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-name { | |||
padding: 0 15px; | |||
font-family: 'Open Sans','Helvetica Neue',Arial,sans-serif; | |||
} | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-category { | |||
text-transform: uppercase; | |||
font-size: 14px; | |||
font-weight: 600; | |||
} | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-name { | |||
font-size: 18px; | |||
} | |||
.portfolio-box:hover .portfolio-box-caption { | |||
opacity: 1; | |||
} | |||
@media(min-width:768px) { | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-category { | |||
font-size: 16px; | |||
} | |||
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-name { | |||
font-size: 22px; | |||
} | |||
} | |||
.call-to-action h2 { | |||
margin: 0 auto 20px; | |||
} | |||
.text-primary { | |||
color: #f05f40; | |||
} | |||
.no-gutter > [class*=col-] { | |||
padding-right: 0; | |||
padding-left: 0; | |||
} | |||
.btn-default { | |||
border-color: #fff; | |||
color: #222; | |||
background-color: #fff; | |||
-webkit-transition: all .35s; | |||
-moz-transition: all .35s; | |||
transition: all .35s; | |||
} | |||
.btn-default:hover, | |||
.btn-default:focus, | |||
.btn-default.focus, | |||
.btn-default:active, | |||
.btn-default.active, | |||
.open > .dropdown-toggle.btn-default { | |||
border-color: #ededed; | |||
color: #222; | |||
background-color: #f2f2f2; | |||
} | |||
.btn-default:active, | |||
.btn-default.active, | |||
.open > .dropdown-toggle.btn-default { | |||
background-image: none; | |||
} | |||
.btn-default.disabled, | |||
.btn-default[disabled], | |||
fieldset[disabled] .btn-default, | |||
.btn-default.disabled:hover, | |||
.btn-default[disabled]:hover, | |||
fieldset[disabled] .btn-default:hover, | |||
.btn-default.disabled:focus, | |||
.btn-default[disabled]:focus, | |||
fieldset[disabled] .btn-default:focus, | |||
.btn-default.disabled.focus, | |||
.btn-default[disabled].focus, | |||
fieldset[disabled] .btn-default.focus, | |||
.btn-default.disabled:active, | |||
.btn-default[disabled]:active, | |||
fieldset[disabled] .btn-default:active, | |||
.btn-default.disabled.active, | |||
.btn-default[disabled].active, | |||
fieldset[disabled] .btn-default.active { | |||
border-color: #fff; | |||
background-color: #fff; | |||
} | |||
.btn-default .badge { | |||
color: #fff; | |||
background-color: #222; | |||
} | |||
.btn-primary { | |||
border-color: #f05f40; | |||
color: #fff; | |||
background-color: #f05f40; | |||
-webkit-transition: all .35s; | |||
-moz-transition: all .35s; | |||
transition: all .35s; | |||
} | |||
.btn-primary:hover, | |||
.btn-primary:focus, | |||
.btn-primary.focus, | |||
.btn-primary:active, | |||
.btn-primary.active, | |||
.open > .dropdown-toggle.btn-primary { | |||
border-color: #ed431f; | |||
color: #fff; | |||
background-color: #ee4b28; | |||
} | |||
.btn-primary:active, | |||
.btn-primary.active, | |||
.open > .dropdown-toggle.btn-primary { | |||
background-image: none; | |||
} | |||
.btn-primary.disabled, | |||
.btn-primary[disabled], | |||
fieldset[disabled] .btn-primary, | |||
.btn-primary.disabled:hover, | |||
.btn-primary[disabled]:hover, | |||
fieldset[disabled] .btn-primary:hover, | |||
.btn-primary.disabled:focus, | |||
.btn-primary[disabled]:focus, | |||
fieldset[disabled] .btn-primary:focus, | |||
.btn-primary.disabled.focus, | |||
.btn-primary[disabled].focus, | |||
fieldset[disabled] .btn-primary.focus, | |||
.btn-primary.disabled:active, | |||
.btn-primary[disabled]:active, | |||
fieldset[disabled] .btn-primary:active, | |||
.btn-primary.disabled.active, | |||
.btn-primary[disabled].active, | |||
fieldset[disabled] .btn-primary.active { | |||
border-color: #f05f40; | |||
background-color: #f05f40; | |||
} | |||
.btn-primary .badge { | |||
color: #f05f40; | |||
background-color: #fff; | |||
} | |||
.btn { | |||
border: 0; | |||
border-radius: 300px; | |||
text-transform: uppercase; | |||
font-family: 'Open Sans','Helvetica Neue',Arial,sans-serif; | |||
font-weight: 700; | |||
} | |||
.btn-xl { | |||
padding: 15px 30px; | |||
} | |||
::-moz-selection { | |||
text-shadow: none; | |||
color: #fff; | |||
background: #222; | |||
} | |||
::selection { | |||
text-shadow: none; | |||
color: #fff; | |||
background: #222; | |||
} | |||
img::selection { | |||
color: #fff; | |||
background: 0 0; | |||
} | |||
img::-moz-selection { | |||
color: #fff; | |||
background: 0 0; | |||
} | |||
body { | |||
webkit-tap-highlight-color: #222; | |||
} |
@ -0,0 +1,22 @@ | |||
{% extends "base.html" %} | |||
{% load bootstrap3 %} | |||
{% block content %} | |||
<h1>Log In</h1> | |||
<p> | |||
Please enter your email address. | |||
Next, we'll send you an email with log-in instructions! | |||
</p> | |||
<form method="post"> | |||
{% csrf_token %} | |||
{% bootstrap_form form %} | |||
{% buttons %} | |||
<button type="submit" class="btn btn-primary"> | |||
Submit | |||
</button> | |||
{% endbuttons %} | |||
</form> | |||
{% endblock %} |
@ -0,0 +1,5 @@ | |||
{% extends "base.html" %} | |||
{% block content %} | |||
<h1>Login link sent, check your inbox!</h1> | |||
{% endblock %} |
@ -0,0 +1,15 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>Waka Waka</title> | |||
{% load staticfiles %} | |||
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"> | |||
</head> | |||
<body> | |||
<div class="container"> | |||
{% block content %}{% endblock %} | |||
</div> | |||
</body> | |||
</html> |