Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Build a Social Network with Flask How to Win Friends Lunch Count

You don't need to call user.followers they are already querysets.

I've read through some of the other answers here that say to remove the parenthesis after followers/following but that doesn't seem to work for me. Not really sure what this wants, it's very unclear.

lunch.py
import datetime

from flask import Flask, g, render_template, flash, redirect, url_for
from flask.ext.bcrypt import check_password_hash
from flask.ext.login import LoginManager, login_user, current_user, login_required, logout_user

import forms
import models

app = Flask(__name__)
app.secret_key = 'this is our super secret key. do not share it with anyone!'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'


@login_manager.user_loader
def load_user(userid):
    try:
        return models.User.select().where(
            models.User.id == int(userid)
        ).get()
    except models.DoesNotExist:
        return None


@app.before_request
def before_request():
    g.db = models.DATABASE
    g.db.connect()
    g.user = current_user


@app.after_request
def after_request(response):
    g.db.close()
    return response


@app.route('/register', methods=('GET', 'POST'))
def register():
    form = forms.SignUpInForm()
    if form.validate_on_submit():
        models.User.new(
            email=form.email.data,
            password=form.password.data
        )
        flash("Thanks for registering!") 
    return render_template('register.html', form=form)


@app.route('/login', methods=('GET', 'POST'))
def login():
    form = forms.SignUpInForm()
    if form.validate_on_submit():
        try:
            user = models.User.get(
                models.User.email == form.email.data
            )
            if check_password_hash(user.password, form.password.data):
                login_user(user)
                flash("You're now logged in!")
            else:
                flash("No user with that email/password combo")
        except models.DoesNotExist:
              flash("No user with that email/password combo")
    return render_template('register.html', form=form)

@app.route('/secret')
@login_required
def secret():
    return "I should only be visible to logged-in users"

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('login'))


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/order', methods=('GET', 'POST'))
def order_lunch():
    form = forms.LunchOrderForm()
    if form.validate_on_submit():
        models.LunchOrder.create(
            user=g.user._get_current_object(),
            date=form.date.data,
            order=form.order.data.strip()
        )
    return render_template('lunch.html', form=form)


@app.route('/today')
@login_required
def today():
    order = models.LunchOrder.select().where(
        models.LunchOrder.date == datetime.date.today() &
        models.LunchOrder.user == g.user._get_current_object()
    ).get()
    return render_template('today.html', order=order)


@app.route('/cancel_order/<int:order_id>')
@login_required
def cancel_order(order_id):
    try:
        order = models.LunchOrder.select().where(
            id=order_id,
            user=g.user._get_current_object()
        ).get()
    except models.DoesNotExist:
        pass
    else:
        order.delete_instance()
    return redirect(url_for('index'))


@app.route('/follow/<int:user_id>')
@login_required
def follow(user_id):
    try:
        user = models.User.get(
            models.User.id == user_id
        )
        models.Relationship.create(
            from_user=g.user._get_current_object(),
            to_user=user
        )
    except (models.DoesNotExist, models.IntegrityError):
        pass
    return redirect(url_for('index'))


@app.route('/unfollow/<int:user_id>')
@login_required
def unfollow(user_id):
    try:
        user = models.User.get(
            models.User.id == user_id
        )
        models.Relationship.get(
            models.Relationship.from_user==g.user._get_current_object(),
            models.Relationship.to_user==user
        ).delete_instance()
    except (models.DoesNotExist, models.IntegrityError):
        pass
    return redirect(url_for('index'))


@app.route('/profile/<int:user_id>')
def profile(user_id):
    user = models.User.select().where(
        models.User.id == user_id
    ).get()
    return render_template('profile.html', user=user)
models.py
import datetime

from flask.ext.bcrypt import generate_password_hash
from flask.ext.login import UserMixin
from peewee import *

DATABASE = SqliteDatabase(':memory:')


class User(UserMixin, Model):
    email = CharField(unique=True)
    password = CharField(max_length=100)
    join_date = DateTimeField(default=datetime.datetime.now)
    bio = CharField(default='')

    class Meta:
        database = DATABASE

    @property
    def following(self):
        return (
            User
            .select()
            .join(Relationship, on=Relationship.to_user)
            .where(Relationship.from_user == self)
        )

    @property
    def followers(self):
        return (
            User
            .select()
            .join(Relationship, on=Relationship.from_user)
            .where(Relationship.to_user == self)
        )


    @classmethod
    def new(cls, email, password):
        cls.create(
            email=email,
            password=generate_password_hash(password)
        )


