CSE 30332 - HW4
Programming Paradigms
In this assignment you will be using HTML and CSS to build flask templates
for your API. Building on top of HW3 you will be using these
tools to wrap your Reddit data and display them nicely on the front-end
Modifying your classes
reddit_classes.py
Your classes will remain largely similar to HW3, with a few small tweaks. As we now need
to pass many variables to the template that we return to the user, your display methods should
now return a list of objects that you want to pass forward to the jinja2 template. For subreddits
and posts this is simple as we already have the classes set up, we just need to slightly modify the
display methods. However this becomes more difficulty when it comes to the comments. To help with this
make a simple Comment class that can hold information for each comment: user, time, attr, body, and children.
This will mean that when you want to display a comment chain we can simply pass a list of the top level
comments to the template and can do the rest using jinja2 features.
Setting up the API
reddit_api.py
Your API will have 4 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.
Finally, 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.
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)
setr = None
subs = None
def check_globals() -> None: # 8 LOC
global setr
global subs
# create the settings object if it doesn't exist
# get the subreddit objects from the database and add the settings 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 Settings object with the new items
# if there is a GET request, display the current settings using a settings template
# return a status code for a successful request
@app.teardown_appcontext
def shutdown_session(exception=None):
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 Settings(Base):
__tablename__ = 'settings'
user_id = Column(Integer, primary_key=True)
# add all of the Columns for the settings the program will have
# 11 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, settings: Settings) -> 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, settings) -> 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
subreddits.html
{% extends 'bootstrap/base.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
{% extends 'bootstrap/base.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
{% extends 'bootstrap/base.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
{% extends 'bootstrap/base.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 %}
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 Monday, April 17th (04/17).
To submit, please create a folder named HW4
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
, and settings.html
.
Assignments are programmatically collected at the due date.
Extension Policy
The course's late work policy is that late work recieves no credit.
However, we all live busy and active lives and should you feel like you
cannot finish the assignment by the due date, please email me and ask
for an extension. Given that the email is polite, I will almost certainly
grant the extension.
Grading Rubric
Component |
Points |
Index, Post, Comment endpoints follow guidelines:
- return a template for each level
|
10
10
|
Subreddit class follows guidelines:
- Display properly returns tuple
- Instantiates Posts with full data from subreddit scrape
|
10
5
5
|
Post class follows guidelines:
- Only scrapes when comments are needed
- Uses Comments class to create a tree of nested objects
- Properly returns tuple
|
10
2
5
3
|
Comments class follows guidelines:
- Correctly instantiates attributes
- Children attribute implemented as list of Comment objects
|
5
2
3
|
Settings endpoint follows guidelines:
- Correctly handles both GET and POST requests
|
5
5
|
Templates correctly follow guidelines:
- Use jinja2 to add data to the template
- Uses a markdown filter to render the bodies correctly
- Properly nests comments
|
10
2
3
5
|
Templates work correctly (Looks nice, buttons work, settings reflected, etc...): |
20 |
Code style |
5 |
Total |
75 |