Skip to content

Commit

Permalink
Working on a big cleanup of the peewee documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
coleifer committed Sep 25, 2011
1 parent 12a221b commit 46267b0
Show file tree
Hide file tree
Showing 4 changed files with 317 additions and 198 deletions.
154 changes: 92 additions & 62 deletions docs/peewee/example.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _example-app:

Example app
===========

Expand All @@ -6,7 +8,9 @@ Example app
peewee ships with an example web app that runs on the
`Flask <http://flask.pocoo.org/>`_ microframework. If you already have flask
and its dependencies installed you should be good to go, otherwise install from
the included requirements file::
the included requirements file.

.. code-block:: console
cd example/
pip install -r requirements.txt
Expand All @@ -16,7 +20,9 @@ Running the example
-------------------

After ensuring that flask, jinja2, werkzeug and sqlite3 are all installed,
switch to the example directory and execute the *run_example.py* script::
switch to the example directory and execute the *run_example.py* script:

.. code-block:: console
python run_example.py
Expand All @@ -32,16 +38,16 @@ definitions. If you're not familiar with django, the idea is that you declare
a class with some members which map directly to the database schema. For the
twitter clone, there are just three models:

User:
``User``:
represents a user account and stores the username and password, an email
address for generating avatars using *gravatar*, and a datetime field
indicating when that account was created

Relationship:
``Relationship``:
this is a "utility model" that contains two foreign-keys to
the User model and represents *"following"*.
the ``User`` model and represents *"following"*.

Message:
``Message``:
analagous to a tweet. this model stores the text content of
the message, when it was created, and who posted it (foreign key to User).

Expand All @@ -50,19 +56,22 @@ If you like UML, this is basically what it looks like:
.. image:: schema.jpg


Here is the code::
Here is what the code looks like:

