How we made our Blog

0 likes

From August 1st until recently, we were busy creating this blog with HTML, CSS, JavaScript and Python backend. Previously we were working on another website, so we started this by simply copy pasting what we already had and modifying as needed. Below is a breakdown of the technologies used. We made this website from scratch.

breakdown of the technology used

The Code

The source code for this blog can be found here on GitHub. We tried to follow best practices as closely as possible, so we hope this repository will help you structure your websites as well.

Frontend - @StevenOh

For the frontend of the website, I mainly worked on the page layout with HTML, styling and responsiveness with CSS, and some frontend logic with Javascript. The frontend section required a lot of testings and trials, and is mostly about adjusting the optimal layout for the viewer. My job was less intellectually demanding than that of the backend.

Pages

The pages are mainly structured by the divs with the class “row.” Even though these HTML tags take up with the majority of the line count, it is simply a repetitive work. If you’ve coded a website you would know.

Modal

If you’ve visited the homepage of our website, you must have noticed the pop up that asks you to join our mailing list. That is called a modal. The pop up was designed to show 4 seconds after the homepage is loaded, but it won’t show again on the homepage if you only navigate on our website with on-page buttons(if you don’t refresh the page). Also, the “submit” button only works if you’ve filled out all the text fields. Below is a snippet of the Javascript code:

function displayPop() {
  var timeOut = setTimeout(function () {
    document.querySelector(".bg-modal").style.display = "flex";
  }, 4000);

  document.querySelector(".close").addEventListener("click", function () {
    document.querySelector(".bg-modal").style.display = "none";
  });
  document
    .getElementById("modal-button")
    .addEventListener("click", function () {
      if ($("#modal-name").val() != "" && $("#modal-name").val() != "") {
        $.ajax({
          url: "{{ url_for('main.email_confirm')}}",
          type: "POST",
          data: {
            name: $("#modal-name").val(),
            email: $("#modal-email").val(),
          },
        });
        document.querySelector(".bg-modal").style.display = "none";
      }
    });
}
if (document.referrer.indexOf(window.location.hostname) == -1) {
  displayPop();
}

Responsiveness

Making this page mobile-friendly in every aspect was a very time-consuming process. If you’ve visited our website on a laptop or a device with a bigger screen, you will notice some differences. For instance, the homepage consists of rows of three blog posts on a bigger screen, whereas mobile views only contain one on each row. This was done through one of the CSS elements from Bootstrap. In my code, I also utilized @media screen CSS element to customize padding and margin on specific screen width.

A few interesting frontend features

Backend - @SoySoy4444

Since I prefer Python over other popular backend alternatives like NodeJS, PHP and Ruby, I watched Corey Schafer's Flask tutorials series on YouTube. If you're learning Python and you don't know who he is, you should definitely check him out as he covers a wide range of Python programming syntax and libraries. I will be writing a post about my top youtube channel recommendations very soon, so definitely make sure to subscribe to our email newsletter.

Naturally, you will most likely find that you want to do more with your blog than what he covered in the tutorials. For me, I wanted to create a draft system, a mailing list, a tag system, a comment section, a dedicated role management system, and more. I will be covering each one of them below, in order of increasing difficulty.

Draft system

I wanted to create an option to "draft" our posts so that we can work on them and save it for later without publishing it for everyone to see. This was just a mater of adding a draft column to the Post database and a draft BooleanField to the Flask form.

Role Management System

I wanted to create Member and Admin roles similar to what you would find on a Discord server, so that only admins (@StevenOh and myself) can access certain content like creating new pages, sending out emails to our subscribers, accessing the database, and more.

For this, I found a Flask extension called flask-admin which perfectly did what we wanted to accomplish. I found this video by Pretty Printed extremely useful, so definitely go check it out and the rest of his videos.

Basically, what this allows me to do is to create a custom page. This is a snippet of my models.py file.

from application import admin # import the admin extension initialised in the __init__ file
from flask_admin.contrib.sqla import ModelView
class MyModelView(ModelView):
	def is_accessible(self):
		return current_user.is_authenticated and current_user.role == "Admin" # only Admins can access
admin.add_view(MyModelView(User, db.session))
admin.add_view(MyModelView(Post, db.session))
admin.add_view(MyModelView(Tag, db.session))
admin.add_view(MyModelView(Like, db.session))
admin.add_view(MyModelView(Comment, db.session))
admin.add_view(MyModelView(SubscribedUser, db.session))

