First Post

05/08/2019 11:50 by Dominic DiTaranto

It feels weird to begin writing the first post of this blog. I spent the last month creating the blogging engine, Alambi, that is powering this page; however, not once did I think about what I was actually going to post once v.1 was completed. There were a bunch of challenges creating Alambi. I started learning Python about 5 months ago, so the whole creation process has been a learning experience for me.  A lot of things that I originally thought would have been simple to implement turned out to take the most time, and what I feared would take a week to complete were done in a matter of seconds. A great example of both situations would be implementing a many-to-many database relationship in SQLAlchemy in order to manage the tags fo a blog post. The Flask-SQLAlchemy documentation clearly lays out how to implement this type of relationship with this example:

 

tags = db.Table('tags',
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
    db.Column('page_id', db.Integer, db.ForeignKey('page.id'), primary_key=True)
)

class Page(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tags = db.relationship('Tag', secondary=tags, lazy='subquery',
        backref=db.backref('pages', lazy=True))

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)

Source: https://flask-sqlalchemy.palletsprojects.com/en/2.x/models/

And guess what, not surprisingly, it works just as it says.  But you would never know that unless you actually understood how to add and get to the tags that are stored in the database (valuable information not available in the documentation linked above)! Before I get into that, let me show you how I implemented the many-to-many relationship above in Alambi (for clarity).

alambi/models.py

tags = db.Table('tags',
                db.Column('blog_id', db.Integer, db.ForeignKey('blog.blog_id')),
                db.Column('tag_id', db.Integer, db.ForeignKey('tag.tag_id'))
                )


class Blog(db.Model):
    blog_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(200), unique=False, nullable=False)
    date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    text = db.Column(db.Text)
    like = db.Column(db.Integer, default=0)
    sticky = db.Column(db.Boolean, default=False)
    category = db.Column(db.String(200), default='No Category')
    comments = db.relationship("Comment", backref='post', cascade="all, delete-orphan")
    comment_count = db.Column(db.Integer, default=0)
    tags = db.relationship('Tag', secondary=tags,
                           backref=db.backref('post_tags', lazy='dynamic'))
    

class Tag(db.Model):
    tag_id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200))

One thing to notice is that the primary key column of both the Blog  and Tag tables, which I usually just call "id", needed to be distinguished from eachother to avoid errors. Thus they are named blog_id and tag_id respectively. 

Okay, so the tables are connected with the 'tags' association table, let's try to insert a dummy post in the python interpreter to make sure everything is working fine.

First, let's take a look at the wrong way:

>>>post = Blog(name="First Post", text="this is my first post", category="Talking", tags=['tags', 'test'])

Alas, this throws the error: AttributeError: 'str' object has no attribute '_sa_instance_state'. So we know something is wrong with just using a list of strings in the tags column (I might need to be more clear, the error thrown does not actually say anything about which column is throwing the error.  but considering "tags" is what we are focusing on here and the other columns are just getting strings, we can infer the issue is with the 'tags' row). Why doesn't this work? It seems like a list would be the logical choice here. But we have to remember that the tags are actually being handled by the 'Tag' table, and are just referenced in the 'Blog' table. Therefore, we must input them as objects in order for them to properly be handled by the Tag table prior to insertion into the Blog table.

Now, let's see the correct way:

>>>post = Blog(name="First Post", text="this is my first post", category="Talking", tags=[Tag(name='foo'), Tag(name='bar')])
>>>db.session.add(post)
>>>db.session.commit()

What should pop out first to you is that when adding tags to a post, we need to add them as their own objects. This is something that was not immediately clear to me, but was able to reverse engineer my understanding by looking at the code in Learning Flask Framework  by Matt Copperwaite and Charles Leifer on page 37-42. I do not know if I can actually post their code here due to copyright, but I do recommend taking a look at their book to get a better grasp on how comma separated values can be taken from a Flask-WTForms string field  and correctly inserted into a many-to-many db. Either way, what we need to take away from here is that, in this case, the backref column has data input as a list of objects, rather than al ist of strings. Do not forget to add 'post' and commit it!

Okay, that was not so bad, the rest will be just as easy. We can view the tags in the python interpreter as such:

>>> post.tags
[<Tag 1>, <Tag 2>]

And to see their names is just as simple:

>>> post.tags[0].name
'foo'
>>> post.tags[1].name
'bar'

Practical Implementation

What if we wanted to post the tags for a specific post on a Flask website. First, in our view, we need to query the post that we want to display. For now, let's just grab the first post in our Blog table:

routes.py

@app.route("/post")
def post(post_id):
    post = Blog.query.first()
    return render_template('post.html', post=post)

Then, in our post.html template we can display the information like this:

post.html

    {# Name of Post #}
    <h1>{{post.name|safe}}</h1>

    {# Date of Post #}
    <small>{{post.date.strftime('%m/%d/%Y %I:%M')|safe}} by Dominic DiTaranto</small>

    {# post content #}
    <p>{{post.text|safe}}</p>

    {# Tags #}
        <b>Tags:</b>
            {% for tag in post.tags %}
                {{tag.name}} {%if not loop.last%}, {%else%} {%endif%}
            {% endfor %}

Of course, we also want to put the name, date, and actual content of the post as well, so I added that in while still trying to remain as minimal as possible for pedagogical purposes. Moving to the {# Tags #} section, we can see a simple for loop in Jinja2 which just grabs the name of the tags for each tag associated with the specific post we are displaying. The if statement following the tag name watches whether or not the loop is on its final iteration, and if it is, will omit the comma after the tag.  You can see an example of this in action by looking at the bottom of this post and finding the "Tags" section.

I hope this was informative and understandable. As this is my first time trying to formally explain programming, I invite any and all criticism!

Continue Reading

Category: Coding
Tags: Python, Web Development, Contact Form, First-Post!, SQLAlchemy, Jinja2, Flask

DOMDIT.COM

Hello and welcome to my blog. I am planning to post from a myriad of different topics including: coding, translations, reflections on books I am currently reading, music that I am writing, photography, as well as personal topics/events that may creep in once in a while. To learn more about me, you can visit the main page of this website here, or you can always comment on one of my posts and I will get back to you!

Recent Posts:

How To: Django Contact Form
06/09/2019 12:06
Asteroids in p5.js
06/08/2019 08:30
First Post
05/08/2019 11:50

Popular Posts:

First Post
05/08/2019 11:50
Asteroids in p5.js
06/08/2019 08:30
How To: Django Contact Form
06/09/2019 12:06

Recent Categories:

  • Coding

  • Recent Tags:

    Asteroids Contact Contact Form Django Email First-Post! Flask Jinja2 Python SQLAlchemy Web Development javascript music p5.js
    Powered by Alambi | Designed by Dominic DiTaranto © 2019