Admin interface for snippets
This commit is contained in:
parent
45df60cfc6
commit
53ce827b17
15 changed files with 214 additions and 83 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
from flask import Flask, session, g, render_template
|
from flask import Flask, session, g, render_template
|
||||||
|
from flaskext.openid import OpenID
|
||||||
|
|
||||||
import websiteconfig as config
|
import websiteconfig as config
|
||||||
|
|
||||||
|
|
@ -6,6 +7,9 @@ app = Flask(__name__)
|
||||||
app.debug = config.DEBUG
|
app.debug = config.DEBUG
|
||||||
app.secret_key = config.SECRET_KEY
|
app.secret_key = config.SECRET_KEY
|
||||||
|
|
||||||
|
from flask_website.openid_auth import DatabaseOpenIDStore
|
||||||
|
oid = OpenID(store_factory=DatabaseOpenIDStore)
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(error):
|
def not_found(error):
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ class User(Model):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.openid = openid
|
self.openid = openid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_admin(self):
|
||||||
|
return self.openid in config.ADMINS
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return type(self) is type(other) and self.id == other.id
|
return type(self) is type(other) and self.id == other.id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,14 @@
|
||||||
from time import time
|
from time import time
|
||||||
from hashlib import sha1
|
|
||||||
|
|
||||||
from openid.association import Association
|
from openid.association import Association
|
||||||
from openid.store.interface import OpenIDStore
|
from openid.store.interface import OpenIDStore
|
||||||
from openid.consumer.consumer import Consumer, SUCCESS, CANCEL
|
|
||||||
from openid.consumer import discover
|
|
||||||
from openid.store import nonce
|
from openid.store import nonce
|
||||||
|
|
||||||
# python-openid is a really stupid library in that regard, we have
|
|
||||||
# to disable logging by monkey patching
|
|
||||||
from openid import oidutil
|
|
||||||
oidutil.log = lambda *a, **kw: None
|
|
||||||
|
|
||||||
from flask import request, redirect, abort, url_for, flash, session
|
|
||||||
from flask_website.database import User, db_session, OpenIDAssociation, \
|
from flask_website.database import User, db_session, OpenIDAssociation, \
|
||||||
OpenIDUserNonce
|
OpenIDUserNonce
|
||||||
|
|
||||||
|
|
||||||
class WebsiteOpenIDStore(OpenIDStore):
|
class DatabaseOpenIDStore(OpenIDStore):
|
||||||
"""Implements the open store for the website using the database."""
|
"""Implements the open store for the website using the database."""
|
||||||
|
|
||||||
def storeAssociation(self, server_url, association):
|
def storeAssociation(self, server_url, association):
|
||||||
|
|
@ -30,6 +21,7 @@ class WebsiteOpenIDStore(OpenIDStore):
|
||||||
assoc_type=association.assoc_type
|
assoc_type=association.assoc_type
|
||||||
)
|
)
|
||||||
db_session.add(assoc)
|
db_session.add(assoc)
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
def getAssociation(self, server_url, handle=None):
|
def getAssociation(self, server_url, handle=None):
|
||||||
q = OpenIDAssociation.query.filter_by(server_url=server_url)
|
q = OpenIDAssociation.query.filter_by(server_url=server_url)
|
||||||
|
|
@ -46,10 +38,13 @@ class WebsiteOpenIDStore(OpenIDStore):
|
||||||
return result_assoc
|
return result_assoc
|
||||||
|
|
||||||
def removeAssociation(self, server_url, handle):
|
def removeAssociation(self, server_url, handle):
|
||||||
return OpenIDAssociation.query.filter(
|
try:
|
||||||
(OpenIDAssociation.server_url == server_url) &
|
return OpenIDAssociation.query.filter(
|
||||||
(OpenIDAssociation.handle == handle)
|
(OpenIDAssociation.server_url == server_url) &
|
||||||
).delete()
|
(OpenIDAssociation.handle == handle)
|
||||||
|
).delete()
|
||||||
|
finally:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
def useNonce(self, server_url, timestamp, salt):
|
def useNonce(self, server_url, timestamp, salt):
|
||||||
if abs(timestamp - time()) > nonce.SKEW:
|
if abs(timestamp - time()) > nonce.SKEW:
|
||||||
|
|
@ -64,64 +59,21 @@ class WebsiteOpenIDStore(OpenIDStore):
|
||||||
rv = OpenIDUserNonce(server_url=server_url, timestamp=timestamp,
|
rv = OpenIDUserNonce(server_url=server_url, timestamp=timestamp,
|
||||||
salt=salt)
|
salt=salt)
|
||||||
db_session.add(rv)
|
db_session.add(rv)
|
||||||
|
db_session.commit()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cleanupNonces(self):
|
def cleanupNonces(self):
|
||||||
return OpenIDUserNonce.query.filter(
|
try:
|
||||||
OpenIDUserNonce.timestamp <= int(time() - nonce.SKEW)
|
return OpenIDUserNonce.query.filter(
|
||||||
).delete()
|
OpenIDUserNonce.timestamp <= int(time() - nonce.SKEW)
|
||||||
|
).delete()
|
||||||
|
finally:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
def cleanupAssociations(self):
|
def cleanupAssociations(self):
|
||||||
return OpenIDAssociation.query.filter(
|
|
||||||
OpenIDAssociation.lifetime < int(time())
|
|
||||||
).delete()
|
|
||||||
|
|
||||||
|
|
||||||
def redirect_back():
|
|
||||||
return redirect(request.values.get('next') or url_for('general.index'))
|
|
||||||
|
|
||||||
|
|
||||||
def check_return_from_provider():
|
|
||||||
if request.args.get('openid_complete') != u'yes':
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
consumer = Consumer(session, WebsiteOpenIDStore())
|
|
||||||
openid_response = consumer.complete(request.args.to_dict(),
|
|
||||||
url_for('general.login',
|
|
||||||
_external=True))
|
|
||||||
if openid_response.status == SUCCESS:
|
|
||||||
return create_or_login(openid_response.identity_url)
|
|
||||||
elif openid_response.status == CANCEL:
|
|
||||||
flash(u'Error: The request was cancelled')
|
|
||||||
return redirect(url_for('general.login'))
|
|
||||||
flash(u'Error: OpenID authentication error')
|
|
||||||
return redirect(url_for('general.login'))
|
|
||||||
finally:
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def create_or_login(identity_url):
|
|
||||||
session['openid'] = identity_url
|
|
||||||
user = User.query.filter_by(openid=identity_url).first()
|
|
||||||
if user is None:
|
|
||||||
next_url = request.values.get('next')
|
|
||||||
return redirect(url_for('general.first_login', next=next_url))
|
|
||||||
flash(u'Successfully logged in')
|
|
||||||
return redirect_back()
|
|
||||||
|
|
||||||
|
|
||||||
def login(identity_url):
|
|
||||||
try:
|
|
||||||
try:
|
try:
|
||||||
consumer = Consumer(session, WebsiteOpenIDStore())
|
return OpenIDAssociation.query.filter(
|
||||||
auth_request = consumer.begin(identity_url)
|
OpenIDAssociation.lifetime < int(time())
|
||||||
except discover.DiscoveryFailure:
|
).delete()
|
||||||
flash(u'Error: The OpenID was invalid')
|
finally:
|
||||||
return redirect(url_for('general.login'))
|
db_session.commit()
|
||||||
trust_root = request.host_url
|
|
||||||
next_url = request.values.get('next') or url_for('general.index')
|
|
||||||
redirect_to = url_for('general.login', openid_complete='yes',
|
|
||||||
next=next_url, _external=True)
|
|
||||||
return redirect(auth_request.redirectURL(trust_root, redirect_to))
|
|
||||||
finally:
|
|
||||||
db_session.commit()
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@ blockquote { margin: 0; font-style: italic; color: #444; }
|
||||||
margin: 5px 0 0 0; font-size: 0.9em; }
|
margin: 5px 0 0 0; font-size: 0.9em; }
|
||||||
.message { background: #DEEBF3; color: #004B6B; padding: 5px 30px;
|
.message { background: #DEEBF3; color: #004B6B; padding: 5px 30px;
|
||||||
margin: 10px -30px; }
|
margin: 10px -30px; }
|
||||||
|
.actions { margin-top: 0; }
|
||||||
|
table { border: 1px solid black; border-collapse: collapse;
|
||||||
|
margin: 15px 0; }
|
||||||
|
td, th { border: 1px solid black; padding: 4px 10px;
|
||||||
|
text-align: left; }
|
||||||
|
th { background: #eee; font-weight: normal; }
|
||||||
|
|
||||||
|
td input { border: none; padding: 0; }
|
||||||
|
|
||||||
/* forms */
|
/* forms */
|
||||||
input, textarea, select { border: 1px solid black; padding: 2px; background: white;
|
input, textarea, select { border: 1px solid black; padding: 2px; background: white;
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@
|
||||||
site. Choose wisely because you cannot change this value later:
|
site. Choose wisely because you cannot change this value later:
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Name:
|
<dt>Name:
|
||||||
<dd><input type=text name=name size=30>
|
<dd><input type=text name=name value="{{ request.values.name }}" size=30>
|
||||||
</dl>
|
</dl>
|
||||||
<p>
|
<p>
|
||||||
|
<input type=hidden name=next value="{{ next }}">
|
||||||
<input type=submit value=Continue>
|
<input type=submit value=Continue>
|
||||||
<input type=submit name=cancel value=Cancel>
|
<input type=submit name=cancel value=Cancel>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<p>
|
<p>
|
||||||
OpenID URL:
|
OpenID URL:
|
||||||
<input type=text name=openid class=openid size=30>
|
<input type=text name=openid class=openid size=30>
|
||||||
<input type=hidden name=next value="{{ request.values.next or request.referrer or url_for('general.index') }}">
|
<input type=hidden name=next value="{{ next }}">
|
||||||
<input type=submit value=Login>
|
<input type=submit value=Login>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
20
flask_website/templates/snippets/delete_category.html
Normal file
20
flask_website/templates/snippets/delete_category.html
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "snippets/layout.html" %}
|
||||||
|
{% block title %}Snippets Archive{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h2>Delete Category “{{ category.name }}”</h2>
|
||||||
|
<p>
|
||||||
|
Do you want to delete this category? And if yes, what should
|
||||||
|
happen with the snippets in the category?
|
||||||
|
<form action="" method=post>
|
||||||
|
<p>
|
||||||
|
<select name=move_to>
|
||||||
|
<option value="">Delete Snippets</option>
|
||||||
|
{%- for category in other_categories %}
|
||||||
|
<option value={{ category.id }}>Move to “{{ category.name }}”</option>
|
||||||
|
{%- endfor %}
|
||||||
|
</select>
|
||||||
|
<p>
|
||||||
|
<input type=submit value=Delete>
|
||||||
|
<input type=submit name=cancel value=Cancel>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -17,13 +17,17 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
Want to share something? Then add a
|
Want to share something? Then add a
|
||||||
<a href="{{ url_for('snippets.new') }}">new snippet</a>.
|
<a href="{{ url_for('new') }}">new snippet</a>.
|
||||||
<h2>Snippets by Category</h2>
|
<h2>Snippets by Category</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
<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>
|
||||||
|
|
|
||||||
30
flask_website/templates/snippets/manage_categories.html
Normal file
30
flask_website/templates/snippets/manage_categories.html
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends "snippets/layout.html" %}
|
||||||
|
{% block title %}Snippets Archive{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h2>Manage Categories</h2>
|
||||||
|
{% if categories %}
|
||||||
|
<form action="" method=post>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name
|
||||||
|
<th>Slug
|
||||||
|
<th>#
|
||||||
|
<th>?
|
||||||
|
{% for category in categories %}
|
||||||
|
<tr>
|
||||||
|
<td><input type=text name=name.{{ category.id }} value="{{ category.name }}" size=24>
|
||||||
|
<td><input type=text name=slug.{{ category.id }} value="{{ category.slug }}" size=17>
|
||||||
|
<td>{{ category.snippets.count() }}
|
||||||
|
<td><a href="{{ url_for('delete_category', id=category.id) }}">delete</a>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<p class=actions>
|
||||||
|
<input type=submit value="Update categories">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<form action="{{ url_for('new_category') }}" method=post>
|
||||||
|
<p>Or create a new category:
|
||||||
|
<input type=text name=name size=30>
|
||||||
|
<input type=submit value=Create>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<h2>{{ snippet.title }}</h2>
|
<h2>{{ snippet.title }}</h2>
|
||||||
<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 %}
|
{% 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('snippets.edit', id=snippet.id) }}">edit</a>)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ snippet.rendered_body }}
|
{{ snippet.rendered_body }}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pygments import highlight
|
||||||
from pygments.formatters import HtmlFormatter
|
from pygments.formatters import HtmlFormatter
|
||||||
from pygments.lexers import get_lexer_by_name
|
from pygments.lexers import get_lexer_by_name
|
||||||
from pygments.util import ClassNotFound
|
from pygments.util import ClassNotFound
|
||||||
from flask import g, url_for, flash, request, redirect, Markup
|
from flask import g, url_for, flash, abort, request, redirect, Markup
|
||||||
from flask_website.flaskystyle import FlaskyStyle # same as docs
|
from flask_website.flaskystyle import FlaskyStyle # same as docs
|
||||||
|
|
||||||
from flask_website.database import User
|
from flask_website.database import User
|
||||||
|
|
@ -85,3 +85,12 @@ def requires_login(f):
|
||||||
return redirect(url_for('general.login', next=request.path))
|
return redirect(url_for('general.login', next=request.path))
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
|
def requires_admin(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if not g.user.is_admin:
|
||||||
|
abort(401)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return requires_login(decorated_function)
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,32 @@ database = [
|
||||||
''',
|
''',
|
||||||
bitbucket='leafstorm/flask-xml-rpc',
|
bitbucket='leafstorm/flask-xml-rpc',
|
||||||
docs='http://packages.python.org/Flask-XML-RPC/'
|
docs='http://packages.python.org/Flask-XML-RPC/'
|
||||||
|
),
|
||||||
|
Extension('flask-csrf', 'Steve Losh',
|
||||||
|
description='''
|
||||||
|
<p>A small Flask extension for adding
|
||||||
|
<a href=http://en.wikipedia.org/wiki/CSRF>CSRF</a> protection.
|
||||||
|
''',
|
||||||
|
docs='http://sjl.bitbucket.org/flask-csrf/',
|
||||||
|
bitbucket='sjl/flask-csrf'
|
||||||
|
),
|
||||||
|
Extension('flask-lesscss', 'Steve Losh',
|
||||||
|
description='''
|
||||||
|
<p>
|
||||||
|
A small Flask extension that makes it easy to use
|
||||||
|
<a href=http://lesscss.org/>LessCSS</a> with your
|
||||||
|
Flask application.
|
||||||
|
''',
|
||||||
|
docs='http://sjl.bitbucket.org/flask-lesscss/',
|
||||||
|
bitbucket='sjl/flask-lesscss'
|
||||||
|
),
|
||||||
|
Extension('flask-urls', 'Steve Losh',
|
||||||
|
description='''
|
||||||
|
<p>
|
||||||
|
A collection of URL-related functions for Flask applications.
|
||||||
|
''',
|
||||||
|
docs='http://sjl.bitbucket.org/flask-urls/',
|
||||||
|
bitbucket='sjl/flask-urls'
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
database.sort(key=lambda x: x.name.lower())
|
database.sort(key=lambda x: x.name.lower())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
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 flask_website import openid_auth
|
from flask_website import oid
|
||||||
from flask_website.database import db_session, User
|
from flask_website.database import db_session, User
|
||||||
|
|
||||||
general = Module(__name__)
|
general = Module(__name__)
|
||||||
|
|
@ -20,17 +20,30 @@ def logout():
|
||||||
|
|
||||||
|
|
||||||
@general.route('/login/', methods=['GET', 'POST'])
|
@general.route('/login/', methods=['GET', 'POST'])
|
||||||
|
@oid.loginhandler
|
||||||
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'))
|
||||||
rv = openid_auth.check_return_from_provider()
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
openid = request.values.get('openid')
|
openid = request.values.get('openid')
|
||||||
if openid:
|
if openid:
|
||||||
return openid_auth.login(openid)
|
return oid.try_login(openid, ask_for=['fullname', 'nickname'])
|
||||||
return render_template('general/login.html')
|
error = oid.fetch_error()
|
||||||
|
if error:
|
||||||
|
flash(u'Error: ' + error)
|
||||||
|
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'])
|
||||||
|
|
@ -45,6 +58,7 @@ def first_login():
|
||||||
db_session.add(User(request.form['name'], session['openid']))
|
db_session.add(User(request.form['name'], session['openid']))
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
flash(u'Successfully created profile and logged in')
|
flash(u'Successfully created profile and logged in')
|
||||||
return openid_auth.redirect_back()
|
return redirect(oid.get_next_url())
|
||||||
return render_template('general/first_login.html',
|
return render_template('general/first_login.html',
|
||||||
|
next=oid.get_next_url(),
|
||||||
openid=session['openid'])
|
openid=session['openid'])
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from urlparse import urljoin
|
||||||
from flask import Module, render_template, request, flash, abort, redirect, \
|
from flask import Module, render_template, request, flash, abort, redirect, \
|
||||||
g, url_for
|
g, url_for
|
||||||
from werkzeug.contrib.atom import AtomFeed
|
from werkzeug.contrib.atom import AtomFeed
|
||||||
from flask_website.utils import requires_login, format_creole
|
from flask_website.utils import requires_login, requires_admin, format_creole
|
||||||
from flask_website.database import Category, Snippet, Comment, db_session
|
from flask_website.database import Category, Snippet, Comment, db_session
|
||||||
|
|
||||||
snippets = Module(__name__, url_prefix='/snippets')
|
snippets = Module(__name__, url_prefix='/snippets')
|
||||||
|
|
@ -71,7 +71,7 @@ def edit(id):
|
||||||
snippet = Snippet.query.get(id)
|
snippet = Snippet.query.get(id)
|
||||||
if snippet is None:
|
if snippet is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if snippet.author != g.user:
|
if g.user is None or (not g.user.is_admin and snippet.author != g.user):
|
||||||
abort(401)
|
abort(401)
|
||||||
preview = None
|
preview = None
|
||||||
form = dict(title=snippet.title, body=snippet.body,
|
form = dict(title=snippet.title, body=snippet.body,
|
||||||
|
|
@ -117,6 +117,64 @@ def category(slug):
|
||||||
snippets=snippets)
|
snippets=snippets)
|
||||||
|
|
||||||
|
|
||||||
|
@snippets.route('/manage-categories/', methods=['GET', 'POST'])
|
||||||
|
@requires_admin
|
||||||
|
def manage_categories():
|
||||||
|
categories = Category.query.order_by(Category.name).all()
|
||||||
|
if request.method == 'POST':
|
||||||
|
for category in categories:
|
||||||
|
category.name = request.form['name.%d' % category.id]
|
||||||
|
category.slug = request.form['slug.%d' % category.id]
|
||||||
|
db_session.commit()
|
||||||
|
flash(u'Categories updated')
|
||||||
|
return redirect(url_for('manage_categories'))
|
||||||
|
return render_template('snippets/manage_categories.html',
|
||||||
|
categories=categories)
|
||||||
|
|
||||||
|
|
||||||
|
@snippets.route('/new-category/', methods=['POST'])
|
||||||
|
@requires_admin
|
||||||
|
def new_category():
|
||||||
|
category = Category(name=request.form['name'])
|
||||||
|
db_session.add(category)
|
||||||
|
db_session.commit()
|
||||||
|
flash(u'Category %s created.' % category.name)
|
||||||
|
return redirect(url_for('manage_categories'))
|
||||||
|
|
||||||
|
|
||||||
|
@snippets.route('/delete-category/<int:id>/', methods=['GET', 'POST'])
|
||||||
|
@requires_admin
|
||||||
|
def delete_category(id):
|
||||||
|
category = Category.query.get(id)
|
||||||
|
if category is None:
|
||||||
|
abort(404)
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'cancel' in request.form:
|
||||||
|
flash(u'Deletion was aborted')
|
||||||
|
return redirect(url_for('manage_categories'))
|
||||||
|
move_to_id = request.form.get('move_to', type=int)
|
||||||
|
if move_to_id:
|
||||||
|
move_to = Category.query.get(move_to_id)
|
||||||
|
if move_to is None:
|
||||||
|
flash(u'Category was removed in the meantime')
|
||||||
|
else:
|
||||||
|
for snippet in category.snippets.all():
|
||||||
|
snippet.category = move_to
|
||||||
|
db_session.delete(category)
|
||||||
|
flash(u'Category %s deleted and entries moved to %s.' %
|
||||||
|
(category.name, move_to.name))
|
||||||
|
else:
|
||||||
|
category.snippets.delete()
|
||||||
|
db_session.delete(category)
|
||||||
|
flash(u'Category %s deleted' % category.name)
|
||||||
|
db_session.commit()
|
||||||
|
return redirect(url_for('manage_categories'))
|
||||||
|
return render_template('snippets/delete_category.html',
|
||||||
|
category=category,
|
||||||
|
other_categories=Category.query
|
||||||
|
.filter(Category.id != category.id).all())
|
||||||
|
|
||||||
|
|
||||||
@snippets.route('/recent.atom')
|
@snippets.route('/recent.atom')
|
||||||
def recent_feed():
|
def recent_feed():
|
||||||
feed = AtomFeed(u'Recent Flask Snippets',
|
feed = AtomFeed(u'Recent Flask Snippets',
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ DEBUG = False
|
||||||
|
|
||||||
SECRET_KEY = 'testkey'
|
SECRET_KEY = 'testkey'
|
||||||
DATABASE_URI = 'sqlite:///' + os.path.join(_basedir, 'flask-website.db')
|
DATABASE_URI = 'sqlite:///' + os.path.join(_basedir, 'flask-website.db')
|
||||||
|
ADMINS = frozenset(['http://lucumr.pocoo.org/'])
|
||||||
|
|
||||||
THREADS_PER_PAGE = 15
|
THREADS_PER_PAGE = 15
|
||||||
MAILINGLIST_PATH = os.path.join(_basedir, '_mailinglist')
|
MAILINGLIST_PATH = os.path.join(_basedir, '_mailinglist')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue