I <3 Django

This commit is contained in:
nikolark committed 2013-12-23 02:02:09 +01:00
commit 58e643d2bf
25 files changed
+820

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
/.idea/
*.swp
*.pyc
*.bak
.sass-cache
db.sqlite3
node_modules
venv
+32
View File
@@ -0,0 +1,32 @@
module.exports = function(grunt) {
// load all grunt tasks
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
grunt.initConfig({
duskenweb: {
// potential config here
},
compass: {
dev: {
options: {
config: 'duskenweb/static/config.rb',
basePath: 'duskenweb/static/'
}
}
},
watch: {
sass: {
files: ['duskenweb/static/sass/*.scss'],
tasks: ['compass:dev'],
},
livereload: {
files: ['duskenweb/templates/**/*.html', 'duskenweb/static/**/*.css', 'duskenweb/static/**/*.js'],
options: {
livereload: true
}
}
}
});
grunt.registerTask('default', 'watch');
};
+40
View File
@@ -0,0 +1,40 @@
# Installation
sudo apt-get install python-virtualenv python-dev
virtualenv --distribute venv
. venv/bin/activate
pip install -r requirements.txt
cd duskenweb
cp settings-sample.py settings.py
cd ..
./manage.py syncdb --all
./manage.py runserver
npm install
grunt watch
# TODO
* Purchase membership-flow
* https
* cookies: secure, httpOnly
Client:
* Rewrite client side code in Backbone.js
* Start here: https://github.com/backbone-boilerplate/backbone-boilerplate
* Backbone Extensions:
* https://github.com/amccloud/backbone-tastypie
* https://github.com/marionettejs
* https://github.com/tbranyen/backbone.layoutmanager
* https://github.com/PaulUithol/Backbone-relational
* https://github.com/jeromegn/Backbone.localStorage
* http://wyuenho.github.io/backgrid/
* Chrome Browser Extension: https://chrome.google.com/webstore/detail/backbone-debugger/bhljhndlimiafopmmhjlgfpnnchjjbhd
* Inline validation
# References:
* Express middleware: https://github.com/senchalabs/connect/wiki
* Reference project: https://github.com/madhums/node-express-mongoose-demo
* Backbone tutorial: http://www.codebeerstartups.com/2012/12/12-listening-to-dom-events-in-backbone-js-learning-backbone-js/
View File
Whitespace-only changes.
+6
View File
@@ -0,0 +1,6 @@
from django.contrib import admin
from pydusken.models import DuskenAccessToken
# Register your models here.
admin.site.register(DuskenAccessToken)
+1
View File
@@ -0,0 +1 @@
from django.db import models
+117
View File
@@ -0,0 +1,117 @@
"""
Django settings for duskenweb project.
For more information on this file, see
https://docs.djangoproject.com/en/1.6/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.6/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'v_-cub3st=0x3h-fafpz*+jqg+ug78jq7se8xn0&s^b9!8j6t_'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_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',
)
INSTALLED_APPS += (
'corsheaders',
'longerusername',
'django_extensions',
'south',
'pydusken',
# Our app
'duskenweb',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
)
AUTHENTICATION_BACKENDS = (
'pydusken.authentication.DuskenBackend',
'django.contrib.auth.backends.ModelBackend',
)
ROOT_URLCONF = 'duskenweb.urls'
WSGI_APPLICATION = 'duskenweb.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# Example PostgreSQL config:
#'ENGINE': 'django.db.backends.postgresql_psycopg2',
#'NAME': 'duskenweb_test',
#'USER': 'postgres',
#'PASSWORD': '',
#'HOST': '127.0.0.1',
#'PORT': '',
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.6/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Oslo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.6/howto/static-files/
STATIC_URL = '/static/'
LOGIN_REDIRECT_URL = "/profile/"
DUSKEN_CLIENT_ID = 'd802768a8afd9dec2660'
DUSKEN_CLIENT_SECRET = '3962467fe60a94727812053c8a4f44533f4ef610'
CORS_ORIGIN_ALLOW_ALL = True
# Allow Ajax calls only from tuple of domain:port
#CORS_ORIGIN_WHITELIST = (
# '127.0.0.1:3000',
# 'dusken.neuf.no:3000',
# 'dusken.neuf.no',
#)
+10
View File
@@ -0,0 +1,10 @@
# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "css"
sass_dir = "sass"
images_dir = "images"
javascripts_dir = "js"
# output_style = :expanded or :nested or :compact or :compressed
# relative_assets = true
# line_comments = false
+78
View File
@@ -0,0 +1,78 @@
/* line 1, ../sass/app.scss */
.title-header {
margin-bottom: 3em;
}
/* line 4, ../sass/app.scss */
.social-login {
text-align: center;
}
/* line 7, ../sass/app.scss */
.social-login .btn-facebook-login {
width: 250px;
height: 40px;
background-color: #3b5998;
color: white;
font-weight: bold;
text-shadow: 0 -1px black;
font-size: 24px;
padding: 0;
}
/* line 17, ../sass/app.scss */
.social-login .btn-facebook-login span {
font-size: 16px;
position: relative;
top: -1px;
}
/* line 23, ../sass/app.scss */
.social-login .btn-facebook-login i {
position: relative;
top: 2px;
padding-right: 6px;
}
/* line 29, ../sass/app.scss */
.social-login .btn-facebook-login:hover, .social-login .btn-facebook-login:focus {
background-color: #2f477a;
color: white;
outline: 0;
}
/* line 38, ../sass/app.scss */
.subtitle {
color: #555;
}
/* line 41, ../sass/app.scss */
.seperator {
margin: 1em 1em;
text-align: center;
}
/* line 45, ../sass/app.scss */
.seperator .text {
padding-left: 4px;
padding-right: 4px;
font-style: italic;
}
/* line 51, ../sass/app.scss */
.seperator:before {
display: inline-block;
content: '';
width: 30%;
height: 1px;
background-color: #ccc;
margin-bottom: .25em;
}
/* line 59, ../sass/app.scss */
.seperator:after {
display: inline-block;
content: '';
width: 30%;
height: 1px;
background-color: #ccc;
margin-bottom: .25em;
}
/* line 68, ../sass/app.scss */
.text-center {
text-align: center;
}
+180
View File
@@ -0,0 +1,180 @@
/* Google+ button language */
//window.___gcfg = {
// lang: 'no'
//};
// Ref: https://developers.facebook.com/docs/facebook-login/using-auto-login/
//window.fbAsyncInit = function() {
// // Initialize the Facebook SDK
// FB.init({
// appId: '654851471204962',
// xfbml: true,
// status: true,
// cookie: true,
// });
//
// // Check if the current user is logged in and has authorized the app
// //FB.getLoginStatus(checkLoginStatus);
//
// // Login in the current user via Facebook and ask for email permission
// $(".btn-facebook-login").on('click', function(e) {
// FB.login(checkLoginStatus, {scope:'email, user_birthday'});
// });
//
// function login_or_create_member(authResponse) {
// console.log("Facebook:", authResponse);
// // Ref: https://developers.facebook.com/docs/facebook-login/multiple-providers/
// FB.api('/me', function(response) {
// var data = {
// last_name: response.last_name,
// first_name: response.first_name,
// username: response.username,
// email: response.email,
// facebook_id: response.id,
// date_of_birth: response.birthday
// };
// /* Check for existing account by looking up email */
// Dusken.findMember({email: data.email}, function(member) {
// if(member === undefined) {
// /* Create new account */
// Dusken.register(data, function(response) {
// console.log(response);
// // TODO Hide login form
// $("form").css('opacity', 0.5);
// // login
// // render profile view with data from dusken.
// });
// } else {
// // TODO what now?
// /* Check for verified email (before merging FB information into Dusken account) */
// if(response.verified) {
// /* Member exists, authenticate and login */
// // TODO Hide login form, render profile view with data from dusken.
// $("form").css('opacity', 0.5);
//
// } else {
// /* Send verification email and ask user to check it */
// // TODO
// }
// }
// });
//
// });
// }
// // Check the result of the user status and display login button if necessary
// function checkLoginStatus(response) {
// if(response && response.status == 'connected') {
// console.log('User is authorized');
//
// // Hide the login button
// //$(".btn-facebook-login").css('opacity', 0.3);
//
// // Now Personalize the User Experience
// // authResponse:
// // - accessToken: make requests to the Facebook APIs on behalf of that user.
// // - userID: the unique identifier for the user who's present in your app.
// //console.log('Access Token: ' + response.authResponse.accessToken);
// // User is logged in to FB and likes our FB app
// login_or_create_member(response.authResponse);
// } else {
// console.log('User is not authorized');
//
// // Display the login button
// $(".btn-facebook-login").show();
// }
// }
//};
// Load the SDK asynchronously
//(function(d){
// var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
// if (d.getElementById(id)) {return;}
// js = d.createElement('script'); js.id = id; js.async = true;
// js.src = "//connect.facebook.net/en_US/all.js";
// ref.parentNode.insertBefore(js, ref);
//}(document));
/* Google + */
//function signinCallback(authResult) {
// if (authResult['access_token']) {
// // Update the app to reflect a signed in user
// // Hide the sign-in button now that the user is authorized, for example:
// $("#signinButton").hide();
// console.log("Google+:", authResult);
// } else if (authResult['error']) {
// // Update the app to reflect a signed out user
// // Possible error values:
// // "user_signed_out" - User is signed-out
// // "access_denied" - User denied access to your app
// // "immediate_failed" - Could not automatically log in the user
// console.log('Sign-in state: ' + authResult['error']);
// }
//}
/* Ref: http://stackoverflow.com/questions/1184624/convert-form-data-to-js-object-with-jquery */
$.fn.serializeObject = function()
{
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
$(function() {
/* Override options for local dev */
Dusken.init({
base_url: "http://127.0.0.1:8000/",
api_base_url: "http://127.0.0.1:8000/api/v1/",
});
//});
/* Ref:
* - https://developers.facebook.com/docs/facebook-login/getting-started-web/
* - https://developers.facebook.com/docs/facebook-login/checklist/
*
* Test the following:
* - When someone new to your app logs in with Facebook
* - When someone who has logged in with Facebook in the past logs back in with Facebook
* - When someone cancels log in and then logs back in with Facebook
* - When someone revokes permissions previously granted to your app and then logs back in with Facebook
* - When your token has expired when someone logs in with Facebook
* */
//$(".sign-out-google-plus").on('click', function(e) {
// gapi.auth.signOut();
//});
/* Register new user */
$(".btn-register").on('click', function(event) {
event.preventDefault();
var data = $("form").serializeObject();
Dusken.register(data, function(response) {
console.log(response);
$("form").html("Vi har sendt deg en epost, trykk på linken i for å fortsette registreringen.");
},
function(jq_xhr) {
$("form .errors").html('<div class="alert alert-danger">' +jq_xhr.responseText + '</div>');
});
});
/* Login existing user */
//$(".btn-login").on('click', function(event) {
// event.preventDefault();
// var data = $("form").serializeObject();
// Dusken.authenticate(data, function(response) {
// console.log(response);
//
// },
// /* On error */
// function(jq_xhr) {
// $("form .errors").html('<div class="alert alert-danger">' +jq_xhr.responseText + '</div>');
// });
//});
});
+60
View File
@@ -0,0 +1,60 @@
var Dusken = {
options: {
base_url: "http://duskendev.neuf.no/",
api_base_url: "http://duskendev.neuf.no/api/v1/",
access_token: "",
},
_auth_header: function() {
/* http://django-tastypie.readthedocs.org/en/latest/authentication.html#apikeyauthentication */
return {"Authorization": "Oauth"+ this.access_token };
},
init: function(options) {
$.extend(true, this.options, options);
return this;
},
authenticate: function(username, password, callback) {
$.post(
"/authenticate",
{username: username, password: password},
callback);
return this;
},
register: function(data, success, error) {
$.ajax({
url: this.options.api_base_url + "member/register/",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
dataType: "json",
// FIXME not secure
headers: this._auth_header(),
success: success,
error: error
});
return this;
},
getMember: function(member_id, callback) {
$.get(
this.options.api_base_url + "member/" + member_id,
null,
callback);
return this;
},
findMember: function(data, success, error) {
// FIXME not secure
// FIXME need to use signed requests via serverside, instead of using username:apikey
$.ajax({
url: this.options.api_base_url + "member/",
type: "GET",
data: data,
contentType: "application/json; charset=utf-8",
dataType: "json",
headers: this._auth_header(),
success: success,
error: error
});
return this;
}
};
+70
View File
@@ -0,0 +1,70 @@
.title-header {
margin-bottom: 3em;
}
.social-login {
text-align: center;
.btn-facebook-login {
width: 250px;
height: 40px;
background-color: #3b5998;
color: white;
font-weight: bold;
text-shadow: 0 -1px black;
font-size: 24px;
padding: 0;
span {
font-size: 16px;
position: relative;
top: -1px;
}
i {
position: relative;
top: 2px;
padding-right: 6px;
}
&:hover,
&:focus {
background-color: #2f477a;
color: white;
outline: 0;
}
}
}
.subtitle{
color: #555;
}
.seperator {
margin: 1em 1em;
text-align: center;
.text {
padding-left: 4px;
padding-right: 4px;
font-style: italic;
}
&:before {
display: inline-block;
content: '';
width: 30%;
height: 1px;
background-color: #ccc;
margin-bottom: .25em;
}
&:after {
display: inline-block;
content: '';
width: 30%;
height: 1px;
background-color: #ccc;
margin-bottom: .25em;
}
}
.text-center {
text-align: center;
}
+10
View File
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-3">
<h4>404</h4>
</div>
</div>
</div>
{% endblock %}
+33
View File
@@ -0,0 +1,33 @@
<!doctype html>
<html>
<head>
<title>{{ title }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" type="text/css" />
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.1/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/app.css" type="text/css" />
</head>
<body>
<header class="title-header">
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>D.U.S.K.E.N.</h1>
<em>Dårlig Utrustet Studentsystem som Kan Endre Norge</em>
</div>
</div>
</div>
</header>
<!-- Main content -->
{% block content %}
{% endblock %}
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
<script src="{{ STATIC_URL }}/js/dusken.js"></script>
<script src="{{ STATIC_URL }}/js/application.js"></script>
</body>
+32
View File
@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<form role="form">
<div class="row">
<div class="col-md-5">
<div class="errors"></div>
</div>
</div>
<div class="row social-login">
<div class="col-md-5">
<button class="btn btn-facebook-login"><i class="fa fa-facebook"></i> <span>Logg inn med Facebook</span></button>
<div class="subtitle"><small>Det går raskest</small></div>
</div>
</div>
<div class="row">
<div class="col-md-5">
</div>
</div>
<div class="row">
<div class="col-md-5 register-with-email">
<div class="seperator"><span class="text">Eller</span></div>
<input id="email" name="email" type="email" placeholder="E-post" class="form-control" />
<br/>
<button class="btn btn-primary btn-lg btn-block">Bli medlem</button>
<br/>
<div class="text-right">Allerede medlem? <a href="/login">Logg inn</a></div>
</div>
</div>
</form>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-12">
<h4>{{user}}</h4>
<ul>
<li>Username: '{{user.username}}'</li>
<li>Email: '{{user.email}}'</li>
</ul>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
{% include "registration/login_form.html" %}
</div>
{% endblock %}
@@ -0,0 +1,27 @@
<form method="post" action="{% url "django.contrib.auth.views.login" %}">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger">{{ form.errors }}</div>
{% endif %}
<!--<div class="row social-login">
<div class="col-md-5">
<button class="btn btn-facebook-login"><i class="fa fa-facebook"></i> <span>Logg inn med Facebook</span></button>
<div class="subtitle"><small>Det går raskest</small></div>
</div>
</div>
<div class="row">
<div class="col-md-5">
<div class="seperator"><span class="text">Eller</span></div>
</div>
</div>-->
<div class="row">
<div class="col-md-5">
<h4>Logg inn med epost og passord</h4>
<input id="username" name="username" type="text" placeholder="E-post (eller brukernavn)" class="form-control" />
<br/>
<input id="password" name="password" type="password" placeholder="Passord" class="form-control" />
<br />
<button class="btn btn-primary btn-lg btn-block btn-login">Logg inn</button>
</div>
</div>
</form>
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+33
View File
@@ -0,0 +1,33 @@
from django.conf import settings
from django.conf.urls import patterns, include, url
from django.contrib.auth.forms import SetPasswordForm
from django.contrib import admin
from longerusername.forms import AuthenticationForm
admin.autodiscover()
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
)
urlpatterns += patterns('duskenweb.views',
url(r'^$', 'home', name='home'),
url(r'^profile/$', 'profile', name='profile'),
)
# Authentication views
urlpatterns += patterns('django.contrib.auth.views',
url(r'^login/$', 'login', {'authentication_form': AuthenticationForm}, name='login'),
url(r'^logout/$', 'logout', name='logout'),
url(r'^password/change/$', # skip entering old password
'password_change',
{'password_change_form': SetPasswordForm},
name='password_change'),
url(r'^password/change/done/$', 'password_change_done', name='password_change_done'),
url(r'^password/reset/$', 'password_reset', name='password_reset'),
url(r'^password/reset/done$', 'password_reset_done', name='password_reset_done'),
url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
'password_reset_confirm',
name='password_reset_confirm'),
url(r'^password/reset/complete/$', 'password_reset_complete', name='password_reset_complete'),
)
+13
View File
@@ -0,0 +1,13 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
# Create your views here.
def home(request):
if request.user.is_authenticated():
return redirect('profile')
return render(request, 'index.html')
@login_required
def profile(request):
return render(request, 'profile.html')
+14
View File
@@ -0,0 +1,14 @@
"""
WSGI config for duskenweb 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.6/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "duskenweb.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "duskenweb.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
+16
View File
@@ -0,0 +1,16 @@
{
"name": "duskenweb",
"version": "0.0.1",
"description": "",
"devDependencies": {
"grunt": "latest",
"grunt-contrib-watch": "latest",
"grunt-contrib-compass": "latest",
"matchdep": "latest"
},
"repository": {
"type": "git",
"url": "git@git.neuf.no:duskenweb"
},
"license": "GPL"
}
+7
View File
@@ -0,0 +1,7 @@
Django==1.6.1
django-extensions==1.2.5
django-cors-headers==0.12
psycopg2==2.5.1
South==0.8.4
#-e git://git.neuf.no/pydusken.git#egg=pydusken
-e ../pydusken