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.
Setting up the project
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.
Working in the Flask environment
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:
1$ python3 -m venv .venv2$ source .venv/bin/activate
Next, we'll install the Flask package and some others with the following command:
1$ 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.
Making the Flask API
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.
1# api.py2import json3from flask import Flask, jsonify, request4from flask_sqlalchemy import SQLAlchemy5from flask_migrate import Migrate6from flask_cors import CORS78app = Flask(__name__)910CORS(app)1112app.config[13 "SQLALCHEMY_DATABASE_URI"14] = "postgresql://username:password@localhost:5432/calendar"1516db = SQLAlchemy(app)1718migrate = Migrate(app, db)
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.
Database schema
We can add the schema for the one table we need below our app setup. Below the existing code, add the following.
1# api.py23...4class EventsModel(db.Model):5 __tablename__ = "events"67 id = db.Column(db.Integer, primary_key=True)8 title = db.Column(db.String())9 date = db.Column(db.Date())10 time = db.Column(db.Time())11 location = db.Column(db.String())12 description = db.Column(db.String())1314 def __init__(self, title, date, time, location, description):15 self.title = title16 self.date = date17 self.time = time18 self.location = location19 self.description = description2021 def __repr__(self):22 return f"<Event {self.title}>"
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.
1$ 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.
1$ flask db migrate -m "added events table"
Finally, we can run this migration and make these changes in the database with the following command.
1$ 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.
Create an event
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.
1# api.py23...4@app.route("/event", methods=["POST"])5def create_event():6 if request.is_json:7 data = request.get_json()89 new_event = EventsModel(10 title=data["title"],11 date=data["date"],12 time=data["time"],13 location=data["location"],14 description=data["description"],15 )1617 db.session.add(new_event)18 db.session.commit()1920 return {"message": f"event {new_event.title} has been created successfully."}21 else:22 return {"error": "The request payload is not in JSON format"}
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.
Get all of the events
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.
1# api.py23...4@app.route("/events", methods=["GET"])5def get_events():6 eventsData = EventsModel.query.all()7 events = [8 {9 "id": event.id,10 "title": event.title,11 "date": event.date,12 "time": event.time,13 "location": event.location,14 "description": event.description,15 }16 for event in eventsData17 ]1819 return {"events": json.dumps(events, default=str)}
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.
Get and update an event
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.
1# api.py23...4@app.route("/event/<event_id>", methods=["GET", "PUT"])5def event(event_id):6 event = EventsModel.query.get_or_404(event_id)78 if request.method == "GET":9 response = {10 "id": event.id,11 "title": event.title,12 "date": event.date,13 "time": event.time,14 "location": event.location,15 "description": event.description,16 }1718 return {"message": "success", "event": json.dumps(response, default=str)}19 else:20 data = request.get_json()2122 event.title = data["title"]23 event.date = data["date"]24 event.time = data["time"]25 event.location = data["location"]26 event.description = data["description"]2728 db.session.add(event)29 db.session.commit()3031 return {"message": f"event {event.title} successfully updated"}
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.
Run the file correctly
We're going to run this as the main program so we'll add this bit of code at the bottom of our file.
1# api.py23...4if __name__ == "__main__":5 app.run()
You can test this out by running the app with the following command in your terminal.
1$ 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.
Building the React App
Switching over to the front-end, go to the root directory in the terminal and run the following command.
1$ 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.
1$ npm i axios react-calendar react-modal
With the packages installed, let's start by making the one component we need for this project.
Add the event modal
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.
1// Event.tsx2import axios from "axios";3import Modal from "react-modal";45Modal.setAppElement("#root");67export default function Event({8 showModal,9 setShowModal,10 event,11}: {12 showModal: boolean;13 setShowModal: () => void;14 event?: any;15}) {16 const saveEvent = async (event: any) => {17 event.preventDefault();18 const eventData = {19 title: event.target.title.value,20 date: event.target.date.value,21 time: event.target.time.value,22 location: event.target.location.value,23 description: event.target.description.value,24 };2526 if (!event) {27 await axios.post("http://localhost:5000/event", eventData);28 } else {29 await axios.put(`http://localhost:5000/event/${event.id}`, eventData);30 }31 };3233 return (34 <>35 <Modal36 isOpen={showModal}37 onRequestClose={setShowModal}38 contentLabel="Event"39 >40 <div>Event</div>41 <form onSubmit={saveEvent}>42 <div>43 <label htmlFor="title">Event title</label>44 <input45 name="title"46 type="text"47 id="title"48 defaultValue={event?.title}49 />50 </div>51 <div>52 <label htmlFor="date">Event dateime</label>53 <input54 name="date"55 type="text"56 id="date"57 defaultValue={event?.date}58 />59 </div>60 <div>61 <label htmlFor="time">Event time</label>62 <input63 name="time"64 type="text"65 id="time"66 defaultValue={event?.time}67 />68 </div>69 <div>70 <label htmlFor="location">Event location</label>71 <input72 name="location"73 type="text"74 id="location"75 defaultValue={event?.location}76 />77 </div>78 <div>79 <label htmlFor="description">Event title</label>80 <input81 name="description"82 type="text"83 id="description"84 defaultValue={event?.description}85 />86 </div>87 <button onClick={setShowModal}>Close modal</button>88 <button type="submit">Save event</button>89 </form>90 </Modal>91 </>92 );93}
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.
The calendar page
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.
1// App.tsx23import axios from "axios";4import { useEffect, useState } from "react";5import Calendar from "react-calendar";6import "react-calendar/dist/Calendar.css";7import Event from "./components/Event";89export default function App() {10 const [date, setDate] = useState(new Date());11 const [showModal, setShowModal] = useState<boolean>(false);12 const [showEditModal, setShowEditModal] = useState<boolean>(false);13 const [event, setEvent] = useState({});14 const [events, setEvents] = useState<any>([]);1516 useEffect(() => {17 axios.get("http://localhost:5000/events").then((res) => {18 setEvents(JSON.parse(res.data.events));19 });20 }, []);2122 function tileContent({ date, view }: any) {23 const display = events.map((event: any) => {24 const eventDate = new Date(event.date);25 eventDate.setDate(eventDate.getDate() + 1);2627 if (date.toDateString() === eventDate.toDateString()) {28 return " " + event.title + " ";29 }30 return "";31 });32 return display;33 }3435 return (36 <div>37 <h1>Event Calendar</h1>38 <Calendar39 onChange={setDate}40 value={date}41 onClickDay={() => setShowModal(true)}42 tileContent={tileContent}43 />44 <>45 {events &&46 events.map((event: any) => (47 <div48 key={event.title}49 onClick={() => {50 setEvent(event);51 setShowEditModal(true);52 }}53 style={{ borderBottom: "1px solid grey", padding: "18px" }}54 >55 <div>{event.title}</div>56 <div>57 {event.date} - {event.time}58 </div>59 <div>{event.location}</div>60 <div>{event.description}</div>61 </div>62 ))}63 </>64 {showEditModal && (65 <Event66 showModal={showEditModal}67 setShowModal={() => setShowEditModal(false)}68 event={event}69 />70 )}71 {showModal && (72 <Event showModal={showModal} setShowModal={() => setShowModal(false)} />73 )}74 </div>75 );76}
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.
Style the calendar
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.
1// index.scss23... .react-calendar {4 width: 100% !important;5 border: solid 1px white;6 border-radius: 12px;7}
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!
Finished code
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.
Conclusion
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.