registration and other stuff

This commit is contained in:
Matthew Scragg 2013-10-03 09:58:07 -05:00
parent 613d1c6ca3
commit cba28239a2
13 changed files with 326 additions and 50 deletions

View file

@ -4,11 +4,12 @@ import time
import redis
import rethinkdb as rdb
from flask import Flask, request, render_template, url_for, redirect
from flask import Flask, request, render_template, url_for, redirect, flash, session
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager
from flask.ext.login import LoginManager, login_user, logout_user
from flask.ext.assets import Environment
from recaptcha.client import captcha
from werkzeug.routing import BaseConverter
import config
from session import RedisSessionInterface
@ -16,12 +17,18 @@ from wiki import Wiki
from util import to_canonical, remove_ext
class RegexConverter(BaseConverter):
def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map)
self.regex = items[0]
app = Flask(__name__)
app.config.update(config.flask)
app.debug = (config.ENV is not 'PROD')
app.secret_key = config.secret_key
app.static_path = os.sep + 'static'
app.session_interface = RedisSessionInterface()
app.url_map.converters['regex'] = RegexConverter
bcrypt = Bcrypt(app)
@ -45,7 +52,13 @@ if not config.db['dbname'] in rdb.db_list().run(conn) and config.ENV is not 'PRO
repo_dir = config.repo['dir']
from models import Site
# This is down here because of dependencies above
from models import Site, User, CurrentUser
@login_manager.user_loader
def load_user(user_id):
return CurrentUser(user_id)
w = Wiki(repo_dir)
@ -54,6 +67,14 @@ def redirect_url():
return request.args.get('next') or request.referrer or url_for('index')
def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
app.config['RECAPTCHA_PRIVATE_KEY'],
request.remote_addr)
return response.is_valid
@app.template_filter('datetime')
def _jinja2_filter_datetime(ts):
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
@ -72,7 +93,18 @@ def page_error(e):
@app.route("/")
def root():
return redirect('/home')
return render('home')
#return redirect('/home')
@app.route("/account/")
def account():
return render_template('account/index.html')
@app.route("/logout/")
def logout():
logout_user()
del session['user']
return redirect(url_for('root'))
@app.route("/commit/<sha>/<name>")
def commit_sha(name, sha):
@ -85,17 +117,28 @@ def commit_sha(name, sha):
return redirect('/create/'+cname)
@app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
diff = w.compare(name, fsha, lsha)
return render_template('page/compare.html', name=name, diff=diff)
@app.route("/register", methods=['GET', 'POST'])
def register():
if request.method == 'POST':
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
app.config['RECAPTCHA_PRIVATE_KEY'],
request.remote_addr)
if not response.is_valid:
return redirect('/register?fail')
else:
user = User()
if user.get_by_email(request.form['email']):
flash('Email is already taken')
return redirect('/register')
if user.get_by_username(request.form['username']):
flash('Username is already taken')
return redirect('/register')
# Create user and login
u = User.create(email=request.form['email'].lower(),
username=request.form['username'],
password=bcrypt.generate_password_hash(request.form['password']))
login_user(u)
return redirect("/")
else:
return render_template('account/register.html')
@ -104,7 +147,11 @@ def register():
@app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
pass
if User.auth(request.form['email'], request.form['password']):
return redirect("/")
else:
flash("Email or Password invalid")
return redirect("/login")
else:
return render_template('account/login.html')

View file

@ -1,6 +1,18 @@
import rethinkdb as rdb
from flask import session
from flask.ext.login import login_user
from rethinkORM import RethinkModel
from reimagine import conn, bcrypt
from reimagine import conn
def to_dict(cur, first=False):
ret = []
for row in cur:
ret.append(row)
if ret and first:
return ret[0]
else:
return ret
class BaseModel(RethinkModel):
@ -14,14 +26,63 @@ class BaseModel(RethinkModel):
def create(cls, **kwargs):
return super(BaseModel, cls).create(**kwargs)
def get_all(self, arg, index):
return rdb.table(self.table).get_all(arg, index=index).run(self._conn)
def get_one(self, arg, index):
return rdb.table(self.table).get_all(arg, index=index).limit(1).run(self._conn)
class Site(BaseModel):
table = 'sites'
class CurrentUser():
id = None
def __init__(self, id):
self.id = id
def get_id(self):
return self.id
def is_active(self):
return True
def is_anonymous(self):
return False if self.id else True
def is_authenticated(self):
return True if self.id else False
class User(BaseModel):
table = 'users'
def get_by_email(self, email):
return to_dict(self.get_one(email, 'email'), True)
def get_by_username(self, username):
return to_dict(self.get_one(username, 'username'), True)
def login(self, login, password):
pass
@classmethod
def get(cls, id):
print id
return cls(id=id)
@classmethod
def auth(cls, username, password):
u = User()
data = u.get_by_email(username)
if not data:
return False
if bcrypt.check_password_hash(data['password'], password):
login_user(CurrentUser(data['id']))
session['user'] = data
return True
else:
return False

