CSE 30332 - HW5
Programming Paradigms
In this assignment you will be using HTML and CSS to build flask templates
for your API. Building on top of HW4 you will be using these
tools to add some additional functionality to your site. The Settings object
will be modified to a User object, and you will be implementing simple
user accounts for your website. Additional functionality will include a navbar
that will allow you to search the current page.
Modifying your classes
reddit_classes.py
Your classes will remain largely similar to HW4, with a few small tweaks. Your Settings
object will now be a User object. To facilitate this, add new columns to your User object
for username and password. Other than this, your class should not
really need to change.
Setting up the API
reddit_api.py
Your API will have 5 endpoints:
The landing page will display the subreddit links.
The second endpoint will allow the user to add a single number to the URL to see
the post titles for the subreddit associated with the number they added to the URL.
The third endpoint will allow the user to add another number to the URL in addition
to the first. This second number will be associated with a post title from the subreddit
associated with the first number. This endpoint will display the comments for the post.
The fourth endpoint will be one accepting both GET
and POST
requests that will
allow the requester to change attributes of the Settings
object during runtime.
using the HTML forms we saw during lecture. If the user is not logged in yet, redirect them
to the login page.
In addition to the settings endpoint you will now need a login endpoint. This endpoint will be
similiar to settings but will just have a username and password box, along with a submit button
to allow the user to login. You can again use simple forms as you did for the above endpoint.
While there are significant security implications to consider when you start adding user accounts
to your site, this is not the class to worry about that in.
Note that now instead of expecting your user to modify the URL directly, they will have links that
they can click to take them forward in the "levels" of our webpage. For now, we can simply rely on
the user's web browser's back button to take them back to the previous page. We can keep the endpoint
URL scheme the same, but now we will need to pass a URL for each button to the template that we
can insert.
Running a Flask server
To run a flask server you first need to set the environmental variable FLASK_APP
for your shell. After doing so you can run your server using the command:
python3 -m flask run -p [PORT]
where [PORT] is a number between 9000 and 9999. You could also add a main
function
to your API file to run your code as we've seen in class 4-5 times now.
Code Overview and Scaffold
To help you get some sense of the approximate length of each part of the code, I have
included the number of lines used in each section in my solution. I will of course
probably implement my code slightly differently than you may. Therefore you should take
these as a relative reference only, it is completely reasonable that your code may be
several lines shorter or longer. You should only start to worry if yours is significantly
different than what I have.
reddit_api.py
from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy
from db_manager import db_session
from reddit_classes import Settings, Subreddit, Post
from flask_bootstrap import Bootstrap
from flaskext.markdown import Markdown
app = Flask(__name__)
Bootstrap(app)
Markdown(app)
user = None
subs = None
def check_globals() -> None: # 8 LOC
global user
global subs
# create a default User object if it doesn't exist
# get the subreddit objects from the database and add the User object
# if the subreddit objects don't already exist
@app.route('/')
def display_subreddits() -> str: # 6 LOC
check_globals()
# return a template displaying all of the subreddits and links to their posts
@app.route('/<int:sub_id>/')
def display_post_titles(sub_id: int) -> str: # 2 LOC
check_globals()
# return a template displaying all of the posts and links to their comments
@app.route('/<int:sub_id>/<int:post_id>/')
def display_post_comments(sub_id: int, post_id: int) -> str: # 6 LOC
check_globals()
# return a template displaying all of the comments
@app.route('/settings/', methods=['GET', 'POST'])
def settings() -> None: # 12 LOC
check_globals()
# if there is a POST request update the User object with the new items
# if there is a GET request, and the user is not logged in, redirect them
# to the login page, otherwise display the settings page for the user
# return a status code for a successful request
@app.route('/login/', methods=['GET', 'POST'])
def login() -> None: #10 LoC
# if there is a POST request, log the user in and load their User object from the DB
# and redirect to the index page
# if there is a GET request, return the login template
@app.teardown_appcontext
def shutdown_session(exception=None): #4 LoC
# save the users settings to the database
db_session.remove()
if __name__ == '__main__':
# run your app
reddit_classes.py
import re
import os
import requests
from db_manager import Base
from sqlalchemy import Column, Integer, String, Boolean
class User(Base):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True)
# add all of the Columns for the settings the program will have
# 14 LOC
def __init__(self, sub_regex='.*', title_regex='.*', comment_regex='.*',
sub_num=25, title_num=25, comment_num=25,
sub_reverse=False, title_reverse=False, comment_reverse=False,
title_attr='score', comment_attr='score'):
# set all of the attributes for the Settings object
# 11 LOC
def __repr__(self) -> str:
return super().__repr__()
class Subreddit(Base):
__tablename__ = 'subreddits'
id = Column(Integer, primary_key=True)
# add a column for the subreddit URL
# 1 LOC
def __init__(self, url: str, user: User) -> None: # 2 LOC
# set the two Subreddit attributes
def scrape(self) -> None: # 4 LOC
# scrape the Subreddit URL and instantiate a list of Post objects
def display(self, loc: int, titles: bool = False) -> tuple: # 8 LOC
# return a tuple with the subreddits URL and a list of Posts you want to
# display (it's possible this may be an empty list)
# if titles is True, then scrape the subreddit
def filter(self) -> bool: # 3 LOC
# Check if the URL of the subreddit matches the regex for subreddits
def __repr__(self) -> str:
return super().__repr__()
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
# add a column for the post URL
# 1 LOC
def __init__(self, data, user) -> None: # 6 LOC
# set the Posts attributes from the passed in data
# such as title, selftext, and name
def scrape(self) -> None: # 3 LOC
# scrape the Post's URL
def display(self, loc: int, comments: bool = False) -> tuple: # 10 LOC
# return a tuple containing the posts title and a list of all Comments to display
# it's possible the comment list may be empty
# if comments is true, then scrape the post
def display_comment_tree(self, reply_dict: dict, depth: int) -> str: 12 LOC
# return a single comment object, whose children attribute contains the comments children
def filter(self, item, comments: bool = False) -> bool: # 6 LOC
# return true or false depending on whether or not the item matches its regex
def __repr__(self) -> str:
return super().__repr__()
class Comment():
def __init__(self, user, time, body, attr):
# initialize the attributes of the comment class
# additionally add a list of child comments as well
navbar.html
{% extends 'bootstrap/base.html' %}
{% block content %}
<nav class="navbar navbar-default">
<!-- Add a navbar to your site, include the same elements as a seen in the screenshots -->
<!-- 27 LoC -->
</nav>
{% endblock %}
subreddits.html
{% include 'navbar.html' %}
{% block content %}
<div class="container">
{% for sub in subs %}
<!-- Add each subreddit to a panel container and add a link to view the subreddits posts -->
<!-- 6 LoC -->
{% endfor %}
</div>
{% endblock %}
posts.html
{% include 'navbar.html' %}
{% block content %}
<div class="container">
{% for post in posts %}
<!-- Add each post to a panel container and add a link to view the posts comments -->
<!-- Use a flask filter to correctly render the markdown -->
<!-- 9 LoC -->
{% endfor %}
</div>
{% endblock %}
comments.html
{% include 'navbar.html' %}
{% block content %}
<style>
/*
Add styling to your comments using CSS
101 LoC
*/
</style>
<h4 class="text-center mb-4 pb-2">{{ title }}</h4>
<div class="container">
{% for comment in comments recursive %}
<div class="comment">
<!-- Add each comment to a container recursively including the user, attr, post-time, and body -->
<!-- 40 LoC -->
</div>
{%- endfor %}
</div>
{% endblock %}
settings.html
{% include 'navbar.html' %}
{% block content %}
<style>
/* add some style to your settings page using CSS */
/* 30 LoC */
</style>
<div class="container">
<!-- Add a form entry area for each attribute in your settings object -->
<!-- When the page is accessed show the current settings in the entry areas -->
<!-- Have a single submission button to send the new settings to the backend -->
<!-- You can assume that the entries for the settings will be nicely entered for this assignment -->
<!-- 55 LoC -->
</div>
{% endblock %}
login.html
{% include 'navbar.html' %}
{% block content %}
<div class="container">
<!-- Add a form entry area for your username and password -->
<!-- When the page is accessed and you aren't logged in-->
<!-- Have a single submission button to send the user info to the login endpoint-->
<!-- You can assume that the entries for the login will be nicely entered for this assignment -->
<!-- 37 LoC -->
</div>
{% endblock %}
We'll do it live
Note, since we are pulling data from an active website, the articles may
change between runs.
Reddit API Oddities
There have been several oddities in the reddit API that have been brought to my attention.
Firstly, image posts are causing lots of problems for students and secondly, every once in
a while you'll get a replies
dictionary that is not empty but also not formatted
in the standard manner. To handle these I recommend you make use of try/except
blocks.
The cats subreddit especially has proven problematic and as such we will be lenient when grading in
relation to this specific subreddit.
Submission Instructions
This assignment is due by 11:59 PM on Wednesday, May 03rd (05/03).
To submit, please create a folder named HW5
in your dropbox. Then
put your python files, named reddit_api.py
, reddit_classes.py
,
db_manager.py
, and reddit_setter.py
into this folder.
Create a sub-directory named templates
as seen in class in which to
place your four html files: subreddits.html
, posts.html
,
comments.html
, settings.html
, login.html
,
and navbar.html
Assignments are programmatically collected at the due date.
Extension Policy
I've made this assignment due as late as I am possibly allowed to
by the university. If this is going to cause an issue please talk
to me before the deadline and we can try and come up with a strategy
for you to finish your assignment.
Grading Rubric
Component |
Points |
Login endpoint follows guidelines:
- Allows the user to enter a username and password
- checks the database for entries matching these fields
- if there is a match, load the settings, otherwise create an account
|
10
2
3
5
|
Settings endpoint follows guidelines:
- If the user is not logged in, redirects them to the login page first
|
5
5
|
User class follows guidelines:
- Modified the Settings class to become User class
- Correctly add username and password fields to the database
|
10
3
7
|
Navbar follows guidelines:
- Homepage link set up correctly
- Filter works correctly relative to page
- settings and login links work
|
10
2
5
3
|
Templates correctly follow guidelines:
- Use jinja2 to add data to the template
- All pages show navbar at top
|
10
5
5
|
Templates work correctly (Looks nice, buttons work, settings reflected, etc...): |
25 |
Code style |
5 |
Total |
75 |