class LunchOrder(Model):
    order = TextField()
    date = DateField()
    user = ForeignKeyField(User, related_name="orders")


class Relationship(Model):
    from_user = ForeignKeyField(User, related_name="relationships")
    to_user = ForeignKeyField(User, related_name="related_to")
    created_at = DateTimeField(default=datetime.datetime.now)

    class Meta:
        database = DATABASE
        indexes = (
            (('from_user', 'to_user'), True),
        )


def initialize():
    DATABASE.connect()
    DATABASE.create_tables([User, LunchOrder], safe=True)
    DATABASE.close()
templates/profile.html
{% extends "layout.html" %}
{% from "macro.html" import hide_email %}

{% block content %}

<h1>{{ hide_email(user) }}</h1>

<dl>
    <dt>Lunches:</dt>
    <dd>{{  user.orders().count()  }}</dd>
    <dt>Followers:</dt>
    <dd>{{  user.followers.count()  }}</dd>
    <dt>Following:</dt>
    <dd>{{  user.following.count()  }}</dd>
</dl>

{% endblock %}

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,423 Points

The error message is misleading. The error is actually with the order line:

<dl>
    <dt>Lunches:</dt>
    <dd>{{  user.orders.count()  }}</dd> {# remove () after 'orders' #}
    <dt>Followers:</dt>
    <dd>{{  user.followers.count()  }}</dd>
    <dt>Following:</dt>
    <dd>{{  user.following.count()  }}</dd>
</dl>

Kenneth Love: Can the challenge error warning be refined in this case?

Got it, Thanks again Chris.

Kenneth Love
Kenneth Love
Treehouse Guest Teacher

Yeah, that is a bad error message. I'll see what I can do about it. It's currently confusing the heck out of me so wish me luck.

EDIT:

OK, updated as best I can due to how the processing of the templates happens.

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,735 Points

Would you mind clarifying a few things:

1) If I'm following correctly, we don't use the parentheses because we've decorated the methods with @property. Is the purpose of this decorator just for the convenience of not typing "()"?

2) Where does user.orders come from? I don't see that property (or method decorated as property) defined alongside followers and following.

3) What is a queryset?

Chris Freeman
Chris Freeman
Treehouse Moderator 68,423 Points

1) If I'm following correctly, we don't use the parentheses because we've decorated the methods with @property. Is the purpose of this decorator just for the convenience of not typing "()"?

Using the @property decorator establishes two things: It makes the name of the function a class attribute (accessible via user_instance.follows, and it establishes this function as the "getter" method for accessing this attribute. So accessing the attribute .follows actually executes the function follows(). See this example on property usage.

2) Where does user.orders come from? I don't see that property (or method decorated as property) defined alongside followers and following.

user.orders is created from the ForiegnKeydefinition in the class LunchOrder:

class LunchOrder(Model):
    order = TextField()
    date = DateField()
    user = ForeignKeyField(User, related_name="orders")

The related_name argument sets the reverse relationship to LunchOrder from User as user.orders

3) What is a queryset?

Flask uses the term Query, but many programmers used the term Queryset borrowed from the Django framework.

From StackOverflow:

A django queryset is like its name says, basically a collection of (sql) queries, Print some_queryset.query will show you the sql query generated from your django filter calls.

Since querysets are lazy, the database query isn't done immediately, but only when needed - when the queryset is evaluated. This happens for example if you call its str method when you print it, if you would call list() on it, or, what happens mostly, you iterate over it (for post in b..). This lazyness should save you from doing unnecessary queries and also allows you to chain querysets and filters for example (you can filter a queryset as often as you want to).

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,735 Points

How does it know by looking at the user object that it has a property called orders without looking at LunchOrder? Or does that line of code in LunchOrder go and modify the User object to give it that property?

Kenneth Love
Kenneth Love
Treehouse Guest Teacher

