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()