Reputation: 1027
I'm new to Flask and Jinja2 and HTML templates and I'm trying to learn how to pass information back and forth between the controller and the views. Basically, I have a calendar of events. Each event name is a hyperlink that uses url_for
to go to a view with more information about that event. But although I can easily pass a list of custom Event objects into the HTML file, I don't know how to have the selected Event object returned to the controller. From what I can tell, the object is being turned into a string. Simplified code below.
app.py
from flask import Flask, render_template, url_for
app = Flask(__name__)
class Event(object):
def __init__(self, name, date):
self.name = name
self.date = date
events = [Event('event1', '2020-04-11')]
@app.route('/')
def index():
return render_template('index.html', events=events)
@app.route('/event/<event>')
def event(event):
return render_template('event.html', event=event)
index.html
<!DOCTYPE html>
<html>
<body>
{% for event in events %}
<a href={{ url_for('event', event=event) }}>{{ event.name }}</a>
{% endfor %}
</body>
</html>
event.html
<!DOCTYPE html>
<html>
<body>
<p>{{ event.name }} {{ event.date }}</p>
</body>
</html>
Clicking on the event brings me to a blank page, presumably because event.html is trying to get attributes of a string object that don't exist.
When passing the python object into the view is so simple, it seems like there's probably an equally simple way to get it back from the view. Please enlighten me!
Upvotes: 1
Views: 2674
Reputation: 3058
This is possible using custom URL converters and providing a method to serialize your class, which can become tedious very quickly:
from __future__ import annotations
import json
class Event():
def __init__(self, name, date):
self.name = name
self.date = date
def to_url(self):
name = self.name.replace(' ', '+')
# format the date similarly
return f'name+{name}+date+{self.date}'
@classmethod
def from_url(cls, url: str):
# extract name and date from url ...
event = Event(name, date)
You then write the converter as follows:
from werkzeug.routing import BaseConverter
from app.models.event import Event
class EventConverter(BaseConverter):
def to_python(self, url):
return Event.from_url(url)
def to_url(self, event):
return event.to_url()
Then declare it in when you create your application using:
from app.converters.event_converter import EventConverter
# etc.
app.url_map.converters['event'] = EventConverter # add URL converters.
and use it your routes like:
@app.route('/event/<event:event>')
def event(event):
pass
Note this is fine, and I've used it before for smaller projects that you can store solely in memory, but you'd be best using an ID and storing your events in a database. So assuming you have an application factory:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
# etc.
You would access your database and define a model like so:
from app import db
class Event(db.Model)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
date = db.Column(db.DateTime, nullable=False)
and passing the <event_id>
as mentioned in https://stackoverflow.com/a/61154232/5058116. You can then query the Event
table/model as follows:
from app.models.event import Event
@app.route('/event/<int:event_id>')
def event(event_id):
event = Event.query.get(event_id)
# etc.
Upvotes: 0
Reputation: 1377
When you receive the <event>
, is just a string, not an object. So you should find the event id out on your database, something like
@app.route('/event/<event>')
def event(event):
e=db.find_event(event) # event is the event number or id
if e:
return render_template('event.html', event=e)
else:
return f'Event {event} not found', 404
Upvotes: 1
Reputation: 73936
@app.route('/event/<event>')
<event>
, in this case, is a string. See the Flask quickstart for details on this. You cannot pass arbitrary objects through URLs like you are trying to do here.
Typically, you would have an ID of some kind here that you can use to look up the object, perhaps in a database. In that case, you would pass that ID into url_for()
which would produce a URL like /events/123
.
Then, when your event
route is called, it's given the ID in the event
argument. Then it's up to you to look up the right object and pass it into your template.
Upvotes: 3