Table of Contents
CSE 30332 - HW4

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 %}
                            
                        

    Example Output


    Here are some screenshots of browsing your reddit API:






    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