And, as always, your admin extension needs to be initialised in your __init__.py file like so:

from flask_admin import Admin
admin = Admin()
def create_app(config_class=Config):
    admin.init_app(app)

Once that is done, the flask-admin extension automatically creates a new page for you.

Screenshot of the Flask admin page
Flask admin page, at undefinedblog.team/admin. This page is restricted for admins only and it allows us to directly create, edit and delete rows from the tables in our database easily.

Tag System

This was my first time working with many-to-many relationships in SQLAlchemy, so I had to do a lot of googling and stack overflow searching to get the feature I wanted.

tag_table = db.Table('tag_association',
    db.Column('id', db.Integer, db.ForeignKey('post.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.tag_id'))
)
class Post(db.Model):
	tags = db.relationship("Tag", secondary=tag_table, backref=db.backref('posts', lazy='dynamic'), lazy='dynamic') #Tag.posts
class Tag(db.Model):
	tag_id = db.Column(db.Integer, primary_key=True)
	name = db.Column(db.String(30), unique=True, nullable=False)
	colour = db.Column(db.String(30), default="black")

Like Feature

Another feature we wanted to implement was a "like post" feature.

class Like(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)

class User(db.Model, UserMixin):
    liked_posts = db.relationship('Like', backref=db.backref('user'), lazy="dynamic")
    def like_post(self, post):
        if not self.has_liked_post(post): #if the user has not already liked this post yet,
            like = Like(user_id=self.id, post_id=post.id)
            db.session.add(like)
            db.session.commit()

    def unlike_post(self, post):
        if self.has_liked_post(post): #if the user has already liked the post,
            Like.query.filter_by(user_id=self.id, post_id=post.id).delete()
    def has_liked_post(self, post):
        return Like.query.filter(Like.user_id == self.id, Like.post_id == post.id).count() > 0
class Post(db.Model):
    likes = db.relationship("Like", backref=db.backref('post'), lazy="dynamic") #Post.liked_users

Comment Section

By far the hardest feature to implement was a comment section. I wanted to create a nested comment section like you find on Reddit, but according to Miguel Grinberg, a well respected author of Flask tutorials, this is extremely difficult to do with an ORM (Object Relational Mapper) like SQLAlchemy.

After choosing to create a flat comment section, I also had another problem. I wanted both logged in users and logged out users to be able to comment on our posts, so I tried implementing two separate models called AnonymousComment and UserComment, but many conflicts arose in the Jinja2 in the post HTML page, so I had to settle with the single Comment class below.

class Comment(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	post_id = db.Column(db.Integer, db.ForeignKey("post.id"))
	name = db.Column(db.String(20))
	content = db.Column(db.Text, nullable=False)
	timestamp = db.Column(db.DateTime, index=True, default=datetime.now(get_localzone()))

I wanted users to be able to read all of their comments, by accessing something like user.comments, but I was not able to implement this feature because of the fact that some comments do not have a "user" author - some are anonymous comments.

Publishing

Sometime along the way, we found out about the GitHub Student Developer Pack, a pack full of awesome perks for anyone enrolled in an educational institution. With this pack, we got a .team domain name for free from name.com and we have been able to create high quality thumbnails with Canva Pro. Our site is hosted on a free dyno at Heroku using gunicorn and a Postgres database.

Our frontend developer @StevenOh also uses Polypane to help develop a responsive website. Polypane is a separate browser, akin to Safari or Chrome, which lets you emulate a page in different screen sizes like a phone, a table and a computer.

Finally, Heroku has an ephemeral hard drive, meaning any files written to the server will not be saved once the app is redeployed (when we push a new change to GitHub, Heroku automatically redeploys our website) or the application is refreshed, which happens every 24 hours. Therefore, we signed up for the AWS Free Tier which lets us use S3 (Simple Storage Service) completely free of charge for one year. I then changed my Python code so that the files are saved to our S3 bucket instead, using Python's boto3 library.


So, what did you think of our blog-making journey? Do you see any possible improvements? Did it inspire you to start your own blog? If so, like this post and comment down below! P.S. (you saw the amount of work it took to create those features, use them!)


Leave a Comment
/200 Characters