View file

@ -1,5 +1,15 @@
.navbar {
margin-bottom: 25px;
}
#main-body {
background-color: #fff;
padding: 20px;
margin: 0 -20px;
-webkit-border-radius: 0 0 6px 6px;
border-radius: 0 0 6px 6px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
box-shadow: 0 1px 2px rgba(0,0,0,.15);
}
.checkbox-cell {
@ -26,7 +36,7 @@
left: 50%;
bottom: 10px;
right: 10px;
top: 60px;
top: 50px;
overflow: auto;
background: rgba(255,255,255,0.9);
border: 1px solid #EEE;
@ -58,7 +68,7 @@
#editor {
position: absolute;
margin-right: 5px;
top: 60px;
top: 50px;
left: 10px;
bottom: 10px;
right: 50%;
@ -86,3 +96,26 @@
-o-box-flex: 1;
box-flex: 1;
}
.user-avatar a img {
width: 32px;
height: 32px;
-webkit-box-shadow: 0 1px 3px #1e1e1e;
-moz-box-shadow: 0 1px 3px #1e1e1e;
box-shadow: 0 1px 3px #1e1e1e;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
-ms-border-radius: 2px;
-o-border-radius: 2px;
border-radius: 2px;
}
.navbar-nav .user-avatar a {
line-height: 30px;
}
.navbar-nav>li.user-avatar a {
padding-top: 9px;
padding-bottom: 9px;
}

View file

@ -0,0 +1,20 @@
{% extends 'layout.html' %}
{% block body %}
<h2>Account</h2>
<form method="POST" role="form" class="form-horizontal">
<div class="form-group">
<label for="email" class="col-md-2 control-label">Email</label>
<div class="col-md-10">
<input id="email" type="text" class="form-control" value="{{ session['user']['email'] }}" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Save">
</div>
</div>
</form>
{% endblock %}

View file

@ -5,8 +5,8 @@
<form role="form" method="post">
<div class="form-group">
<label for="username">Username or Email</label>
<input type="text" class="form-control" id="username" name="username" />
<label for="email">Email Address</label>
<input type="text" class="form-control" id="email" name="email" />
</div>
<div class="form-group">

View file

@ -27,4 +27,6 @@
<input type="submit" class="btn btn-primary" value="Submit" />
</form>
<a href="/login" class="pull-right">Already registered? Login here.</a>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% extends 'layout.html' %}
{% block body %}
<h1>Page Not Found</h1>
{% endblock %}

View file

@ -6,7 +6,7 @@
<meta name="description" content="">
<meta name="author" content="">
<title>ReImagine</title>
<title>Realms</title>
<link href="/static/css/cerulean.bootstrap.min.css" rel="stylesheet">
<link href="/static/css/font-awesome.min.css" rel="stylesheet">
@ -27,7 +27,7 @@
<body>
<!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
@ -35,24 +35,54 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">ReImagine</a>
<a class="navbar-brand" href="/">Realms</a>
</div>
<div class="navbar-collapse collapse">
<div class="navbar-collapse collapse navbar-inverse-collapse">
<ul class="nav navbar-nav">
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/create/"><i class="icon-plus"></i> Create</a></li>
{% if name %}
<li><a href="/edit/{{- name -}}"><i class="icon-edit"></i> Edit</a></li>
<li><a href="/history/{{- name -}}"><i class="icon-time"></i> History</a></li>
{% endif %}
{% if session.get('user') %}
<li class="dropdown user-avatar">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span>
<img src="http://static.ffxiah.com/images/avatars/mini/5a85e45e0976c8a96c955e83a9743b47.jpg" class="menu-avatar">
<span>{{ session['user'].get('username') }} <i class="icon-caret-down"></i></span>
</span>
</a>
<ul class="dropdown-menu">
<li><a href="/account">Account</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</li>
{% else %}
<li><a href="/login"><i class="icon-user"></i> Login</a></li>
<li><a href="/register"><i class="icon-pencil"></i> Register</a></li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container">{% block body %}{% endblock %}</div>
<div class="container">
<div id="main-body">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
{% if category == 'message' %}
{% set category = "info" %}
{% endif %}
<div class='alert alert-{{ category }}'>
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
</div>
</div>
<script src="/static/js/jquery-1.10.2.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>

View file

@ -0,0 +1,16 @@
{% extends 'layout.html' %}
{% block body %}
<h2>History for <strong>{{ name }}</strong></h2>
<p>
<a class="btn btn-default btn-sm" href="/history/{{ name }}">Back to History</a>
<a href="" class="btn btn-default btn-sm">Revert Changes</a>
</p>
{{ diff|safe }}
<p></p>
<p>
<a class="btn btn-default btn-sm" href="/history/{{ name }}">Back to History</a>
</p>
{% endblock %}

View file

@ -10,16 +10,16 @@
<div id="app-wrap" class="container-fluid">
<div id="app-controls" class="row">
<div class="col-xs-3">
<input id="page-name" type="text" class="form-control" name="name" placeholder="Name" value="{{- name -}}" />
<input id="page-name" type="text" class="form-control input-sm" name="name" placeholder="Name" value="{{- name -}}" />
</div>
<div class="col-xs-3">
<input id="page-message" type="text" class="form-control" name="page-message" placeholder="Comment" value="" />
<input id="page-message" type="text" class="form-control input-sm" name="page-message" placeholder="Comment" value="" />
</div>
<div class="col-xs-6">
<div class="pull-right">
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default" data-toggle="dropdown">Theme <b class="caret"></b></a>
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">Theme <b class="caret"></b></a>
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6">
<li><a tabindex="-1" href="#" data-value="ace/theme/chrome" class="">Chrome</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds" class="">Clouds</a></li>
@ -47,7 +47,7 @@
<li><a tabindex="-1" href="#" data-value="ace/theme/twilight" class="">Twilight</a></li>
<li><a tabindex="-1" href="#" data-value="ace/theme/vibrant_ink" class="">Vibrant Ink</a></li>
</ul>
<a id="save-native" class="btn btn-primary"><i class="icon-save"></i> Save</a>
<a id="save-native" class="btn btn-primary btn-sm"><i class="icon-save"></i> Save</a>
</div>
</div>
</div>

View file

@ -1,16 +1,58 @@
{% extends 'layout.html' %}
{% block body %}
<h2>History</h2>
<table class="table table-bordered">
<h2>History for <strong>{{ name }}</strong></h2>
<p>
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
</p>
<table class="table table-bordered revision-tbl">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Revision Message</th>
<th>Date</th>
</tr>
</thead>
{% for h in history %}
<tr>
<td class="checkbox-cell text-center"><input type="checkbox" /></td>
<td class="checkbox-cell text-center">
<input type="checkbox" name="versions[]" value="{{ h.sha }}" />
</td>
<td>{{ h.author }}</td>
<td><a href="/commit/{{ h.sha }}/{{ name }}" class='label label-primary'>{{ h.sha|truncate(7, True, end="") }}</a> {{ h.message }} </td>
<td>{{ h.time|datetime }}</td>
</tr>
{% endfor %}
</table>
<p>
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
</p>
{% endblock %}
{% block js %}
<script>
$(function(){
$('.revision-tbl :checkbox').change(function () {
var $cs=$(this).closest('.revision-tbl').find(':checkbox:checked');
if ($cs.length > 2) {
this.checked=false;
}
});
$(".compare-revisions").click(function(){
var $cs = $('.revision-tbl').find(':checkbox:checked');
console.log($cs.length);
if ($cs.length != 2) return;
var revs = [];
$.each($cs, function(i, v){
revs.push(v.value);
});
revs = revs.join("...");
location.href = "/compare/{{ name }}/" + revs;
});
});
</script>
{% endblock %}

View file

@ -1,6 +1,11 @@
{% extends 'layout.html' %}
{% block body %}
<div class="controls pull-right">
<a class="btn btn-default btn-sm" href="/edit/{{ name }}">Edit</a>
<a class="btn btn-default btn-sm" href="/history/{{ name }}">History</a>
</div>
<div id="page-content" style="display:none">
{{ page.data|safe }}
</div>

View file

@ -1,11 +1,11 @@
import os
import ghdiff
from gittle import Gittle
from util import to_canonical
class MyGittle(Gittle):
def file_history(self, path):
"""Returns all commits where given file was modified
"""
@ -57,7 +57,7 @@ class Wiki():
self.path = path
def write_page(self, name, content, message=None, create=False):
def write_page(self, name, content, message=None, create=False, username=None, email=None):
#content = clean_html(content)
filename = self.cname_to_filename(to_canonical(name))
f = open(self.path + "/" + filename, 'w')
@ -67,8 +67,16 @@ class Wiki():
if create:
self.repo.add(filename)
return self.repo.commit(name=self.default_committer_name,
email=self.default_committer_email,
if not message:
message = "Updated %s" % name
if not username:
username = self.default_committer_name
email = "%s@domain.com" % username
return self.repo.commit(name=username,
email=email,
message=message,
files=[filename])
@ -88,6 +96,12 @@ class Wiki():
# HEAD doesn't exist yet
return None
def compare(self, name, new_sha, old_sha):
old = self.get_page(name, sha=old_sha)
new = self.get_page(name, sha=new_sha)
return ghdiff.diff(old['data'], new['data'])
def get_history(self, name):
return self.repo.file_history(self.cname_to_filename(name))