Raising awareness of things going on in your community is a great way to make people feel more connected and involved in what’s going on around them. A community calendar where people can add events can help with that.
We’re going to build a calendar that uses Flask on the back-end to interact with a Postgres database and React on the front-end. Users will be able to upload event details, see which days have events, and more.
We’re going to need a root folder to hold the front-end and back-end of our project, so create a new folder called events-calendar
. Inside this folder, add a new subfolder called server
. We’ll make the Flask app in the server
folder.
Now let’s set up the local Postgres instance. You can download it here. Make sure you take note of your username and password because we’ll need them in the connection string for the back-end. That’s all the base setup we need and now we can start making the Flask app.
Since the back-end will use a Python framework, you’ll need to create a virtual environment before we start installing packages. To do that, open your terminal, go to the server
directory, and run the following commands:
$ python3 -m venv .venv
$ source .venv/bin/activate
Next, we’ll install the Flask package and some others with the following command:
$ pip install flask flask_sqlalchemy flask_cors flask_migrate
With the environment set up and all of the packages installed, we’re ready to create some files for our server.
Inside the server
folder, add a new file called api.py
. This is the file that will hold the different endpoints our front-end will call. Open this new file and add the following code.
# api.py
import json
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
app.config[
"SQLALCHEMY_DATABASE_URI"
] = "postgresql://username:password@localhost:5432/calendar"
db = SQLAlchemy(app)
migrate = Migrate(app, db)
Code language: PHP (php)
This brings in all of the packages we need and it creates the application. We’re applying the CORS package to the app so that we can call the endpoint from a different domain. Then we’re connecting the app to our Postgres database and getting it ready to migrate the schema. Make sure you put in your username and password for that connection string.
We can add the schema for the one table we need below our app setup. Below the existing code, add the following.
# api.py
...
class EventsModel(db.Model):
__tablename__ = "events"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String())
date = db.Column(db.Date())
time = db.Column(db.Time())
location = db.Column(db.String())
description = db.Column(db.String())
def __init__(self, title, date, time, location, description):
self.title = title
self.date = date
self.time = time
self.location = location
self.description = description
def __repr__(self):
return f"<Event {self.title}>"
Code language: HTML, XML (xml)
This describes the table that we want to add to the database. It will be the events
table and it’ll have six columns to hold details about every event we add. This is all we need to run the database migration. To do that, go to the server
directory in your terminal and run the following.
$ flask db init
This will add a migrations
folder to your project. Next, we need to generate the migration for our model with the following command.
$ flask db migrate -m "added events table"
Code language: JavaScript (javascript)
Finally, we can run this migration and make these changes in the database with the following command.
$ flask db upgrade
If you take a look in your Postgres instance, you’ll see the new table and all of the fields we defined in the model. Now we can start creating the endpoints that will work with the data we store in this table.
Let’s start with the endpoint that will allow users to create new events on the calendar. Below the events model, add the following code.
# api.py
...
@app.route("/event", methods=["POST"])
def create_event():
if request.is_json:
data = request.get_json()
new_event = EventsModel(
title=data["title"],
date=data["date"],
time=data["time"],
location=data["location"],
description=data["description"],
)
db.session.add(new_event)
db.session.commit()
return {"message": f"event {new_event.title} has been created successfully."}
else:
return {"error": "The request payload is not in JSON format"}
Code language: PHP (php)
When a POST
request is made to the /event
endpoint with the correct data, it will add a new record to the events table in the database and send a success message with the event title in the response.
We’ll need to be able to get all of the events that are in the calendar so that we can render them correctly. To fetch all of the events from the database, add the following code to your api.py
file below the create event endpoint.
# api.py
...
@app.route("/events", methods=["GET"])
def get_events():
eventsData = EventsModel.query.all()
events = [
{
"id": event.id,
"title": event.title,
"date": event.date,
"time": event.time,
"location": event.location,
"description": event.description,
}
for event in eventsData
]
return {"events": json.dumps(events, default=str)}
Code language: PHP (php)
This creates a new GET
route called /events
. Anytime this endpoint is called with a GET
request, it will go to the database and retrieve all of the records from the events table and return it as a JSON string.
We can take advantage of the request type to use the same endpoint to handle different functionality. There will be times we want to look at or edit a particular event. We could use separate endpoints for this, but we’re going to use different request types to distinguish which type of call is being made. Add the following code below the endpoint to get all of the events.
# api.py
...
@app.route("/event/<event_id>", methods=["GET", "PUT"])
def event(event_id):
event = EventsModel.query.get_or_404(event_id)
if request.method == "GET":
response = {
"id": event.id,
"title": event.title,
"date": event.date,
"time": event.time,
"location": event.location,
"description": event.description,
}
return {"message": "success", "event": json.dumps(response, default=str)}
else:
data = request.get_json()
event.title = data["title"]
event.date = data["date"]
event.time = data["time"]
event.location = data["location"]
event.description = data["description"]
db.session.add(event)
db.session.commit()
return {"message": f"event {event.title} successfully updated"}
Code language: PHP (php)
This checks that the database has a record for the given event ID or it returns an error. If there is a record, the request type is checked and if it is a GET
request, it will return a JSON string with that event’s details. If it’s a POST
request, it will update the event record and save the changes to the database.
We’re going to run this as the main program so we’ll add this bit of code at the bottom of our file.
# api.py
...
if __name__ == "__main__":
app.run()
Code language: PHP (php)
You can test this out by running the app with the following command in your terminal.
$ python api.py
You should see your server start on localhost:5000
and try sending a request to the /event
endpoint to make a new record. This is actually everything we need for the API! We have the database connection and all of the endpoints we need to work with data on the front-end so we can switch our attention there.
Switching over to the front-end, go to the root directory in the terminal and run the following command.
$ npx create-react-app client --template typescript
This will generate a new React project for you in a new directory called client
. In your terminal, go to the client
directory and run the following command to add the packages we’ll use.
$ npm i axios react-calendar react-modal
With the packages installed, let’s start by making the one component we need for this project.
In the client
folder, add a new subfolder called components
. Inside that folder, add a new file called Event.tsx
. This will be the modal that appears when users want to view or edit an individual event. Add the following code to that file.
// Event.tsx
import axios from "axios";
import Modal from "react-modal";
Modal.setAppElement("#root");
export default function Event({
showModal,
setShowModal,
event,
}: {
showModal: boolean;
setShowModal: () => void;
event?: any;
}) {
const saveEvent = async (event: any) => {
event.preventDefault();
const eventData = {
title: event.target.title.value,
date: event.target.date.value,
time: event.target.time.value,
location: event.target.location.value,
description: event.target.description.value,
};
if (!event) {
await axios.post("http://localhost:5000/event", eventData);
} else {
await axios.put(`http://localhost:5000/event/${event.id}`, eventData);
}
};
return (
<>
<Modal
isOpen={showModal}
onRequestClose={setShowModal}
contentLabel="Event"
>
<div>Event</div>
<form onSubmit={saveEvent}>
<div>
<label htmlFor="title">Event title</label>
<input
name="title"
type="text"
id="title"
defaultValue={event?.title}
/>
</div>
<div>
<label htmlFor="date">Event dateime</label>
<input
name="date"
type="text"
id="date"
defaultValue={event?.date}
/>
</div>
<div>
<label htmlFor="time">Event time</label>
<input
name="time"
type="text"
id="time"
defaultValue={event?.time}
/>
</div>
<div>
<label htmlFor="location">Event location</label>
<input
name="location"
type="text"
id="location"
defaultValue={event?.location}
/>
</div>
<div>
<label htmlFor="description">Event title</label>
<input
name="description"
type="text"
id="description"
defaultValue={event?.description}
/>
</div>
<button onClick={setShowModal}>Close modal</button>
<button type="submit">Save event</button>
</form>
</Modal>
</>
);
}
Code language: JavaScript (javascript)
This imports a few packages and sets up the modal element to target the root element. Then we have the Event
component which takes a few props. Then there is a saveEvent
method that will take the values from the form we have and either update or create a new event based on if there is any current event data available.
Then we have the form that will either be blank or display the event information in the fields depending on if the event
prop was passed to the modal. We also decide whether the modal should currently be showing or if it should be hidden based on the showModal
prop.
That’s all for the modal to show, update, or create new events. Now we need the page that shows the calendar and all of the events we have.
We’re going to modify the existing App.tsx
file to render our calendar and the events. Here’s what the new code will look like. There’s a lot going on here so we’ll walk through it.
// App.tsx
import axios from "axios";
import { useEffect, useState } from "react";
import Calendar from "react-calendar";
import "react-calendar/dist/Calendar.css";
import Event from "./components/Event";
export default function App() {
const [date, setDate] = useState(new Date());
const [showModal, setShowModal] = useState<boolean>(false);
const [showEditModal, setShowEditModal] = useState<boolean>(false);
const [event, setEvent] = useState({});
const [events, setEvents] = useState<any>([]);
useEffect(() => {
axios.get("http://localhost:5000/events").then((res) => {
setEvents(JSON.parse(res.data.events));
});
}, []);
function tileContent({ date, view }: any) {
const display = events.map((event: any) => {
const eventDate = new Date(event.date);
eventDate.setDate(eventDate.getDate() + 1);
if (date.toDateString() === eventDate.toDateString()) {
return " " + event.title + " ";
}
return "";
});
return display;
}
return (
<div>
<h1>Event Calendar</h1>
<Calendar
onChange={setDate}
value={date}
onClickDay={() => setShowModal(true)}
tileContent={tileContent}
/>
<>
{events &&
events.map((event: any) => (
<div
key={event.title}
onClick={() => {
setEvent(event);
setShowEditModal(true);
}}
style={{ borderBottom: "1px solid grey", padding: "18px" }}
>
<div>{event.title}</div>
<div>
{event.date} - {event.time}
</div>
<div>{event.location}</div>
<div>{event.description}</div>
</div>
))}
</>
{showEditModal && (
<Event
showModal={showEditModal}
setShowModal={() => setShowEditModal(false)}
event={event}
/>
)}
{showModal && (
<Event showModal={showModal} setShowModal={() => setShowModal(false)} />
)}
</div>
);
}
Code language: PHP (php)
We start off by importing the packages we need to get the calendar and the events data. Inside the App
component we have some states that will control how elements are rendered on the page. After that, we have a useEffect
hook that fetches all of the events from the database when the page is loaded.
Then there’s a function that adds the title of an event to the date on the calendar. Next, we render the Calendar
component and pass it a few props. After that, we display a list of all of the events that are currently on the calendar. Finally, we conditionally show the event modal based on current states.
The last thing we need to edit is the CSS so this looks a little better.
There will be a lot of things you need to change stylistically before this goes to production, but we’ll at least add a few things to get this to an MVP stage. Find the index.scss
file in client > src
and add the following code at the bottom.
// index.scss
... .react-calendar {
width: 100% !important;
border: solid 1px white;
border-radius: 12px;
}
Code language: JavaScript (javascript)
This will at least make the calendar the full width of the page so people can see the event titles. Here’s what the calendar page will look like if you run the front-end and back-end at the same time.
Now you have a full-stack application ready to handle all of the events in your community!
You can find the code for the finished project in the events-calendar
folder of this repo. You can also check out this Code Sandbox for the front-end.
<CodeSandBox title=“frosty-grass-bh34cq” id=“frosty-grass-bh34cq” />
Planning events is hard enough without trying to figure out how to spread the word. This might be a way to encourage people to let others know what is going on and how they can participate. This project should be flexible enough that you can style it and expand the functionality in a number of ways.