commit 14f006ee5ed24b3dd6d6d30588b0acf7f299a4aa Author: Eric S. Bullington Date: Tue Mar 6 12:57:28 2012 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..712585e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +logs/*.log diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/app.cfg b/app.cfg new file mode 100644 index 0000000..9ef45b0 --- /dev/null +++ b/app.cfg @@ -0,0 +1,19 @@ +####################################### +########APP Settings ########## +####################################### + +# enable debug mode. Disable this in production! +DEBUG = True + +# Secret key for app encryption. +# Simple way to generate a random secret key: +# +# python -c "print repr(__import__('os').urandom(40))" +SQLALCHEMY_DATABASE_URI = 'postgresql://user:password@127.0.0.1/app' +SECRET_KEY = 'kfdasferuiawohnjlkasdfjkl' +TITLE = 'app' +SUBTITLE = 'A Flask Template with Bootstrap' +AUTHOR = 'Eric S. Bullington' +AUTHOR_EMAIL = 'eric.s.bullington@gmail.com' +KEYWORDS = 'python, Flask, template, Bootstrap, authorization' +DESCRIPTION = '' diff --git a/app.py b/app.py new file mode 100644 index 0000000..57f3d13 --- /dev/null +++ b/app.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +from flask import Flask, request, jsonify, make_response, render_template, flash, redirect, url_for, session, escape, g +from models.database import db_session +from flaskext.sqlalchemy import SQLAlchemy +from flaskext.auth import Auth, AuthUser, login_required, logout +from models.sa import get_user_class + +app = Flask(__name__) +app.config.from_pyfile('app.cfg') + +# Instantiate DB +db = SQLAlchemy(app) + +## Set SQL Alchemy to automatically tear down +@app.teardown_request +def shutdown_session(exception=None): + db_session.remove() + +# Instantiate authentication +auth = Auth(app, login_url_name='login') +User = get_user_class(db.Model) + + +def index(): + return render_template('index.html') + +##login methods + +def login(): + if request.method == 'POST': + username = request.form['username'] + user = User.query.filter(User.username==username).first() + if user is not None: + # Authenticate and log in! + if user.authenticate(request.form['password']): + session['username'] = request.form['username'] + return redirect(url_for('home')) + else: + flash('Incorrect password. Please try again') + return render_template('login.html') + else: + flash('Incorrect username. Please try again') + return render_template('login.html') + return render_template('login.html') + +@login_required() +def home(): + ##Dump variables in templates + return render_template('home.html') + +def user_create(): + if request.method == 'POST': + username = request.form['username'] + if User.query.filter(User.username==username).first(): + return 'User already exists.' + password = request.form['password'] + user = User(username=username, password=password) + db.session.add(user) + db.session.commit() + return redirect(url_for('index')) + return render_template('user_create.html') + +def logout_view(): + user_data = logout() + if user_data is None: + msg = 'No user to log out.' + return render_template('logout.html', msg=msg) + else: + msg = 'Logged out user {0}.'.format(user_data['username']) + return render_template('logout.html', msg=msg) + +# URLs +app.add_url_rule('/', 'index', index) +app.add_url_rule('/login/', 'login', login, methods=['GET', 'POST']) +app.add_url_rule('/home/', 'home', home) +app.add_url_rule('/users/create/', 'user_create', user_create, methods=['GET', 'POST']) +app.add_url_rule('/logout/', 'logout', logout_view) + +# Secret key needed to use sessions. +app.secret_key = 'mysecretkey' + +if __name__ == "__main__": + try: + open('/tmp/app.db') + except IOError: + db.create_all() + app.run(debug=True,host='127.0.0.1') diff --git a/etc/mime.types b/etc/mime.types new file mode 100644 index 0000000..b73b270 --- /dev/null +++ b/etc/mime.types @@ -0,0 +1,74 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.wap.xhtml+xml xhtml; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mpeg mpeg mpg; + video/quicktime mov; + video/x-flv flv; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} \ No newline at end of file diff --git a/etc/nginx.conf b/etc/nginx.conf new file mode 100644 index 0000000..a20db68 --- /dev/null +++ b/etc/nginx.conf @@ -0,0 +1,64 @@ +worker_processes 1; +pid etc/nginx.pid; +#user ec2-user; +error_log logs/nginx-error.log; +#daemon off; +worker_rlimit_nofile 8192; +events { + #use epoll; + #use kqueue; + worker_connections 8000; + accept_mutex off; +} + +http { + # Some sensible defaults. + include mime.types; + default_type application/octet-stream; + keepalive_timeout 10; + client_max_body_size 20m; + sendfile on; + gzip on; + + gzip_proxied expired no-cache no-store private auth; + gzip_disable "MSIE [1-6]\."; + gzip_vary on; + + # Directories + client_body_temp_path tmp/client_body/ 2 2; + fastcgi_temp_path tmp/fastcgi/; + proxy_temp_path tmp/proxy/; + uwsgi_temp_path tmp/uwsgi/; + + # Logging + access_log logs/nginx-access.log combined; + + upstream fysv { + # Distribute requests to servers based on client IP. This keeps load + # balancing fair but consistent per-client. In this instance we're + # only using one uWGSI worker anyway. + ip_hash; + server unix:tmp/app.sock; + } + + server { + #listen 80; + listen 5000; + server_name app; + charset utf-8; + + expires 1M; + + location /static/ { + expires max; + alias static/; + access_log logs/static.log; + } + + # Finally, send all non-media requests to the Django server. + location / { + uwsgi_pass app; + include uwsgi_params; + } + } +} diff --git a/etc/placeholder b/etc/placeholder new file mode 100644 index 0000000..97a8c97 --- /dev/null +++ b/etc/placeholder @@ -0,0 +1 @@ +# This is just a placeholder to make sure this directory can be checked in. \ No newline at end of file diff --git a/etc/uwsgi_params b/etc/uwsgi_params new file mode 100644 index 0000000..da78cce --- /dev/null +++ b/etc/uwsgi_params @@ -0,0 +1,15 @@ + +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; \ No newline at end of file diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 0000000..d0652b6 --- /dev/null +++ b/fabfile.py @@ -0,0 +1,85 @@ +from fabric.api import run, require, env, local, put +from fabric.contrib.files import exists +from fabric.context_managers import cd +import time + +from app import app + +env.branch = 'master' +env.app_name = app.logger.name + +env.key_filename = '' # Full path to pem +env.hosts = [] # Hosts + +env.user = 'ec2-user' +env.app_dir = '/home/ec2-user/apps/%s' % (env.app_name) +env.timestamp = time.strftime('%Y%m%d%H%M') +env.warn_only = True +env.version = '%s-%s' % (env.app_name, env.timestamp) + +def setup(): + """Set up the initial structure on the remote hosts.""" + require('hosts') + require('app_dir') + + with cd(env.app_dir): + run("mkdir versions") + run("mkdir archives") + +def switch_to(version): + """Switch the current (ie live) version""" + require('hosts') + require('app_dir') + with cd(env.app_dir): + if exists('versions/previous'): + run('rm versions/previous') + + if exists('versions/current'): + run('mv versions/current versions/previous') + + run('ln -s ../versions/%s versions/current' % version) + with cd('versions/current'): + run("mkdir logs") + run("mkdir etc") + +def switch_to_version(version): + switch_to(version) + # restart nginx/apache + +def make_tar(): + require('version') + #local('tar -cf %s.tar.gz .' % (env.version), capture=False) + local("git archive --format=tar --prefix=%(release)s/ %(branch)s | gzip -c > %(release)s.tar.gz" % { + 'release': env.version, + 'branch': env.branch, + } + ) + local('rm -fr %s' % env.version) + +def upload_tar(): + require('version', provided_by=[deploy]) + + put('%s.tar.gz' % env.version, '%s/archives/' % env.app_dir) + with cd(env.app_dir): + with cd('versions'): + run('tar -zxvf ../archives/%s.tar.gz ' % (env.version)) + + local('rm %s.tar.gz' % env.version) + + +def deploy(): + make_tar() + upload_tar() + switch_to(env.version) + +def start(): + with cd('%s/versions/current' % env.app_dir): + run('supervisord') + +def stop(): + with cd('%s/versions/current' % env.app_dir): + run('supervisorctl shutdown') + +def restart(): + with cd('%s/versions/current' % env.app_dir): + run('supervisorctl restart ') \ No newline at end of file diff --git a/logs/placeholder b/logs/placeholder new file mode 100644 index 0000000..97a8c97 --- /dev/null +++ b/logs/placeholder @@ -0,0 +1 @@ +# This is just a placeholder to make sure this directory can be checked in. \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..8f5b3e2 --- /dev/null +++ b/manage.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +from __future__ import absolute_import + +from flaskext.script import Manager + +from app import app + + +manager = Manager(app) + +if __name__ == "__main__": + manager.run() diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/appmodels.py b/models/appmodels.py new file mode 100644 index 0000000..bb0299c --- /dev/null +++ b/models/appmodels.py @@ -0,0 +1,45 @@ +from sqlalchemy import Column, Integer, String +from database import Base +import datetime +from app import db + + +########################################## + +class Contact(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(50)) + first_name = db.Column(db.String(50)) + middle_initial = db.Column(db.String(50)) + last_name = db.Column(db.String(50)) + phone = db.Column(db.String(50)) + email = db.Column(db.String(50)) + created_on = db.Column(db.DateTime) + student = db.Column(db.String(50)) + + def __init__(self, title): + self.title = title + self.first_name = first_name + self.middle_initial = middle_initial + self.last_name = last_name + self.student_class = student_class + self.email = mail + if created_on is None: + created_on = datetime.utcnow() + self.created_on = created_on + + def __repr__(self): + return '' % self.last_name + + +########################################## + +class Title(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(50)) + + def __init__(self, title): + self.title = title + + def __repr__(self): + return '' % self.title diff --git a/models/database.py b/models/database.py new file mode 100644 index 0000000..72ab2a3 --- /dev/null +++ b/models/database.py @@ -0,0 +1,17 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.ext.declarative import declarative_base + +engine = create_engine('postgresql://username:password@127.0.0.1/app', convert_unicode=True) +db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) +Base = declarative_base() +Base.query = db_session.query_property() + +def init_db(): + # import all modules here that might define models so that + # they will be registered properly on the metadata. Otherwise + # you will have to import them first before calling init_db() + import appmodels + Base.metadata.create_all(bind=engine) diff --git a/models/sa.py b/models/sa.py new file mode 100644 index 0000000..a060076 --- /dev/null +++ b/models/sa.py @@ -0,0 +1,55 @@ +""" +Module to provide plug-and-play authentication support for SQLAlchemy. +""" + +import datetime +from sqlalchemy import Column, Integer, String, DateTime +from flaskext.auth import AuthUser, get_current_user_data + +def get_user_class(declarative_base): + """ + Factory function to create an SQLAlchemy User model with a declarative + base (for example db.Model from the Flask-SQLAlchemy extension). + """ + class User(declarative_base, AuthUser): + """ + Implementation of User for SQLAlchemy. + """ + id = Column(Integer, primary_key=True) + username = Column(String(80), unique=True, nullable=False) + password = Column(String(120), nullable=False) + salt = Column(String(80)) + role = Column(String(80)) + title = Column(String(50)) + email = Column(String(50)) + first_name = Column(String(50)) + middle_initial = Column(String(50)) + last_name = Column(String(50)) + created = Column(DateTime(), default=datetime.datetime.utcnow) + modified = Column(DateTime()) + + def __init__(self, *args, **kwargs): + super(User, self).__init__(*args, **kwargs) + password = kwargs.get('password') + if password is not None and not self.id: + self.created = datetime.datetime.utcnow() + # Initialize and encrypt password before first save. + self.set_and_encrypt_password(password) + + def __getstate__(self): + return { + 'id': self.id, + 'username': self.username, + 'role': self.role, + 'created': self.created, + 'modified': self.modified, + } + + @classmethod + def load_current_user(cls, apply_timeout=True): + data = get_current_user_data(apply_timeout) + if not data: + return None + return cls.query.filter(cls.username==data['username']).one() + + return User diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eb06a72 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +Flask +Flask-Script +WTForms +Flask-WTF +SQLAlchemy +Flask-SQLAlchemy +Flask-Auth +redis diff --git a/static/.gitignore b/static/.gitignore new file mode 100644 index 0000000..f058ff0 --- /dev/null +++ b/static/.gitignore @@ -0,0 +1,40 @@ +# Numerous always-ignore extensions +*.diff +*.err +*.orig +*.log +*.rej +*.swo +*.swp +*.vi +*~ +*.sass-cache + +# OS or Editor folders +.DS_Store +.cache +.project +.settings +.tmproj +nbproject +Thumbs.db + +# Dreamweaver added files +_notes +dwsync.xml + +# Komodo +*.komodoproject +.komodotools + +# Folders to ignore +.hg +.svn +.CVS +intermediate +publish +.idea + +# build script local files +build/buildinfo.properties +build/config/buildinfo.properties diff --git a/static/.htaccess b/static/.htaccess new file mode 100644 index 0000000..232c96f --- /dev/null +++ b/static/.htaccess @@ -0,0 +1,503 @@ +# Apache configuration file +# httpd.apache.org/docs/2.2/mod/quickreference.html + +# Note .htaccess files are an overhead, this logic should be in your Apache config if possible +# httpd.apache.org/docs/2.2/howto/htaccess.html + +# Techniques in here adapted from all over, including: +# Kroc Camen: camendesign.com/.htaccess +# perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/ +# Sample .htaccess file of CMS MODx: modxcms.com + + +### +### If you run a webserver other than apache, consider: +### github.com/paulirish/html5-boilerplate-server-configs +### + + + +# ---------------------------------------------------------------------- +# Better website experience for IE users +# ---------------------------------------------------------------------- + +# Force the latest IE version, in various cases when it may fall back to IE7 mode +# github.com/rails/rails/commit/123eb25#commitcomment-118920 +# Use ChromeFrame if it's installed for a better experience for the poor IE folk + + + Header set X-UA-Compatible "IE=Edge,chrome=1" + # mod_headers can't match by content-type, but we don't want to send this header on *everything*... + + Header unset X-UA-Compatible + + + + +# ---------------------------------------------------------------------- +# Cross-domain AJAX requests +# ---------------------------------------------------------------------- + +# Serve cross-domain ajax requests, disabled. +# enable-cors.org +# code.google.com/p/html5security/wiki/CrossOriginRequestSecurity + +# +# Header set Access-Control-Allow-Origin "*" +# + + + +# ---------------------------------------------------------------------- +# Webfont access +# ---------------------------------------------------------------------- + +# Allow access from all domains for webfonts. +# Alternatively you could only whitelist your +# subdomains like "subdomain.example.com". + + + + Header set Access-Control-Allow-Origin "*" + + + + + +# ---------------------------------------------------------------------- +# Proper MIME type for all files +# ---------------------------------------------------------------------- + + +# JavaScript +# Normalize to standard type (it's sniffed in IE anyways) +# tools.ietf.org/html/rfc4329#section-7.2 +AddType application/javascript js + +# Audio +AddType audio/ogg oga ogg +AddType audio/mp4 m4a + +# Video +AddType video/ogg ogv +AddType video/mp4 mp4 m4v +AddType video/webm webm + +# SVG. +# Required for svg webfonts on iPad +# twitter.com/FontSquirrel/status/14855840545 +AddType image/svg+xml svg svgz +AddEncoding gzip svgz + +# Webfonts +AddType application/vnd.ms-fontobject eot +AddType application/x-font-ttf ttf ttc +AddType font/opentype otf +AddType application/x-font-woff woff + +# Assorted types +AddType image/x-icon ico +AddType image/webp webp +AddType text/cache-manifest appcache manifest +AddType text/x-component htc +AddType application/x-chrome-extension crx +AddType application/x-xpinstall xpi +AddType application/octet-stream safariextz +AddType text/x-vcard vcf + + + +# ---------------------------------------------------------------------- +# Allow concatenation from within specific js and css files +# ---------------------------------------------------------------------- + +# e.g. Inside of script.combined.js you could have +# +# +# and they would be included into this single file. + +# This is not in use in the boilerplate as it stands. You may +# choose to name your files in this way for this advantage or +# concatenate and minify them manually. +# Disabled by default. + +# +# Options +Includes +# AddOutputFilterByType INCLUDES application/javascript application/json +# SetOutputFilter INCLUDES +# +# +# Options +Includes +# AddOutputFilterByType INCLUDES text/css +# SetOutputFilter INCLUDES +# + + +# ---------------------------------------------------------------------- +# Gzip compression +# ---------------------------------------------------------------------- + + + +# Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ + + + SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding + RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding + + + +# HTML, TXT, CSS, JavaScript, JSON, XML, HTC: + + FilterDeclare COMPRESS + FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html + FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css + FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain + FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml + FilterProvider COMPRESS DEFLATE resp=Content-Type $text/x-component + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/vnd.ms-fontobject + FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml + FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf + FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype + FilterChain COMPRESS + FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no + + + + # Legacy versions of Apache + AddOutputFilterByType DEFLATE text/html text/plain text/css application/json + AddOutputFilterByType DEFLATE application/javascript + AddOutputFilterByType DEFLATE text/xml application/xml text/x-component + AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml + AddOutputFilterByType DEFLATE image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype + + + + + +# ---------------------------------------------------------------------- +# Expires headers (for better cache control) +# ---------------------------------------------------------------------- + +# These are pretty far-future expires headers. +# They assume you control versioning with cachebusting query params like +# + + + + + + + + + + + + + + +

QUnit Test Suite

+

+
+

+
    +
    test markup
    + + diff --git a/static/test/qunit/qunit.css b/static/test/qunit/qunit.css new file mode 100755 index 0000000..214b9b0 --- /dev/null +++ b/static/test/qunit/qunit.css @@ -0,0 +1,148 @@ +/** Font Family and Sizes */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; +} + +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-tests { font-size: smaller; } + + +/** Resets */ + +#qunit-tests, #qunit-tests li ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { + margin: 0; + padding: 0; +} + + +/** Header */ + +#qunit-header { + padding: 0.5em 0 0.5em 1em; + + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; + background-color: #0d3349; + + border-radius: 15px 15px 0 0; + -moz-border-radius: 15px 15px 0 0; + -webkit-border-top-right-radius: 15px; + -webkit-border-top-left-radius: 15px; +} + +#qunit-banner { + height: 5px; +} + +#qunit-testrunner-toolbar { + padding: 0em 0 0.5em 2em; +} + +#qunit-userAgent { + padding: 0.5em 0 0.5em 2.5em; + background-color: #2b81af; + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Tests: Pass/Fail */ + +#qunit-tests { + list-style-position: inside; +} + +#qunit-tests li { + padding: 0.4em 0.5em 0.4em 2.5em; + border-bottom: 1px solid #fff; + list-style-position: inside; +} + +#qunit-tests li strong { + cursor: pointer; +} + +#qunit-tests li ol { + margin-top: 0.5em; + padding: 0.5em; + + background-color: #fff; + + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; +} + +#qunit-tests li li { + margin: 0.5em; + padding: 0.4em 0.5em 0.4em 0.5em; + background-color: #fff; + border-bottom: none; + list-style-position: inside; +} + +/*** Passing Styles */ + +#qunit-tests li li.pass { + color: #5E740B; + background-color: #fff; + border-left: 26px solid #C6E746; +} + +#qunit-tests li.pass { color: #528CE0; background-color: #D2E0E6; } +#qunit-tests li.pass span.test-name { color: #366097; } + +#qunit-tests li li.pass span.test-actual, +#qunit-tests li li.pass span.test-expected { color: #999999; } + +strong b.pass { color: #5E740B; } + +#qunit-banner.qunit-pass { background-color: #C6E746; } + +/*** Failing Styles */ + +#qunit-tests li li.fail { + color: #710909; + background-color: #fff; + border-left: 26px solid #EE5757; +} + +#qunit-tests li.fail { color: #000000; background-color: #EE5757; } +#qunit-tests li.fail span.test-name, +#qunit-tests li.fail span.module-name { color: #000000; } + +#qunit-tests li li.fail span.test-actual { color: #EE5757; } +#qunit-tests li li.fail span.test-expected { color: green; } + +strong b.fail { color: #710909; } + +#qunit-banner.qunit-fail, +#qunit-testrunner-toolbar { background-color: #EE5757; } + + +/** Footer */ + +#qunit-testresult { + padding: 0.5em 0.5em 0.5em 2.5em; + + color: #2b81af; + background-color: #D2E0E6; + + border-radius: 0 0 15px 15px; + -moz-border-radius: 0 0 15px 15px; + -webkit-border-bottom-right-radius: 15px; + -webkit-border-bottom-left-radius: 15px; +} + +/** Fixture */ + +#qunit-fixture { + position: absolute; + top: -10000px; + left: -10000px; +} diff --git a/static/test/qunit/qunit.js b/static/test/qunit/qunit.js new file mode 100755 index 0000000..9399a60 --- /dev/null +++ b/static/test/qunit/qunit.js @@ -0,0 +1,1265 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }); + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = '' + testName + '', testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = '' + config.currentModule + ": " + name; + } + + if ( !validTest(config.currentModule + ": " + testName) ) { + return; + } + + synchronize(function() { + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + + QUnit.testStart( testName, testEnvironment ); + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + name; + var li = document.createElement("li"); + li.appendChild( b ); + li.id = "current-test-output"; + tests.appendChild( li ) + } + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + }); + + synchronize(function() { + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + }); + + synchronize(function() { + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || "(no message)"; + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + if (bad == 0) { + ol.style.display = "none"; + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); + } + }); + + var li = id("current-test-output"); + li.id = ""; + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( ol ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + msg = escapeHtml(msg); + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + push(expected !== actual, actual, expected, message); + }, + + raises: function(fn, message) { + try { + fn(); + ok( false, message ); + } + catch (e) { + ok( true, message ); + } + }, + + start: function() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + } + +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +// define these after exposing globals to keep them in these QUnit namespace only +extend(QUnit, { + config: config, + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + assertions: [], + filters: [], + queue: [] + }); + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function() { + if ( window.jQuery ) { + jQuery("#main, #qunit-fixture").html( config.fixture ); + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) == type; + }, + + objectType: function( obj ) { + if (typeof obj === "undefined") { + return "undefined"; + + // consider: typeof null === object + } + if (obj === null) { + return "null"; + } + + var type = Object.prototype.toString.call( obj ) + .match(/^\[object\s(.*)\]$/)[1] || ''; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return "nan"; + } else { + return "number"; + } + case 'String': + case 'Boolean': + case 'Array': + case 'Date': + case 'RegExp': + case 'Function': + return type.toLowerCase(); + } + if (typeof obj === "object") { + return "object"; + } + return undefined; + }, + + // Logging callbacks + begin: function() {}, + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name, testEnvironment) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}); + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + QUnit.begin(); + + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main') || id('qunit-fixture'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if (config.autostart) { + QUnit.start(); + } +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
    ', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function escapeHtml(s) { + s = s === null ? "" : s + ""; + return s.replace(/[\&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); +} + +function push(result, actual, expected, message) { + message = escapeHtml(message) || (result ? "okay" : "failed"); + message = '' + message + ""; + expected = escapeHtml(QUnit.jsDump.parse(expected)); + actual = escapeHtml(QUnit.jsDump.parse(actual)); + var output = message + ', expected: ' + expected + ''; + if (actual != expected) { + output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual); + } + + // can't use ok, as that would double-escape messages + QUnit.log(result, output); + config.assertions.push({ + result: !!result, + message: output + }); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + var start = (new Date()).getTime(); + + while ( config.queue.length && !config.blocking ) { + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + config.queue.shift()(); + + } else { + setTimeout( process, 13 ); + break; + } + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = QUnit.objectType(o); + if (prop) { + if (QUnit.objectType(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return QUnit.objectType(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if ( ! (QUnit.objectType(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + //track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for(j=0;j= 0) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:false //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +// from Sizzle.js +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +}; + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + function diff(o, n){ + var ns = new Object(); + var os = new Object(); + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { + rows: new Array(), + o: null + }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { + rows: new Array(), + n: null + }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { + text: n[ns[i].rows[0]], + row: os[i].rows[0] + }; + o[os[i].rows[0]] = { + text: o[os[i].rows[0]], + row: ns[i].rows[0] + }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && + n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { + text: n[i + 1], + row: n[i].row + 1 + }; + o[n[i].row + 1] = { + text: o[n[i].row + 1], + row: i + 1 + }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { + text: n[i - 1], + row: n[i].row - 1 + }; + o[n[i].row - 1] = { + text: o[n[i].row - 1], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function(o, n){ + o = o.replace(/\s+$/, ''); + n = n.replace(/\s+$/, ''); + var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + + var str = ""; + + var oSpace = o.match(/\s+/g); + if (oSpace == null) { + oSpace = [" "]; + } + else { + oSpace.push(" "); + } + var nSpace = n.match(/\s+/g); + if (nSpace == null) { + nSpace = [" "]; + } + else { + nSpace.push(" "); + } + + if (out.n.length == 0) { + for (var i = 0; i < out.o.length; i++) { + str += '' + out.o[i] + oSpace[i] + ""; + } + } + else { + if (out.n[0].text == null) { + for (n = 0; n < out.o.length && out.o[n].text == null; n++) { + str += '' + out.o[n] + oSpace[n] + ""; + } + } + + for (var i = 0; i < out.n.length; i++) { + if (out.n[i].text == null) { + str += '' + out.n[i] + nSpace[i] + ""; + } + else { + var pre = ""; + + for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { + pre += '' + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + } +})(); + +})(this); diff --git a/static/test/tests.js b/static/test/tests.js new file mode 100644 index 0000000..8c0be53 --- /dev/null +++ b/static/test/tests.js @@ -0,0 +1,27 @@ + +// documentation on writing tests here: http://docs.jquery.com/QUnit +// example tests: https://github.com/jquery/qunit/blob/master/test/same.js + +// below are some general tests but feel free to delete them. + +module("example tests"); +test("HTML5 Boilerplate is sweet",function(){ + expect(1); + equals("boilerplate".replace("boilerplate","sweet"),"sweet","Yes. HTML5 Boilerplate is, in fact, sweet"); + +}) + +// these test things from plugins.js +test("Environment is good",function(){ + expect(3); + ok( !!window.log, "log function present"); + + var history = log.history && log.history.length || 0; + log("logging from the test suite.") + equals( log.history.length - history, 1, "log history keeps track" ) + + ok( !!window.Modernizr, "Modernizr global is present") +}) + + + diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..b5ea1ac --- /dev/null +++ b/templates/404.html @@ -0,0 +1,22 @@ + +not found + + + + + + +
    +

    Not found

    +

    :(

    +
    \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..a97eea1 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + {% block title %}{% endblock %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +
    + × + {{ message }} +
    + {% endfor %} + {% endif %} + {% endwith %} + + + + +
    +
    + {% block header %}{% endblock %} +
    + +
    + {% block main %}{% endblock %} +
    + +
    + {% block footer %}{% endblock %} +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..1808b81 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,21 @@ +{% extends "inner_base.html" %} + +{% block title %}Welcome Inside!{% endblock %} + {% block header %}Welcome to Flask!{% endblock %} +{% block main %} + + + +
    +

    This is a template for a simple marketing or informational website. It includes a large callout called the hero unit and three supporting pieces of content. Use it as a starting point to create something more unique.

    +

    Learn more »

    +

    Current user: + {{ session['username'] }} +

    +

    Switch: {{ switch }} +

    + + + + +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..45ea663 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block title %}Welcome!{% endblock %} + +{% block main %} + +
    + + +
    +

    Welcome to appname!

    +

    This is a template for a simple marketing or informational website. It includes a large callout called the hero unit and three supporting pieces of content. Use it as a starting point to create something more unique.

    +

    Learn more »

    +
    + + +
    +
    +

    Heading

    +

    Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

    +

    View details »

    +
    +
    +

    Heading

    +

    Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

    +

    View details »

    +
    +
    +

    Heading

    +

    Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    +

    View details »

    +
    +
    + +
    + +
    +

    © Company 2012

    +
    + +
    + +{% endblock %} diff --git a/templates/inner_base.html b/templates/inner_base.html new file mode 100644 index 0000000..2e44bd8 --- /dev/null +++ b/templates/inner_base.html @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + {% block title %}{% endblock %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +
    + × + {{ message }} +
    + {% endfor %} + {% endif %} + {% endwith %} + + + +
    +
    +
    + +
    + +
    +
    +

    {% block header %}{% endblock %}

    +
    + {% block main %}{% endblock %} +
    +
    +
    + +
    +
    + +
    +

    © Company 2012

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..12b68bf --- /dev/null +++ b/templates/login.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %}Login{% endblock %} + +{% block main %} + +
    +
    + Username:
    + Password:
    + +
    +
    + +{% endblock %} diff --git a/templates/logout.html b/templates/logout.html new file mode 100644 index 0000000..aa3fbd2 --- /dev/null +++ b/templates/logout.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block title %}Login{% endblock %} + +{% block main %} + +
    +{{ msg }} +
    + +{% endblock %} diff --git a/templates/user_create.html b/templates/user_create.html new file mode 100644 index 0000000..7d91b7f --- /dev/null +++ b/templates/user_create.html @@ -0,0 +1,5 @@ +
    + Username:
    + Password:
    + +
    diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..878513c --- /dev/null +++ b/tests.py @@ -0,0 +1,19 @@ +import os +import unittest +import tempfile + +from app import app + + +class AppTestCase(unittest.TestCase): + + def setUp(self): + + self.app = app.test_client() + + + def tearDown(self): + pass + +if __name__ == '__main__': + unittest.main() diff --git a/tmp/placeholder b/tmp/placeholder new file mode 100644 index 0000000..97a8c97 --- /dev/null +++ b/tmp/placeholder @@ -0,0 +1 @@ +# This is just a placeholder to make sure this directory can be checked in. \ No newline at end of file