Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a class variable available to Jinja2 templates with Flask?

I have a template that is using flask-login and I have a permission model built on bitsets. In my template, I want to expose content in a page based on permissions. I have a function can(permission) in my user model that controls what a user can do.

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True)
    fullname = db.Column(db.String(75))
    password_hash = db.Column(db.String(128))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def can(self, permissions):
        return self.role is not None and (self.role.permissions & permissions) == permissions

    def __repr__(self):
        return '<User: {}>'.format(self.email)

class Permission:
    FOLLOW = 0x01
    COMMENT = 0x02
    WRITE_ARTICLES = 0x04
    MODERATE_COMMENTS = 0x08
    ADMINISTER = 0x80

When I try to call the can() function in my template and pass it a permission, I get an error that Permission is not defined, presumably because the Permission class is not in scope for the template. Here is part of my base.html that all other templates extend:

{% if current_user.can(Permission.ADMINISTER) %}
<h3>Administration</h3>
<ul>
    <li><a href="{{ url_for('main.add_user') }}">Add User</a></li>
    <li><a href="{{ url_for('main.change_password') }}">Change Password</a></li>
    <li><a href="{{ url_for('main.lock_user') }}">Lock User</a></li>
</ul>
{% endif %}

I'm loosely following Miguel Grinberg's book on Flask Web Development and he is doing the same thing in his base.html (line 32).

How can I make the Permission class available to my template?

like image 464
jcaine04 Avatar asked Mar 25 '15 13:03

jcaine04


1 Answers

Most obviously, but least conveniently, just pass the Permission class when rendering a template that needs it.

return render_template('page.html', Permission=Permission)

Take this a step further by telling Flask to automatically add it to the template context. Decorate a context processor somewhere while setting up your app.

@app.context_processor
def include_permission_class():
    return {'Permission': Permission}

Context is only avaialble to the immediate template being rendered for performance and clarity reasons. If you need it available in an {% import %} or {% include %} you could add with context to each block that needs it: {% include 'other_template.html' with context%}. Or, you could add the class to the Jinja env's global namespace.

app.add_template_global(Permission, 'Permission')
like image 181
davidism Avatar answered Oct 21 '22 09:10

davidism