forked from orbit-oss/flask
Beefed up the tutorial
This commit is contained in:
parent
1246f4088a
commit
6dd92ae4b3
7 changed files with 306 additions and 92 deletions
|
|
@ -40,6 +40,8 @@ So here a simple example how you can use SQLite 3 with Flask::
|
|||
g.db.close()
|
||||
return response
|
||||
|
||||
.. _easy-querying:
|
||||
|
||||
Easy Querying
|
||||
`````````````
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,14 @@ Or pass it to run::
|
|||
|
||||
Both will have exactly the same effect.
|
||||
|
||||
.. admonition:: Attention
|
||||
|
||||
The interactive debugger however does not work in forking environments
|
||||
which makes it nearly impossible to use on production servers but the
|
||||
debugger still allows the execution of arbitrary code which makes it a
|
||||
major security risk and **must never be used on production machines**
|
||||
because of that.
|
||||
|
||||
|
||||
Routing
|
||||
-------
|
||||
|
|
|
|||
157
docs/testing.rst
157
docs/testing.rst
|
|
@ -22,72 +22,78 @@ installation.
|
|||
The Application
|
||||
---------------
|
||||
|
||||
First we need an application to test for functionality. Let's start
|
||||
simple with a Hello World application (`hello.py`)::
|
||||
First we need an application to test for functionality. For the testing
|
||||
we will use the application from the :ref:`tutorial`. If you don't have
|
||||
that application yet, get the sources from `the examples`_.
|
||||
|
||||
from flask import Flask, render_template_string
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/<name>')
|
||||
def hello(name='World'):
|
||||
return render_template_string('''
|
||||
<!doctype html>
|
||||
<title>Hello {{ name }}!</title>
|
||||
<h1>Hello {{ name }}!</h1>
|
||||
''', name=name)
|
||||
.. _the examples:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
|
||||
|
||||
The Testing Skeleton
|
||||
--------------------
|
||||
|
||||
In order to test that, we add a second module (
|
||||
`hello_tests.py`) and create a unittest skeleton there::
|
||||
`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
|
||||
import unittest
|
||||
import hello
|
||||
import flaskr
|
||||
import tempfile
|
||||
|
||||
class HelloWorldTestCase(unittest.TestCase):
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = hello.app.test_client()
|
||||
self.db = tempfile.NamedTemporaryFile()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.DATABASE = self.db.name
|
||||
flaskr.init_db()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
The code in the `setUp` function creates a new test client. That function
|
||||
is called before each individual test function. What the test client does
|
||||
for us is giving us a simple interface to the application. We can trigger
|
||||
test requests to the application and the client will also keep track of
|
||||
cookies for us.
|
||||
The code in the `setUp` function creates a new test client and initialize
|
||||
a new database. That function is called before each individual test function.
|
||||
What the test client does for us is giving us a simple interface to the
|
||||
application. We can trigger test requests to the application and the
|
||||
client will also keep track of cookies for us.
|
||||
|
||||
Because SQLite3 is filesystem based we can easily use the tempfile module
|
||||
to create a temporary database and initialize it. Just make sure that you
|
||||
keep a reference to the :class:`~tempfile.NamedTemporaryFile` around (we
|
||||
store it as `self.db` because of that) so that the garbage collector does
|
||||
not remove that object and with it the database from the filesystem.
|
||||
|
||||
If we now run that testsuite, we should see the following output::
|
||||
|
||||
$ python hello_tests.py
|
||||
$ python flaskr_tests.py
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 0 tests in 0.000s
|
||||
|
||||
OK
|
||||
|
||||
Even though it did not run any tests, we already know that our hello
|
||||
Even though it did not run any tests, we already know that our flaskr
|
||||
application is syntactically valid, otherwise the import would have died
|
||||
with an exception.
|
||||
|
||||
The First Test
|
||||
--------------
|
||||
|
||||
Now we can add the first test. Let's check that the application greets us
|
||||
with "Hello World" if we access it on ``/``. For that we modify our
|
||||
created test case class so that it looks like this::
|
||||
Now we can add the first test. Let's check that the application shows
|
||||
"No entries here so far" if we access the root of the application (``/``).
|
||||
For that we modify our created test case class so that it looks like
|
||||
this::
|
||||
|
||||
class HelloWorldTestCase(unittest.TestCase):
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = hello.app.test_client()
|
||||
self.db = tempfile.NamedTemporaryFile()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.DATABASE = self.db.name
|
||||
flaskr.init_db()
|
||||
|
||||
def test_hello_world(self):
|
||||
def test_empty_db(self):
|
||||
rv = self.app.get('/')
|
||||
assert 'Hello World!' in rv.data
|
||||
assert 'No entries here so far' in rv.data
|
||||
|
||||
Test functions begin with the word `test`. Every function named like that
|
||||
will be picked up automatically. By using `self.app.get` we can send an
|
||||
|
|
@ -95,22 +101,87 @@ HTTP `GET` request to the application with the given path. The return
|
|||
value will be a :class:`~flask.Flask.response_class` object. We can now
|
||||
use the :attr:`~werkzeug.BaseResponse.data` attribute to inspect the
|
||||
return value (as string) from the application. In this case, we ensure
|
||||
that ``'Hello World!'`` is part of the output.
|
||||
that ``'No entries here so far'`` is part of the output.
|
||||
|
||||
Run it again and you should see one passing test. Let's add a second test
|
||||
here::
|
||||
Run it again and you should see one passing test::
|
||||
|
||||
def test_hello_name(self):
|
||||
rv = self.app.get('/Peter')
|
||||
assert 'Hello Peter!' in rv.data
|
||||
$ python flaskr_tests.py
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.034s
|
||||
|
||||
Of course you can submit forms with the test client as well. For that and
|
||||
other features of the test client, check the documentation of the Werkzeug
|
||||
test :class:`~werkzeug.Client` and the tests of the MiniTwit example
|
||||
application:
|
||||
OK
|
||||
|
||||
Of course you can submit forms with the test client as well which we will
|
||||
use now to log our user in.
|
||||
|
||||
Logging In and Out
|
||||
------------------
|
||||
|
||||
The majority of the functionality of our application is only available for
|
||||
the administration user. So we need a way to log our test client into the
|
||||
application and out of it again. For that we fire some requests to the
|
||||
login and logout pages with the required form data (username and
|
||||
password). Because the login and logout pages redirect, we tell the
|
||||
client to `follow_redirects`.
|
||||
|
||||
Add the following two methods do your `FlaskrTestCase` class::
|
||||
|
||||
def login(self, username, password):
|
||||
return self.app.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
|
||||
def logout(self):
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
Now we can easily test if logging in and out works and that it fails with
|
||||
invalid credentials. Add this as new test to the class::
|
||||
|
||||
def test_login_logout(self):
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
assert 'You were logged in' in rv.data
|
||||
rv = self.logout()
|
||||
assert 'You were logged out' in rv.data
|
||||
rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD)
|
||||
assert 'Invalid username' in rv.data
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x')
|
||||
assert 'Invalid password' in rv.data
|
||||
|
||||
Test Adding Messages
|
||||
--------------------
|
||||
|
||||
Now we can also test that adding messages works. Add a new test method
|
||||
like this::
|
||||
|
||||
def test_messages(self):
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
rv = self.app.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
text='<strong>HTML</strong> allowed here'
|
||||
), follow_redirects=True)
|
||||
assert 'No entries here so far' not in rv.data
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
assert '<Hello>' in rv.data
|
||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
||||
|
||||
Here we also check that HTML is allowed in the text but not in the title
|
||||
which is the intended behavior.
|
||||
|
||||
Running that should now give us three passing tests::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.332s
|
||||
|
||||
OK
|
||||
|
||||
For more complex tests with headers and status codes, check out the
|
||||
`MiniTwit Example`_ from the sources. That one contains a larger test
|
||||
suite.
|
||||
|
||||
- Werkzeug Test :class:`~werkzeug.Client`
|
||||
- `MiniTwit Example`_
|
||||
|
||||
.. _MiniTwit Example:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ less web-2.0-ish name ;) Basically we want it to do the following things:
|
|||
3. the page shows all entries so far in reverse order (newest on top) and
|
||||
the user can add new ones from there if logged in.
|
||||
|
||||
We will be using SQlite3 directly for that application because it's good
|
||||
enough for an application of that size. For larger applications however
|
||||
it makes a lot of sense to use `SQLAlchemy`_ that handles database
|
||||
connections in a more intelligent way, allows you to target different
|
||||
relational databases at once and more. You might also want to consider
|
||||
one of the popular NoSQL databases if your data is more suited for those.
|
||||
|
||||
Here a screenshot from the final application:
|
||||
|
||||
.. image:: _static/flaskr.png
|
||||
|
|
@ -38,6 +45,8 @@ Here a screenshot from the final application:
|
|||
:class: screenshot
|
||||
:alt: screenshot of the final application
|
||||
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
|
||||
Step 0: Creating The Folders
|
||||
----------------------------
|
||||
|
||||
|
|
@ -50,7 +59,13 @@ application::
|
|||
|
||||
The `flaskr` folder is not a python package, but just something where we
|
||||
drop our files. Directly into this folder we will then put our database
|
||||
schema as well as main module in the following steps.
|
||||
schema as well as main module in the following steps. The files inside
|
||||
the `static` folder are available to users of the application via `HTTP`.
|
||||
This is the place where css and javascript files go. Inside the
|
||||
`templates` folder Flask will look for `Jinja2`_ templates. Drop all the
|
||||
templates there.
|
||||
|
||||
.. _Jinja2: http://jinja.pocoo.org/2/
|
||||
|
||||
Step 1: Database Schema
|
||||
-----------------------
|
||||
|
|
@ -79,12 +94,18 @@ Step 2: Application Setup Code
|
|||
|
||||
Now that we have the schema in place we can create the application module.
|
||||
Let's call it `flaskr.py` inside the `flaskr` folder. For starters we
|
||||
will add the imports we will need as well as the config section::
|
||||
will add the imports we will need as well as the config section. For
|
||||
small applications it's a possibility to drop the configuration directly
|
||||
into the module which we will be doing here. However a cleaner solution
|
||||
would be to create a separate `.ini` or `.py` file and load that or import
|
||||
the values from there.
|
||||
|
||||
::
|
||||
|
||||
# all the imports
|
||||
import sqlite3
|
||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||
render_template, flash
|
||||
from flask import Flask, request, session, g, redirect, url_for, \
|
||||
abort, render_template, flash
|
||||
|
||||
# configuration
|
||||
DATABASE = '/tmp/flaskr.db'
|
||||
|
|
@ -93,17 +114,25 @@ will add the imports we will need as well as the config section::
|
|||
USERNAME = 'admin'
|
||||
PASSWORD = 'default'
|
||||
|
||||
The `with_statement` and :func:`~contextlib.closing` function are used to
|
||||
make dealing with the database connection easier later on for setting up
|
||||
the initial database. Next we can create our actual application and
|
||||
initialize it with the config::
|
||||
Next we can create our actual application and initialize it with the
|
||||
config::
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET_KEY
|
||||
app.debug = DEBUG
|
||||
|
||||
We can also add a method to easily connect to the database sepcified::
|
||||
The `secret_key` is needed to keep the client-side sessions secure.
|
||||
Choose that key wisely and as hard to guess and complex as possible. The
|
||||
debug flag enables or disables the interactive debugger. Never leave
|
||||
debug mode activated in a production system because it will allow users to
|
||||
executed code on the server!
|
||||
|
||||
We also add a method to easily connect to the database specified. That
|
||||
can be used to open a connection on request and also from the interactive
|
||||
Python shell or a script. This will come in handy later
|
||||
|
||||
::
|
||||
|
||||
def connect_db():
|
||||
return sqlite3.connect(DATABASE)
|
||||
|
|
@ -114,6 +143,11 @@ server if we run that file as standalone application::
|
|||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
||||
With that out of the way you should be able to start up the application
|
||||
without problems. When you head over to the server you will get an 404
|
||||
page not found error because we don't have any views yet. But we will
|
||||
focus on that a little later. First we should get the database working.
|
||||
|
||||
.. admonition:: Troubleshooting
|
||||
|
||||
If you notice later that the browser cannot connect to the server
|
||||
|
|
@ -125,11 +159,6 @@ server if we run that file as standalone application::
|
|||
default and not every browser is happy with that. This forces IPv4
|
||||
usage.
|
||||
|
||||
With that out of the way you should be able to start up the application
|
||||
without problems. When you head over to the server you will get an 404
|
||||
page not found error because we don't have any views yet. But we will
|
||||
focus on that a little later. First we should get the database working.
|
||||
|
||||
Step 3: Creating The Database
|
||||
-----------------------------
|
||||
|
||||
|
|
@ -159,7 +188,8 @@ first (`__future__` imports must be the very first import)::
|
|||
from contextlib import closing
|
||||
|
||||
Next we can create a function called `init_db` that initializes the
|
||||
database::
|
||||
database. For this we can use the `connect_db` function we defined
|
||||
earlier. Just add that function below the `connect_db` function::
|
||||
|
||||
def init_db():
|
||||
with closing(connect_db()) as db:
|
||||
|
|
@ -167,21 +197,26 @@ database::
|
|||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
The :func:`~contextlib.closing` helper function allows us to keep a
|
||||
connection open for the duration of the `with` block. The
|
||||
:func:`~flask.Flask.open_resource` method of the application object
|
||||
supports that functionality out of the box, so it can be used in the
|
||||
`with` block directly. This function opens a file from the resource
|
||||
location (your `flaskr` folder) and allows you to read from it. We are
|
||||
using this here to execute a script on the database connection.
|
||||
|
||||
When we connect to a database we get a connection object (here called
|
||||
`db`) that can give us a cursor. On that cursor there is a method to
|
||||
execute a complete script. Finally we only have to commit the changes.
|
||||
SQLite 3 and other transactional databases will not commit unless you
|
||||
explicitly tell it to.
|
||||
|
||||
Now it is possible to create a database by starting up a Python shell and
|
||||
importing and calling that function::
|
||||
|
||||
>>> from flaskr import init_db
|
||||
>>> init_db()
|
||||
|
||||
The :meth:`~flask.Flask.open_resource` function opens a file from the
|
||||
resource location (your flaskr folder) and allows you to read from it. We
|
||||
are using this here to execute a script on the database connection.
|
||||
|
||||
When we connect to a database we get a connection object (here called
|
||||
`db`) that can give us a cursor. On that cursor there is a method to
|
||||
execute a complete script. Finally we only have to commit the changes and
|
||||
close the transaction.
|
||||
|
||||
Step 4: Request Database Connections
|
||||
------------------------------------
|
||||
|
||||
|
|
@ -225,7 +260,16 @@ view functions. We will need for of them:
|
|||
Show Entries
|
||||
````````````
|
||||
|
||||
This view shows all the entries stored in the database::
|
||||
This view shows all the entries stored in the database. It listens on the
|
||||
root of the application and will select title and text from the database.
|
||||
The one with the highest id (the newest entry) on top. The rows returned
|
||||
from the cursor are tuples with the columns ordered like specified in the
|
||||
select statement. This is good enough for small applications like here,
|
||||
but you might want to convert them into a dict. If you are interested how
|
||||
to do that, check out the :ref:`easy-querying` example.
|
||||
|
||||
The view function will pass the entries as dicts to the
|
||||
`show_entries.html` template and return the rendered one::
|
||||
|
||||
@app.route('/')
|
||||
def show_entries():
|
||||
|
|
@ -238,7 +282,9 @@ Add New Entry
|
|||
|
||||
This view lets the user add new entries if he's logged in. This only
|
||||
responds to `POST` requests, the actual form is shown on the
|
||||
`show_entries` page::
|
||||
`show_entries` page. If everything worked out well we will
|
||||
:func:`~flask.flash` an information message to the next request and
|
||||
redirect back to the `show_entries` page::
|
||||
|
||||
@app.route('/add', methods=['POST'])
|
||||
def add_entry():
|
||||
|
|
@ -250,10 +296,19 @@ responds to `POST` requests, the actual form is shown on the
|
|||
flash('New entry was successfully posted')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
Note that we check that the user is logged in here (the `logged_in` key is
|
||||
present in the session and `True`).
|
||||
|
||||
Login and Logout
|
||||
````````````````
|
||||
|
||||
These functions are used to sign the user in and out::
|
||||
These functions are used to sign the user in and out. Login checks the
|
||||
username and password against the ones from the configuration and sets the
|
||||
`logged_in` key in the session. If the user logged in successfully that
|
||||
key is set to `True` and the user is redirected back to the `show_entries`
|
||||
page. In that case also a message is flashed that informs the user he or
|
||||
she was logged in successfully. If an error occoured the template is
|
||||
notified about that and the user asked again::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
|
|
@ -269,6 +324,15 @@ These functions are used to sign the user in and out::
|
|||
return redirect(url_for('show_entries'))
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
The logout function on the other hand removes that key from the session
|
||||
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
||||
of the dict and pass a second parameter to it (the default) the method
|
||||
will delete the key from the dictionary if present or do nothing when that
|
||||
key was not in there. This is helpful because we don't have to check in
|
||||
that case if the user was logged in.
|
||||
|
||||
::
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('logged_in', None)
|
||||
|
|
@ -279,13 +343,32 @@ Step 6: The Templates
|
|||
---------------------
|
||||
|
||||
Now we should start working on the templates. If we request the URLs now
|
||||
we would only get an exception that Flask cannot find the templates.
|
||||
we would only get an exception that Flask cannot find the templates. The
|
||||
templates are using `Jinja2`_ syntax and have autoescaping enabled by
|
||||
default. This means that unless you mark a value in the code with
|
||||
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
||||
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
||||
escaped with their XML equivalents.
|
||||
|
||||
We are also using template inheritance which makes it possible to reuse
|
||||
the layout of the website in all pages.
|
||||
|
||||
Put the following templates into the `templates` folder:
|
||||
|
||||
layout.html
|
||||
```````````
|
||||
|
||||
This template contains the HTML skeleton, the header and a link to log in
|
||||
(or log out if the user was already logged in). It also displays the
|
||||
flashed messages if there are any. The ``{% block body %}`` block can be
|
||||
replaced by a block of the same name (``body``) in a child template.
|
||||
|
||||
The :class:`~flask.session` dict is available in the template as well and
|
||||
you can use that to check if the user is logged in or not. Note that in
|
||||
Jinja you can access missing attributes and items of objects / dicts which
|
||||
makes the following code work, even if there is no ``'logged_in'`` key in
|
||||
the session:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -309,11 +392,17 @@ layout.html
|
|||
show_entries.html
|
||||
`````````````````
|
||||
|
||||
This template extends the `layout.html` template from above to display the
|
||||
messages. Note that the `for` loop iterates over the messages we passed
|
||||
in with the :func:`~flask.render_template` function. We also tell the
|
||||
form to submit to your `add_entry` function and use `POST` as `HTTP`
|
||||
method:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% if g.logged_in %}
|
||||
{% if session.logged_in %}
|
||||
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
|
||||
<dl>
|
||||
<dt>Title:
|
||||
|
|
@ -336,6 +425,9 @@ show_entries.html
|
|||
login.html
|
||||
``````````
|
||||
|
||||
Finally the login template which basically just displays a form to allow
|
||||
the user to login:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
|
|
@ -352,3 +444,41 @@ login.html
|
|||
</dl>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
Step 7: Adding Style
|
||||
--------------------
|
||||
|
||||
Now that everything else works, it's time to add some style to the
|
||||
application. Just create a stylesheet called `style.css` in the `static`
|
||||
folder we created before:
|
||||
|
||||
.. sourcecode:: css
|
||||
|
||||
body { font-family: sans-serif; background: #eee; }
|
||||
a, h1, h2 { color: #377BA8; }
|
||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
|
||||
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
.entries { list-style: none; margin: 0; padding: 0; }
|
||||
.entries li { margin: 0.8em 1.2em; }
|
||||
.entries li h2 { margin-left: -1em; }
|
||||
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
|
||||
.add-entry dl { font-weight: bold; }
|
||||
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
|
||||
margin-bottom: 1em; background: #fafafa; }
|
||||
.flash { background: #CEE5F5; padding: 0.5em;
|
||||
border: 1px solid #AACBE2; }
|
||||
.error { background: #F0D6D6; padding: 0.5em; }
|
||||
|
||||
Bonus: Testing the Application
|
||||
-------------------------------
|
||||
|
||||
Now that you have finished the application and everything works as
|
||||
expected, it's probably not the best idea to add automated tests to
|
||||
simplify modifications in the future. The application above is used as a
|
||||
basic example of how to perform unittesting in the :ref:`testing` section
|
||||
of the documentation. Go there to see how easy it is to test Flask
|
||||
applications.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ class FlaskrTestCase(unittest.TestCase):
|
|||
|
||||
# testing functions
|
||||
|
||||
def test_empty_db(self):
|
||||
"""Start with a blank database."""
|
||||
rv = self.app.get('/')
|
||||
assert 'No entries here so far' in rv.data
|
||||
|
||||
def test_login_logout(self):
|
||||
"""Make sure login and logout works"""
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
|
|
@ -46,9 +51,6 @@ class FlaskrTestCase(unittest.TestCase):
|
|||
|
||||
def test_messages(self):
|
||||
"""Test that messages work"""
|
||||
# start with a blank state
|
||||
rv = self.app.get('/')
|
||||
assert 'No entries here so far' in rv.data
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
rv = self.app.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
body { font-family: sans-serif; background: #eee; }
|
||||
a, h1, h2 { color: #377BA8; }
|
||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
body { font-family: sans-serif; background: #eee; }
|
||||
a, h1, h2 { color: #377BA8; }
|
||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
|
||||
div.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
ul.entries { list-style: none; margin: 0; padding: 0; }
|
||||
ul.entries li { margin: 0.8em 1.2em; }
|
||||
ul.entries li h2 { margin-left: -1em; }
|
||||
form.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
|
||||
form.add-entry dl { font-weight: bold; }
|
||||
div.metanav { text-align: right; font-size: 0.8em; background: #fafafa;
|
||||
padding: 0.3em; margin-bottom: 1em; }
|
||||
div.flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; }
|
||||
p.error { background: #F0D6D6; padding: 0.5em; }
|
||||
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
.entries { list-style: none; margin: 0; padding: 0; }
|
||||
.entries li { margin: 0.8em 1.2em; }
|
||||
.entries li h2 { margin-left: -1em; }
|
||||
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
|
||||
.add-entry dl { font-weight: bold; }
|
||||
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
|
||||
margin-bottom: 1em; background: #fafafa; }
|
||||
.flash { background: #CEE5F5; padding: 0.5em;
|
||||
border: 1px solid #AACBE2; }
|
||||
.error { background: #F0D6D6; padding: 0.5em; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% if g.logged_in %}
|
||||
{% if session.logged_in %}
|
||||
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
|
||||
<dl>
|
||||
<dt>Title:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue