Added snippet admin and profile editing

This commit is contained in:
Armin Ronacher 2010-05-16 13:51:12 +02:00
parent aa79c72ae0
commit d87cbff134
14 changed files with 228 additions and 40 deletions

View file

@ -24,6 +24,8 @@ def remove_db_session(response):
db_session.remove() db_session.remove()
return response return response
app.add_url_rule('/docs/', endpoint='documentation.index', build_only=True)
from flask_website.views.general import general from flask_website.views.general import general
from flask_website.views.mailinglist import mailinglist from flask_website.views.mailinglist import mailinglist
from flask_website.views.snippets import snippets from flask_website.views.snippets import snippets
@ -38,3 +40,4 @@ from flask_website import utils
app.jinja_env.filters['datetimeformat'] = utils.format_datetime app.jinja_env.filters['datetimeformat'] = utils.format_datetime
app.jinja_env.filters['timedeltaformat'] = utils.format_timedelta app.jinja_env.filters['timedeltaformat'] = utils.format_timedelta
app.jinja_env.filters['displayopenid'] = utils.display_openid

View file

@ -44,9 +44,13 @@ td input { border: none; padding: 0; }
input, textarea, select { border: 1px solid black; padding: 2px; background: white; input, textarea, select { border: 1px solid black; padding: 2px; background: white;
font-family: 'Georgia', serif; font-size: 17px; color: #004B6B; } font-family: 'Georgia', serif; font-size: 17px; color: #004B6B; }
textarea { width: 99%; } textarea { width: 99%; }
input[type="submit"] { background: #DEEBF3; border-color: #004B6B; } input[type="submit"] { background: #DEEBF3; border-color: #004B6B;
cursor: pointer; }
input[name="delete"]:hover { background: #A50000; color: white;
border-color: #860000; }
input.openid { background: url(openid.png) no-repeat 4px center; input.openid { background: url(openid.png) no-repeat 4px center;
padding-left: 26px; } padding-left: 26px; }
.formlist dt { color: #004B6B; margin: 8px 0; }
/* snippets */ /* snippets */
.snippet-author { margin: 0 0 20px 0; font-size: 0.9em; } .snippet-author { margin: 0 0 20px 0; font-size: 0.9em; }

View file

@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% block head %}
{{ super() }}
<style type=text/css>
h1 { background-image: url(/static/login.png); }
</style>
{% endblock %}
{% block title %}Change OpenID{% endblock %}
{% block body %}
<h2>Change OpenID</h2>
<form action="" method=post>
<p>
You can the OpenID used to log into this website. Right
now your OpenID identity is
<a href="{{ g.user.openid }}">{{ g.user.openid|displayopenid }}</a>
<p>
OpenID URL:
<input type=text name=openid class=openid size=26>
<input type=hidden name=next value="{{ next }}">
<input type=submit value=Change>
<input type=submit name=cancel value=Cancel>
<p>
Alternatively you can directly link your profile with one
of the common id providers:
<ul>
<li><a href=?provider=aol>AOL</a>
<li><a href=?provider=google>Google</a>
<li><a href=?provider=yahoo>Yahoo</a>
</ul>
</form>
{% endblock %}

View file

@ -11,7 +11,8 @@
<form action="{{ url_for('general.first_login') }}" method=post> <form action="{{ url_for('general.first_login') }}" method=post>
<p> <p>
This is your first login on this website. You are about to sign This is your first login on this website. You are about to sign
in with the following OpenID: <a href="{{ openid }}">{{ openid }}</a> in with the following OpenID: <a href="{{ openid }}">{{
openid|displayopenid }}</a>
<p> <p>
Before you can use this site we need a name for you. This is what Before you can use this site we need a name for you. This is what
will be displayed next to all of your public contributions to this will be displayed next to all of your public contributions to this

View file

@ -7,7 +7,7 @@
{% endblock %} {% endblock %}
{% block title %}Login{% endblock %} {% block title %}Login{% endblock %}
{% block body %} {% block body %}
<form action="{{ url_for('general.login') }}" method=post> <form action="" method=post>
<p> <p>
For some of the features on this site (such as creating snippets For some of the features on this site (such as creating snippets
or adding comments) you have to be signed in. You don't need to or adding comments) you have to be signed in. You don't need to
@ -18,5 +18,13 @@
<input type=text name=openid class=openid size=30> <input type=text name=openid class=openid size=30>
<input type=hidden name=next value="{{ next }}"> <input type=hidden name=next value="{{ next }}">
<input type=submit value=Login> <input type=submit value=Login>
<p>
Alternatively you can directly sign in by clicking on one of
the providers here in case you don't know the identity URL:
<ul>
<li><a href=?provider=aol>AOL</a>
<li><a href=?provider=google>Google</a>
<li><a href=?provider=yahoo>Yahoo</a>
</ul>
</form> </form>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,27 @@
{% extends "layout.html" %}
{% block head %}
{{ super() }}
<style type=text/css>
h1 { background-image: url(/static/login.png); }
</style>
{% endblock %}
{% block title %}Profile{% endblock %}
{% block body %}
<form action="" method=post>
<p>
Here you can change the profile information used for this
website. Please keep in mind that you can only have one
OpenID identity linked to this account, if you change it
the old one will no longer be able to sign in.
<dl class=formlist>
<dt>Name:
<dd><input type=text name=name value="{{ name }}" size=30>
<dt>OpenID:
<dd>
<span class=disabled>{{ g.user.openid|displayopenid }}</span>
(<a href="{{ url_for('change_openid') }}">change</a>)
</dl>
<p>
<input type=submit value="Update profile">
</form>
{% endblock %}

View file

@ -2,7 +2,7 @@
{% block head %} {% block head %}
<title>{% block title %}Welcome{% endblock %} | Flask (A Python Microframework)</title> <title>{% block title %}Welcome{% endblock %} | Flask (A Python Microframework)</title>
<meta charset=utf-8> <meta charset=utf-8>
<link rel=stylesheet type=text/css href="/static/style.css"> <link rel=stylesheet type=text/css href="{{ url_for('.static', filename='style.css') }}">
<script type=text/javascript <script type=text/javascript
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
{% endblock %} {% endblock %}
@ -11,11 +11,11 @@
<h1><span>{{ self.title() }}</span></h1> <h1><span>{{ self.title() }}</span></h1>
{% endblock %} {% endblock %}
<p class=nav> <p class=nav>
<a href=/>overview</a> // <a href="{{ url_for('general.index') }}">overview</a> //
<a href=/docs/>documentation</a> // <a href="{{ url_for('documentation.index') }}">documentation</a> //
<a href=/mailinglist/>mailinglist</a> // <a href="{{ url_for('mailinglist.index') }}">mailinglist</a> //
<a href=/snippets/>snippets</a> // <a href="{{ url_for('snippets.index') }}">snippets</a> //
<a href=/extensions/>extensions</a> <a href="{{ url_for('extensions.index') }}">extensions</a>
{% for message in get_flashed_messages() %} {% for message in get_flashed_messages() %}
<p class=message>{{ message }} <p class=message>{{ message }}
{% endfor %} {% endfor %}
@ -23,8 +23,11 @@
<p class=footer> <p class=footer>
&copy; Copyright 2010 by <a href=http://lucumr.pocoo.org/>Armin Ronacher</a> // &copy; Copyright 2010 by <a href=http://lucumr.pocoo.org/>Armin Ronacher</a> //
{% if g.user %} {% if g.user %}
<a href=/logout/ title="logged in as {{ g.user.name }} [{{ g.user.openid }}]">logout</a> <a href="{{ url_for('general.profile') }}">profile</a> /
<a href="{{ url_for('general.logout') }}" title="signed in as {{
g.user.name }} [{{ g.user.openid }}]{% if g.user.is_admin
%} - Administrator{% endif %}">sign out</a>
{% else %} {% else %}
<a href=/login/>login</a> <a href="{{ url_for('general.login') }}">sign in</a>
{% endif %} {% endif %}
</div> </div>

View file

@ -0,0 +1,22 @@
{% extends "snippets/layout.html" %}
{% block title %}Edit Comment {{ comment.title }}{% endblock %}
{% block body %}
<h2>Edit Comment</h2>
<script type=text/javascript>
$(function() {
$('input[name="delete"]').click(function() {
if (!confirm("Do you really want to delete this comment?\n\n" +
"THIS CANNOT BE UNDONE!"))
return false;
});
});
</script>
<form action="" method=post>
<p>Title: <input type=text name=title value="{{ form.title }}" size=30>
<p><textarea name=text cols=40 rows=8>{{ form.text }}</textarea>
<p>
<input type=submit value="Update Comment">
<input type=submit name=delete value="Delete">
<input type=submit name=cancel value="Cancel">
</form>
{% endblock %}

View file

@ -8,7 +8,9 @@
<p> <p>
You're signed in as “<span title="{{ g.user.openid }}">{{ g.user.name You're signed in as “<span title="{{ g.user.openid }}">{{ g.user.name
}}</span>”. You can <a href="{{ url_for('general.logout') }}</span>”. You can <a href="{{ url_for('general.logout')
}}">sign out</a> here after you're done if you want. }}">sign out</a> here after you're done if you want. If you want to
change your OpenID login or display name, head over to the
<a href="{{ url_for('general.profile') }}">profile</a>.
{% else %} {% else %}
<p> <p>
In order to add snippets to this page or to add comments, all you need In order to add snippets to this page or to add comments, all you need
@ -24,10 +26,6 @@
<li><a href="{{ category.url }}">{{ category.name }}</a> ({{ category.count }}) <li><a href="{{ category.url }}">{{ category.name }}</a> ({{ category.count }})
{% endfor %} {% endfor %}
</ul> </ul>
{% if g.user.is_admin %}
<p>
<a href="{{ url_for('manage_categories') }}">manage categories</a>
{% endif %}
{% if recent %} {% if recent %}
<h2>Recently Added</h2> <h2>Recently Added</h2>
<ul> <ul>
@ -37,4 +35,10 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
{% if g.user.is_admin %}
<h2>Admin Tools</h2>
<ul>
<li><a href="{{ url_for('manage_categories') }}">Manage categories</a>
</ul>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -17,7 +17,7 @@
with <code>#!python</code>, <code>#!html+jinja</code> or any other with <code>#!python</code>, <code>#!html+jinja</code> or any other
<a href="http://pygments.org/docs/lexers">Pygments lexer name</a>. <a href="http://pygments.org/docs/lexers">Pygments lexer name</a>.
<form action="" method=post> <form action="" method=post>
<dl> <dl class=formlist>
<dt>Title: <dt>Title:
<dd><input type=text name=title value="{{ request.form.title }}" size=40> <dd><input type=text name=title value="{{ request.form.title }}" size=40>
<dt>Category: <dt>Category:

View file

@ -9,7 +9,7 @@
<p class=snippet-author>By {{ snippet.author.name }} <p class=snippet-author>By {{ snippet.author.name }}
filed in <a href="{{ snippet.category.url }}">{{ snippet.category.name }}</a> filed in <a href="{{ snippet.category.url }}">{{ snippet.category.name }}</a>
{% if snippet.author == g.user or g.user.is_admin %} {% if snippet.author == g.user or g.user.is_admin %}
(<a href="{{ url_for('snippets.edit', id=snippet.id) }}">edit</a>) (<a href="{{ url_for('edit', id=snippet.id) }}">edit</a>)
{% endif %} {% endif %}
{{ snippet.rendered_body }} {{ snippet.rendered_body }}
{% if snippet.comments or g.user %} {% if snippet.comments or g.user %}
@ -23,6 +23,9 @@
{{ comment.title or "Comment" }} {{ comment.title or "Comment" }}
by {{ comment.author.name }} by {{ comment.author.name }}
on {{ comment.pub_date.strftime('%Y-%m-%d @ %H:%M') }} on {{ comment.pub_date.strftime('%Y-%m-%d @ %H:%M') }}
{% if g.user.is_admin %}
(<a href="{{ url_for('edit_comment', id=comment.id) }}">edit</a>)
{% endif %}
<div class=body>{{ comment.rendered_text }}</div></li> <div class=body>{{ comment.rendered_text }}</div></li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -30,7 +33,7 @@
{% if g.user %} {% if g.user %}
<div id=add-comment> <div id=add-comment>
<h2>Add Comment</h2> <h2>Add Comment</h2>
<form action=#add-comment method=post> <form action="" method=post>
<p>Title: <input type=text name=title value="{{ request.form.title }}" size=30> <p>Title: <input type=text name=title value="{{ request.form.title }}" size=30>
<p><textarea name=text cols=40 rows=8>{{ request.form.text }}</textarea> <p><textarea name=text cols=40 rows=8>{{ request.form.text }}</textarea>
<p><input type=submit value="Add Comment"> <p><input type=submit value="Add Comment">

View file

@ -133,3 +133,12 @@ def format_timedelta(delta, granularity='second', threshold=.85):
rv += u's' rv += u's'
return rv return rv
return u'' return u''
def display_openid(openid):
if not openid:
return ''
rv = openid
if rv.startswith(('http://', 'https://')):
rv = rv.split('/', 2)[-1]
return rv.rstrip('/')

View file

@ -1,6 +1,8 @@
from flask import Module, render_template, session, redirect, url_for, \ from flask import Module, render_template, session, redirect, url_for, \
request, flash, g, Response request, flash, g, Response
from flaskext.openid import COMMON_PROVIDERS
from flask_website import oid, twitter from flask_website import oid, twitter
from flask_website.utils import requires_login
from flask_website.database import db_session, User from flask_website.database import db_session, User
general = Module(__name__) general = Module(__name__)
@ -30,28 +32,20 @@ def logout():
def login(): def login():
if g.user is not None: if g.user is not None:
return redirect(url_for('general.index')) return redirect(url_for('general.index'))
if request.method == 'POST': if reassign and 'cancel' in request.form:
openid = request.values.get('openid') flash(u'Cancelled. The OpenID was not changed.')
if openid: return redirect(oid.get_next_url())
return oid.try_login(openid, ask_for=['fullname', 'nickname']) openid = request.values.get('openid')
if not openid:
openid = COMMON_PROVIDERS.get(request.args.get('provider'))
if openid:
return oid.try_login(openid, ask_for=['fullname', 'nickname'])
error = oid.fetch_error() error = oid.fetch_error()
if error: if error:
flash(u'Error: ' + error) flash(u'Error: ' + error)
return render_template('general/login.html', next=oid.get_next_url()) return render_template('general/login.html', next=oid.get_next_url())
@oid.after_login
def create_or_login(resp):
session['openid'] = resp.identity_url
user = User.query.filter_by(openid=resp.identity_url).first()
if user is not None:
flash(u'Successfully signed in')
g.user = user
return redirect(oid.get_next_url())
return redirect(url_for('first_login', next=oid.get_next_url(),
name=resp.fullname or resp.nickname))
@general.route('/first-login/', methods=['GET', 'POST']) @general.route('/first-login/', methods=['GET', 'POST'])
def first_login(): def first_login():
if g.user is not None or 'openid' not in session: if g.user is not None or 'openid' not in session:
@ -68,3 +62,55 @@ def first_login():
return render_template('general/first_login.html', return render_template('general/first_login.html',
next=oid.get_next_url(), next=oid.get_next_url(),
openid=session['openid']) openid=session['openid'])
@general.route('/profile/', methods=['GET', 'POST'])
@requires_login
def profile():
name = g.user.name
if request.method == 'POST':
name = request.form['name'].strip()
if not name:
flash(u'Error: a name is required')
else:
g.user.name = name
db_session.commit()
flash(u'User profile updated')
return redirect(url_for('index'))
return render_template('general/profile.html', name=name)
@general.route('/profile/change-openid/', methods=['GET', 'POST'])
@requires_login
@oid.loginhandler
def change_openid():
if request.method == 'POST':
if 'cancel' in request.form:
flash(u'Cancelled. The OpenID was not changed.')
return redirect(oid.get_next_url())
openid = request.values.get('openid')
if not openid:
openid = COMMON_PROVIDERS.get(request.args.get('provider'))
if openid:
return oid.try_login(openid)
error = oid.fetch_error()
if error:
flash(u'Error: ' + error)
return render_template('general/change_openid.html',
next=oid.get_next_url())
@oid.after_login
def create_or_login(resp):
session['openid'] = resp.identity_url
user = g.user or User.query.filter_by(openid=resp.identity_url).first()
if user is None:
return redirect(url_for('first_login', next=oid.get_next_url(),
name=resp.fullname or resp.nickname))
if user.openid != resp.identity_url:
user.openid = resp.identity_url
db_session.commit()
flash(u'OpenID identity changed')
else:
flash(u'Successfully signed in')
return redirect(oid.get_next_url())

View file

@ -32,7 +32,7 @@ def new():
else: else:
title = request.form['title'] title = request.form['title']
body = request.form['body'] body = request.form['body']
if not body: if body:
flash(u'Error: you have to enter a snippet') flash(u'Error: you have to enter a snippet')
else: else:
category = Category.query.get(category_id) category = Category.query.get(category_id)
@ -55,9 +55,7 @@ def show(id):
if request.method == 'POST': if request.method == 'POST':
title = request.form['title'] title = request.form['title']
text = request.form['text'] text = request.form['text']
if not text: if text:
flash(u'Error: the text is required')
else:
db_session.add(Comment(snippet, g.user, title, text)) db_session.add(Comment(snippet, g.user, title, text))
db_session.commit() db_session.commit()
flash(u'Your comment was added') flash(u'Your comment was added')
@ -65,6 +63,35 @@ def show(id):
return render_template('snippets/show.html', snippet=snippet) return render_template('snippets/show.html', snippet=snippet)
@snippets.route('/comments/<int:id>/', methods=['GET', 'POST'])
@requires_admin
def edit_comment(id):
comment = Comment.query.get(id)
if comment is None:
abort(404)
form = dict(title=comment.title, text=comment.text)
if request.method == 'POST':
if 'delete' in request.form:
db_session.delete(comment)
db_session.commit()
flash(u'Comment was deleted.')
return redirect(comment.snippet.url)
elif 'cancel' in request.form:
return redirect(comment.snippet.url)
form['title'] = request.form['title']
form['text'] = request.form['text']
if not form['text']:
flash(u'Error: comment text is required.')
else:
comment.title = form['title']
comment.text = form['text']
db_session.commit()
flash(u'Comment was updated.')
return redirect(comment.snippet.url)
return render_template('snippets/edit_comment.html', form=form,
comment=comment)
@snippets.route('/edit/<int:id>/', methods=['GET', 'POST']) @snippets.route('/edit/<int:id>/', methods=['GET', 'POST'])
@requires_login @requires_login
def edit(id): def edit(id):