Yeah, basically. Assuming Peewee works like Django (I haven't read all of the source code yet), all of the models are parsed at startup and then creates attributes on models with relations using the relation name.

Chris Freeman
Chris Freeman
Treehouse Moderator 68,423 Points

Brendan,

How does it know by looking at the user object that it has a property called orders without looking at LunchOrder? Or does that line of code in LunchOrder go and modify the User object to give it that property?

Great questions!. If you were to inspect the database, you would not see a column for "order", "relationships", or "related_to" in the User table. As an experiment, I moved LunchOrder and Relationships into their own files (lunchorder.py and relationship.py, respectively). In the interactive shell I can then inspect the User class as the other classes are imported:

# Start Python interactively
$ python3
Python 3.4.0 (default, Jun 19 2015, 14:20:21) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import models
>>> dir(models.User)
['DoesNotExist', '__class__', '__delattr__', '__dict__', '__dir__',
 '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
 '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', '_create_indexes', '_data', '_fields_to_index',
 '_get_pk_value', '_meta', '_pk_expr', '_populate_unsaved_relations',
 '_prepare_instance', '_prune_fields', '_set_pk_value', 'alias',
 'as_entity', 'bio', 'create', 'create_or_get', 'create_table',
 'delete', 'delete_instance', 'dependencies', 'dirty_fields',
 'drop_table', 'email', 'filter', 'followers', 'following', 'get',
 'get_id', 'get_or_create', 'id', 'insert', 'insert_from',
 'insert_many', 'is_active', 'is_anonymous', 'is_authenticated',
 'is_dirty', 'join_date', 'new', 'password', 'prepared', 'raw',
 'save', 'select', 'set_id', 'sqlall', 'table_exists', 'update']

### notice no 'orders' ###

# import lunchorder 
>>> import lunchorder
# check User attributes: orders now present
>>> dir(models.User)
['DoesNotExist', '__class__', '__delattr__', '__dict__', '__dir__',
 '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
 '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', '_create_indexes', '_data', '_fields_to_index',
 '_get_pk_value', '_meta', '_pk_expr', '_populate_unsaved_relations',
 '_prepare_instance', '_prune_fields', '_set_pk_value', 'alias',
 'as_entity', 'bio', 'create', 'create_or_get', 'create_table',
 'delete', 'delete_instance', 'dependencies', 'dirty_fields',
 'drop_table', 'email', 'filter', 'followers', 'following', 'get',
 'get_id', 'get_or_create', 'id', 'insert', 'insert_from',
 'insert_many', 'is_active', 'is_anonymous', 'is_authenticated',
 'is_dirty', 'join_date', 'new', 'orders', 'password', 'prepared', #<-- Look!
 'raw', 'save', 'select', 'set_id', 'sqlall', 'table_exists', 'update']

# What is 'orders'
>>> models.User.orders
<peewee.ReverseRelationDescriptor object at 0x7fa017739b70>
>>> type(models.User.orders)
<class 'peewee.ReverseRelationDescriptor'>

# Check relationships
>>> models.User.relationships
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'User' has no attribute 'relationships'

# not yet imported...
>>> import relationship
>>> models.User.relationships
<peewee.ReverseRelationDescriptor object at 0x7fa017719208>

# check User attributes again: 
>>> dir(models.User)
['DoesNotExist', '__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
 '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', '_create_indexes', '_data', '_fields_to_index',
 '_get_pk_value', '_meta', '_pk_expr', '_populate_unsaved_relations',
 '_prepare_instance', '_prune_fields', '_set_pk_value', 'alias',
 'as_entity', 'bio', 'create', 'create_or_get', 'create_table',
 'delete', 'delete_instance', 'dependencies', 'dirty_fields',
 'drop_table', 'email', 'filter', 'followers', 'following', 'get',
 'get_id', 'get_or_create', 'id', 'insert', 'insert_from',
 'insert_many', 'is_active', 'is_anonymous', 'is_authenticated',
 'is_dirty', 'join_date', 'new', 'orders', 'password', 'prepared',
 'raw', 'related_to', 'relationships', 'save', 'select', 'set_id', #<-- Look!
 'sqlall', 'table_exists', 'update']
>>> 

# create user database table:
>>> models.DATABASE.connect()
>>> models.DATABASE.create_tables([models.User])
# create new user
>>> models.User.new(email="user@example.com", password="weakpw")

# select user from database
>>> user1 = models.User.select().where(models.User.email == "user@example.com").get()

# At instantiation the ReverseRelationDescriptor is turn into a SelectQuery
>>> type(user1.orders)
<class 'peewee.SelectQuery'>

# inspect instance attribute orders
>>> user1.orders
<class 'lunchorder.LunchOrder'> SELECT "t1"."id", "t1"."order", "t1"."date", "t1"."user_id" FROM "lunchorder" AS t1 WHERE ("t1"."user_id" = ?) [1]

# check current orders... Error no lunchorder database table yet
>>> user1.orders.count()
Traceback (most recent call last):
  File "/home/chrisf/.virtualenvs/flasksocial/lib/python3.4/site-packages/peewee.py", line 3331, in execute_sql
    cursor.execute(sql, params or ())
sqlite3.OperationalError: no such table: lunchorder

# create lunchorder table
>>> models.DATABASE.create_tables([lunchorder.LunchOrder])

# Now, check user.orders.count()
>>> user1.orders.count()
0