database = peewee.Database(peewee.SqliteAdapter(), DATABASE)
.. code-block:: python
# model definitions
class User(peewee.Model):
username = peewee.CharField()
password = peewee.CharField()
email = peewee.CharField()
join_date = peewee.DateTimeField()
database = SqliteDatabase(DATABASE)
# model definitions
class BaseModel(Model):
class Meta:
database = database
class User(BaseModel):
username = CharField()
password = CharField()
email = CharField()
join_date = DateTimeField()
def following(self):
return User.select().join(
Expand All @@ -85,77 +94,82 @@ Here is the code::
(md5(self.email.strip().lower().encode('utf-8')).hexdigest(), size)
class Relationship(peewee.Model):
from_user = peewee.ForeignKeyField(User, related_name='relationships')
to_user = peewee.ForeignKeyField(User, related_name='related_to')

class Meta:
database = database

class Relationship(BaseModel):
from_user = ForeignKeyField(User, related_name='relationships')
to_user = ForeignKeyField(User, related_name='related_to')
class Message(peewee.Model):
user = peewee.ForeignKeyField(User)
content = peewee.TextField()
pub_date = peewee.DateTimeField()
class Meta:
database = database
class Message(BaseModel):
user = ForeignKeyField(User)
content = TextField()
pub_date = DateTimeField()
peewee supports a handful of field types which map to different column types in
sqlite. Conversion between python and the database is handled transparently,
including the proper handling of None/NULL.
including the proper handling of ``None``/``NULL``.

.. note:: you might have noticed that each model sets the database attribute
explicitly. by default peewee will use "peewee.db". explicitly setting this
instructs peewee to use the database specified by ``DATABASE`` (tweepee.db).
.. note::
You might have noticed that we created a ``BaseModel`` which sets the
database, and then all the other models extend the ``BaseModel``. This is
a good way to make sure all your models are talking to the right database.


Creating the initial tables
^^^^^^^^^^^^^^^^^^^^^^^^^^^

In order to start using the models, its necessary to create the tables. This is
a one-time operation and can be done quickly using the interactive interpreter::
a one-time operation and can be done quickly using the interactive interpreter.

Open a python shell in the directory alongside the example app and execute the
following:

.. code-block:: python
>>> from app import *
>>> create_tables()
The ``create_tables()`` method is defined in the app module and looks like this::
The ``create_tables()`` method is defined in the app module and looks like this:

.. code-block:: python
def create_tables():
database.connect() # <-- note the explicit call to connect()
User.create_table()
Relationship.create_table()
Message.create_table()
Every model has a ``create_table()`` classmethod which runs a ``CREATE TABLE``
Every model has a :py:meth:`~Model.create_table` classmethod which runs a ``CREATE TABLE``
statement in the database. Usually this is something you'll only do once,
whenever a new model is added.

.. note:: adding fields after the table has been created will required you to
either drop the table and re-create it or manually add the columns using
``ALTER TABLE``.
.. note::
adding fields after the table has been created will required you to
either drop the table and re-create it or manually add the columns using ``ALTER TABLE``.


Connecting to the database
^^^^^^^^^^^^^^^^^^^^^^^^^^

You may have noticed in the above model code that there is a class defined
within each model named ``Meta`` that sets the ``database`` attribute. peewee
allows every model to specify which database it uses, defaulting to "peewee.db",
but since you probably want a bit more control, you can instantiate your own
database and point your models at it::
You may have noticed in the above model code that there is a class defined on the
base model named ``Meta`` that sets the ``database`` attribute. peewee
allows every model to specify which database it uses, defaulting to "peewee.db".
Since you probably want a bit more control, you can instantiate your own
database and point your models at it:

.. code-block:: python
# config
DATABASE = 'tweepee.db'
# ... more config here, omitted
database = peewee.Database(peewee.SqliteAdapter(), DATABASE) # tell our models to use "tweepee.db"
database = SqliteDatabase(DATABASE) # tell our models to use "tweepee.db"
Because sqlite likes to have a separate connection per-thread, we will tell
flask that during the request/response cycle we need to create a connection to
the database. Flask provides some handy decorators to make this a snap::
the database. Flask provides some handy decorators to make this a snap:

.. code-block:: python
@app.before_request
def before_request():
Expand All @@ -167,10 +181,11 @@ the database. Flask provides some handy decorators to make this a snap::
g.db.close()
return response
Note that we're storing the db on the magical variable ``g`` - that's a
flask-ism and can be ignored as an implementation detail. The meat of this code
is in the idea that we connect to our db every request and close that connection
every response. Django does the `exact same thing <http://code.djangoproject.com/browser/django/tags/releases/1.2.3/django/db/__init__.py#L80>`_.
.. note::
We're storing the db on the magical variable ``g`` - that's a
flask-ism and can be ignored as an implementation detail. The meat of this code
is in the idea that we connect to our db every request and close that connection
every response. Django does the `exact same thing <http://code.djangoproject.com/browser/django/tags/releases/1.2.3/django/db/__init__.py#L80>`_.


Doing queries
Expand All @@ -183,25 +198,30 @@ user-specific functionality, i.e.
* ``followers()``: who is following this user?

These methods are rather similar in their implementation but with one key
difference::
difference:

.. code-block:: python
def following(self):
return User.select().join(
Relationship, on='to_user_id'
Relationship, on='to_user_id' # <-- explicitly declaring join column
).where(from_user=self).order_by('username')
def followers(self):
return User.select().join(
Relationship
).where(to_user=self).order_by('username')
.. note: the ``following()`` method specifies an extra bit of metadata,
``on='to_user_id'``. because there are two foreign keys to ``User``, peewee
.. note:
The ``following()`` method specifies an extra bit of metadata,
``on='to_user_id'``. Because there are two foreign keys to ``User``, peewee
will automatically assume the first one, which happens to be ``from_user``.
Specifying the foreign key manually instructs peewee to join on the ``to_user_id`` field.
the queries end up looking like::
The queries end up looking like:

.. code-block:: sql
# following:
SELECT t1.*
Expand All @@ -225,7 +245,9 @@ Creating new objects

So what happens when a new user wants to join the site? Looking at the
business end of the ``join()`` view, we can that it does a quick check to see
if the username is taken, and if not executes a ``.create()``::
if the username is taken, and if not executes a :py:meth:`~Model.create`.

.. code-block:: python
try:
user = User.get(username=request.form['username'])
Expand All @@ -238,8 +260,10 @@ if the username is taken, and if not executes a ``.create()``::
join_date=datetime.datetime.now()
)
Much like the ``create()`` method, all models come with a built-in method called
``get_or_create`` which is used when one user follows another::
Much like the :py:meth:`~Model.create` method, all models come with a built-in method called
:py:meth:`~Model.get_or_create` which is used when one user follows another:

.. code-block:: python
Relationship.get_or_create(
from_user=session['user'], # <-- the logged-in user
Expand All @@ -252,10 +276,17 @@ Doing subqueries

If you are logged-in and visit the twitter homepage, you will see tweets from
the users that you follow. In order to implement this, it is necessary to do
a subquery::
a subquery:

.. code-block:: python
# python code
qr = Message.select().where(user__in=some_user.following())
Results in the following SQL query:

.. code-block:: sql
>>> qr = Message.select().where(user__in=some_user.following())
>>> print qr.sql()[0] # formatting cleaned up for readability
SELECT *
FROM message
WHERE user_id IN (
Expand All @@ -267,5 +298,4 @@ a subquery::
ORDER BY username ASC
)
peewee supports doing subqueries on any ``ForeignKeyField`` or
``PrimaryKeyField``.
peewee supports doing subqueries on any :py:class:`ForeignKeyField` or :py:class:`PrimaryKeyField`.
21 changes: 17 additions & 4 deletions docs/peewee/installation.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
.. _installation:

Installing peewee
=================

First you need to grab a checkout of the code. There are a couple ways::
.. code-block:: console
pip install peewee
Installing with git
-------------------

To get the latest development version::

git clone http://github.com/coleifer/peewee.git
.. code-block:: console
git clone https://github.com/coleifer/peewee.git
cd peewee
python setup.py install
If you grabbed the checkout, to install system-wide, use::
python setup.py install
You can test your installation by running the test suite.

.. code-block:: console
python setup.py test
Feel free to check out the :ref:`example-app` which ships with the project.
Loading

0 comments on commit 46267b0

Please sign in to comment.