Skip to the content.

I eat kids

A review on the multiple choice exam on My AP Classroom.

Go Back to Main Page

Project Feature Blog

For our N@tM project I mainly worked on the backend of the website. While I did not have many commits, each commit took a lot of time and reworking in order to bring to fruition. I’ll quickly go over each part and summarize it’s contents.

Models

I created numerous models for our team. Here is a list of them and their purposes, along with some code snippets

binaryLearningGame.py

This model interact’s with Weston’s game. It stores the scores of users who play his game. This model while originally developed did undergo changes by Weston, but I think it is important to acknowledge the foundation I put in. I created the basic data storage of id, username, user_id, user_score and user_difficulty. I also added all the initial CRUD features, with update being changed by Weston.

class BinaryLearningGameScores(db.Model):
    __tablename__ = 'binaryLearningGameScores'

    id = db.Column(db.Integer, primary_key=True)
    _username = db.Column(db.String(255), nullable=False)
    _user_id = db.Column(db.String(255), db.ForeignKey('users.id'), nullable=False)
    _user_score = db.Column(db.Integer, nullable=False)
    _user_difficulty = db.Column(db.String(255), nullable=False)

binaryOverflowContent.py

This model stores the posts of users on our Binary Overflow page, similar to Stack overflow. Unfortunately we weren’t able to fully use this model as it was originally intended for users to be able to click on posts and open a page with only the post on it. We had issues with the fronend making this impossible within the timeframe.

Key parts

Some cool things I did with this module is db.relationships, which define relationships with another model with a built-in function too flask. I learned about these when doing research on easier ways to make two closely related models interact.

class BinaryOverflowContent(db.Model):
    __tablename__ = 'binaryPostContent'
        
    # Defined columns
    id = db.Column(db.Integer, primary_key=True)
    _title = db.Column(db.String(255), nullable=False)
    _author = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    _date_posted = db.Column(db.DateTime, nullable=False, default=datetime.now(timezone.utc))
    _content = db.Column(db.String(255), nullable=False)
    
    # Defined relationships
    comments = db.relationship('BinaryOverflowComments', backref='comments', cascade='all, delete-orphan')
    votes = db.relationship('BinaryOverflowContentVotes', backref='votes', cascade='all, delete-orphan')

You can see the defined relationships are cascading. This means when I make a change to this model it’ll affect those related models. In this case we are deleting them when this post gets deleted.

Something else I did was counting the votes, this function while simple did take a while to make due to my own stubborness. I wanted the votes to be stored in the same model, but others on Stack Overflow stated that it was simply better to store in a second related model. Here is how I counted the votes in the other model

    def read(self):
        user = User.query.get(self._author)
        votes = [vote.read() for vote in self.votes]
        upvotes = 0
        downvotes = 0
        # There's probably a more efficient way to do this but oh well
        if votes:
            for vote in votes:
                if vote['vote'] < 0:
                    downvotes += 1
                elif vote['vote'] > 0:
                    upvotes += 1
        return {
            'id': self.id,
            'title': self._title,
            'author': user.name if user else self._author, 
            'date_posted': self._date_posted.isoformat(),
            'content': self._content,
            'upvotes': upvotes,
            'downvotes': downvotes
        }

binaryOverflowContentVotes.py

This is the last model I wanted to share, this model manages all the up and downvotes for each post. It stores very little information and is only there as it is better than storing a list in the original model. It sets a relationship using the post_id and stores the user who made the vote.

Data initialization

class BinaryOverflowContentVotes(db.Model):
    __tablename__ = 'binaryPostContentVotes'
        
    # Defined columns
    id = db.Column(db.Integer, primary_key=True)
    _post_id = db.Column(db.String(255), db.ForeignKey('binaryPostContent.id'), nullable=False)
    _user = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    _vote = db.Column(db.Integer, nullable=False)

API’s

I only made two API’s in the end, I believe that the binaryOverflow API is much more advanced so I’ll only be going over this one.

binaryOverflow.py

The Binary Overflow API has four parts. I’ll quickly go through each one.

fetch_frontend

This is a API call for the front page of Binary Overflow, it has a get function that returns an object with every post along with it’s votes. It also has a POST option so that frontend developers won’t have to use two different API urls.

    class fetch_frontend(Resource):
        def get(self):
            posts = BinaryOverflowContent.query.all()
            json_ready = [post.read() for post in posts]
            return json_ready
        
        @token_required()
        def post(self):
            current_user = g.current_user
            data = request.get_json()
            post = BinaryOverflowContent(data["title"], current_user.id, data["content"])
            post.create()
            return jsonify(post.read())

POST_CRUD

This part of the API was meant for the individual pages that you could open by interacting with the posts. This was unfortunately never integrated into the frontend due to time constraints. It had a GET call for the post itself so it can be built on the dynamic page and used URL parameters so that users could bookmark the page. It also had a POST feature for creating new posts from the page. The interesting parts are the PUT and DELETE which would check if the user matched the creator before deleting the post. This could be improved by allowing admins to delete the posts as well though.

        @token_required()
        def put(self):
            current_user = g.current_user
            data = request.get_json()
            # Change from id, not a reliable one
            post = BinaryOverflowContent.query.get(data["id"])
            # WIP Feature, unknown if it works
            if post["_author"] == current_user.id:
                pass
            else:
                return "You cannot change another user's posts"
            
        @token_required()
        def delete(self):
            current_user = g.current_user
            data = request.get_json()
            # Change to reference post_id
            post = BinaryOverflowContent.query.get(data["id"])
            author = post.read()["author"]
            if author == current_user.id:
                post.delete()
                return "post sucessfully deleted"
            else:
                return "You cannot delete another user's posts"

CONTENT_VOTE

This is the part of the API made for voting on content pages. It had simple logic, it checked if the user had previously voted on the post before and if so updated their vote. If they hadn’t voted before it would instead create a new data point.

    class CONTENT_VOTE(Resource):
        @token_required()
        def post(self):
            data = request.get_json()
            current_user = g.current_user
            vote = BinaryOverflowContentVotes.query.filter_by(_user=current_user.id, id=data['post_id']).first()
            if vote:
                vote.update(data)
            else:
                vote = BinaryOverflowContentVotes(data["post_id"], current_user.id, data["vote"])
                vote.create()
            return jsonify(vote.read())

COMMENT_VOTE

This part of the API does the same exact thing as CONTENT_VOTE but for comments instead, this was due to the fact that there was a seperate model for comments so that there would be easier interactions between content and comments for a post. I’ll include the code for code’s sake but it is fundamentally the exact same as CONTENT_VOTE.

    class COMMENT_VOTE(Resource):
        @token_required()
        def post(self):
            data = request.get_json()
            current_user = g.current_user
            vote = BinaryOverflowCommentVotes.query.filter_by(_user=current_user.id, id=data['post_id']).first()
            if vote:
                vote.update(data)
            else:
                vote = BinaryOverflowCommentVotes(data["post_id"], current_user.id, data["vote"])
                vote.create()
            return vote.read()