HOLIDAY SALE! Save 50% on Membership with code HOLIDAY50. Save 15% on Mentorship with code HOLIDAY15.

16) Representing Content Lesson

Create Permanent Links - aka Permalinks

9 min to complete · By Brandon Gigous

You've been on websites, and you've looked at content on those websites. Say you find a cool thing, like an article on a technological innovation that allows ice cream to never melt, called a "freezer," and you want to read more about it later. This website has all kinds of content, but how do you get back to that specific article? Luckily, humans invented permalinks, which we'll discuss in this lesson.

Permalinks are unique URLs that reference specific content on a website. For the Python + Flask app you're building as a part of this course, you'll be able to give permalinks to user-generated content like compositions so they can bookmark those pages to access them again. And send them to grandma.

Some websites generate permalinks for content with a unique numeric ID that matches the ID of that particular data in their database. Your Compositions are a great example. Each one has a unique ID in the database, which you can use to link directly to a specific composition. The first composition created in the database might have a permanent link that looks like this:

http://localhost:5000/composition/1

That works just fine! But what if you want to make permalinks that better represent what's contained at the link? Something more readable, like .../composition/44315-the-shimmer-of-idols-album. These are called slugs, and that's exactly what you'll do for your permalink to compositions. Each one is a unique string based on what's on the "other side" of the link.

a photo of a slug

To generate slugs for your users' compositions, you'll want to encapsulate this bit of functionality in your Composition model. Not only that, it'd be good to add a new field for your slug to the model so that you can stash it away and even update it if the user decides they want to change the title of their creation.

import re

class Composition(db.Model):
    # ...
    slug = db.Column(db.String(128), unique=True)
    # ...
    def generate_slug(self):
        self.slug = f"{self.id}-" + re.sub(r'[^\w]+', '-', self.title.lower())
        db.session.add(self)
        db.session.commit()

This new column will contain a string long enough to hold any kind of title. To create a more human-readable permalink, you can use regular expressions. Spaces and other non-letter and non-number characters won't look pretty in your new permalink, so it's best to remove those and replace them with hyphens. Then, all the letters can be lowercase to make for a pretty link. In order to ensure each link to a composition is truly unique, it's necessary to tack on the actual ID of the composition. Two users could have the same composition title, ensuring that both can be accessed separately.

You'll want to include this new function in your fake.py as well as your in your index() view function:

# fake.py
def compositions(count=200):
    # ...
    db.session.commit()
    for c in Composition.query.all():
        c.generate_slug()

# main/views.py
def index():
    # ...
    if current_user.can(Permission.PUBLISH)
            and form.validate_on_submit():
        # ...
        db.session.add(composition)
        db.session.commit()
        # must be generated after the first commit
        composition.generate_slug()
        return redirect(url_for('.home'))

Both of these calls to generate_slug() must be done after the compositions are submitted to the database. This is because the function depends on the composition's id, which isn't created until after the composition is committed.

Now that your permalinks, or slugs, can be created, it's time to make those links come to life! To do this, create a new view function for an individual composition. The dynamic part of the route will correspond to the slugs in the database.

@main.route('/composition/<slug>', methods=['GET', 'POST'])
def composition(slug):
    composition = Composition.query.filter_by(slug=slug).first_or_404()
    return render_template('composition.html',
                           compositions=[composition],
                           form=form,
                           comments=comments,
                           pagination=pagination)

You'll notice that this bit of code passes the composition contained in a list and is represented as compositions to the template. That's so that you can reuse your _compositions.html once again to prevent you from doing double or triple the work! So, now you'll have a template that can render a single composition.

Speaking of which, you'll need to add permalinks to all of those beloved compositions you list on the index page and beyond.

Learn by Doing

Task: Add a link to the respective composition in your _compositions.html file. You can put the link in the title.

Then, you'll need to make the template composition.html that can render the single composition.

Task: Create your composition.html. Don't forget to include _compositions.html!

web page showing a single composition at the target slug

Once you're all done with that, congratulations! You've gotten another step closer to completing your first Flask web app. Now it's time to let your users edit their compositions at will in the next lesson.

  • Permalinks are unique URLs that reference specific content on a website.

  • Slugs better represent what's contained at the link and make it more readable to a human

  • To generate slugs for your users' compositions, encapsulate this bit of functionality in your Composition model. Then, add a new field for your slug to the model so that you can stash it away and even update it if the user decides they want to change the title of their creation.

    import re
    
    class Composition(db.Model):
        # ...
        slug = db.Column(db.String(128), unique=True)
        # ...
        def generate_slug(self):
            self.slug = f"{self.id}-" + re.sub(r'[^\w]+', '-', self.title.lower())
            db.session.add(self)
            db.session.commit()
    
  • To make those links come to life! Create a new view function for an individual composition. The dynamic part of the route will correspond to the slugs in the database