How To Build an Electoral Voting App with Python and Fauna
Tags: fauna, python, webdev, serverless
Published: 2021-02-09 20:28:38

In this article, we will be building an electoral voting app with Python, Flask, and [Fauna](https://fauna.com) serverless database, similar to [electionrunner.com](https://electionrunner.com). An electoral voting app lets users create election polls on the internet and provides functionalities that allow other users to cast their votes based on the options provided by the election creator.

If you are new to the concept of Fauna and serverless databases, read this article to become familiar with the terms associated with the topic: <https://dev.to/lordghostx/building-a-telegram-bot-with-python-and-fauna-494i>

# Getting Started with Fauna

## Step 1: Set Up Our Fauna Database

The first thing we need to do is create the database for our electoral voting app in the Fauna dashboard. If you have not created an account on Fauna before now, create one here: [https://dashboard.fauna.com/accounts/register](https://dashboard.fauna.com/accounts/register?utm_source%3DDevTo%26utm_medium%3Dreferral%26utm_campaign%3DWritewithFauna_VotingAppPythonFauna_SEsenyi)

In the dashboard, click on the “NEW DATABASE” button, provide a name for your database then press the `SAVE` button.

![](https://dev-to-uploads.s3.amazonaws.com/i/awbeo6rtg2rliztkunbr.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/lp9ixd76zz821nomukbn.png)

## Step 2: Generate Fauna API Key

We will need to create a Fauna API key to connect the database to our election voting app. To do this, navigate to the security settings on the Fauna sidebar (left side of the screen).

![](https://dev-to-uploads.s3.amazonaws.com/i/c3innxgdigdpu4elvjt5.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/218bjnw3epewikv9czqb.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/8tiwp0q8op9bnnhtx3wj.png)

Once you have done this, you will be presented with your API key (hidden here for privacy reasons). The key should be copied as soon as it is generated then stored somewhere easily retrievable.

## Step 3: Integrate Fauna into Python

Next, we need to get the Python library for Fauna. It’s available on pip and can be installed with a single line in our terminal.

```
pip install faunadb
```

After this is installed, we run the sample code provided in Fauna Python driver docs <https://docs.fauna.com/fauna/current/drivers/python.html>

```Python
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient 

client = FaunaClient(secret="your-secret-here")

indexes = client.query(q.paginate(q.indexes()))

print(indexes)
```

The code above shows how the Fauna Python driver connects to a database with its API key and prints the indexes associated with it. The result from running this code is similar to the image below.

![](https://dev-to-uploads.s3.amazonaws.com/i/bryvezsiwjgsu3otkc51.png)

# Building Our Electoral Voting App

Now that we have successfully integrated our Python script with Fauna, let’s get started with building our electoral voting app. We will build the backend and logic with [Flask](https://flask.palletsprojects.com/en/1.1.x/) and our user interface with [Flask-Bootstrap](https://pypi.org/project/Flask-Bootstrap4/) since it will be a full-stack web application.

We will be building five (5) web pages for our electoral voting app. These are:


* Register Page
* Login Page
* Create Vote Page
* Submit Vote Page
* View Vote Results Page

## Step 1: Set Up Our Flask Server

We need to install the Flask library and Flask-Bootstrap the same way we installed Fauna earlier using pip and our terminal.

```
pip install flask
pip install flask-bootstrap4
```

Let’s get a basic server running in Flask that will display the text `Hello World!` when opened. Create a new project folder and a Python file with the name `app.py` and type the following code inside.

```Python
from flask import *
from flask_bootstrap import Bootstrap
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient

app = Flask(__name__)
Bootstrap(app)
app.config["SECRET_KEY"] = "APP_SECRET_KEY"
client = FaunaClient(secret="your-secret-here")


@app.route("/")
def index():
    return “Hello World!”


if __name__ == "__main__":
    app.run(debug=True)
```

When we run our `app.py` file we should get a response quite like the image below

![](https://dev-to-uploads.s3.amazonaws.com/i/1xrk6andt7j9t1l3oeup.png)

If you get this, you’re on the right track. Open this in your browser `http://127.0.0.1:5000` to access the app.

![](https://dev-to-uploads.s3.amazonaws.com/i/aswguhq5vbx1xfolsrrt.png)

## Step 2: Design Our Registration Page

The registration page is where users are provided with a form to create a new account on our application so they can create elections and view results.

First, create a folder named `templates` in the same folder as our `app.py`. A templates folder is used by Flask to store its HTML files which will be rendered by the server in later parts of the application. Our project folder should resemble the image below:

![](https://dev-to-uploads.s3.amazonaws.com/i/kdfnns9ner49sxj5r1rh.png)

Create another file named `register.html` which will be stored inside the templates folder and save the code below in it.

```html
{% extends "bootstrap/base.html" %}

{% block content %}
<div class="container">
  <div class="row justify-content-center">
    <div class="col-lg-12">
      <div class="jumbotron text-center">
        <h2>Online Election Voting with Python and Fauna</h2>
        <p>Build a Secure Online Election for Free</p>
      </div>
    </div>

    <div class="col-lg-6">
      {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
      {% for category, message in messages %}
      <p class="text-{{ category }}">{{ message }}</p>
      {% endfor %}
      {% endif %}
      {% endwith %}
      <form method="POST">
        <div class="form-group">
          <label for="username">Username</label>
          <input type="text" class="form-control" id="username" name="username" placeholder="Choose Username" required>
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input type="password" class="form-control" id="password" name="password" placeholder="Choose Password" required>
        </div>
        <button type="submit" class="btn btn-primary">Create Account</button>
      </form>
    </div>
  </div>
</div>
{% endblock %}
```

We also need to create a `register` route in our Flask app that will render the template with the registration form. Update your `app.py` file with the code below.

```Python
from flask import *
from flask_bootstrap import Bootstrap
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient

app = Flask(__name__)
Bootstrap(app)
app.config["SECRET_KEY"] = "APP_SECRET_KEY"
client = FaunaClient(secret="your-secret-here")


@app.route("/")
def index():
    return redirect(url_for("register"))


@app.route("/register/", methods=["GET", "POST"])
def register():
    return render_template("register.html")


if __name__ == "__main__":
    app.run(debug=True)
```

When we run our `app.py` file we should get a response close to the image below when we open the `register` route in our browser:

![](https://dev-to-uploads.s3.amazonaws.com/i/2vyo3lluvucvti0enkro.png)

## Step 3: Build Our Registration Logic

Next, we want to build the functionality that allows users to create accounts on our application in Fauna. To do this, we first need to create a Fauna collection. A collection is similar to SQL tables that contain data with similar characteristics e.g user collection that contain information about users in the database. Login to your Fauna dashboard once again, and start by pressing the `NEW COLLECTION` button then provide a name for the collection we want to create.

![](https://dev-to-uploads.s3.amazonaws.com/i/60mwaumnlb2zy89nhimj.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/sley4l0fawfwj5lr81u1.png)

We also need to create an index for our collection. A Fauna index allows us to browse through data that is stored in a database collection, based on specific attributes. To create one, navigate to the `DB Overview` tab on the Fauna sidebar (left side of the screen) then click the `NEW INDEX` button.

![](https://dev-to-uploads.s3.amazonaws.com/i/96hf9w6qw8wm5wu5k6c1.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/u0ux7qnb4z1hi5w0aolj.png)

Supply the name you want to use for your index, set the terms to `data.username` as we will be using that variable to reference our users later. Also, remember to select the `Unique` attribute for our index, this ensures that we do not have a duplicate in our database entries.

Update the `app.py` file with the code below to implement the logic for account creation

```Python
import pytz
import hashlib
from datetime import datetime
from flask import *
from flask_bootstrap import Bootstrap
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient

app = Flask(__name__)
Bootstrap(app)
app.config["SECRET_KEY"] = "APP_SECRET_KEY"
client = FaunaClient(secret="your-secret-here")


@app.route("/")
def index():
    return redirect(url_for("register"))


@app.route("/register/", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        username = request.form.get("username").strip().lower()
        password = request.form.get("password")

        try:
            user = client.query(
                q.get(q.match(q.index("users_index"), username)))
            flash("The account you are trying to create already exists!", "danger")
        except:
            user = client.query(q.create(q.collection("users"), {
                "data": {
                    "username": username,
                    "password": hashlib.sha512(password.encode()).hexdigest(),
                    "date": datetime.now(pytz.UTC)
                }
            }))
            flash(
                "You have successfully created your account, you can now create online elections!", "success")
        return redirect(url_for("register"))

    return render_template("register.html")


if __name__ == "__main__":
    app.run(debug=True)
```

We first made a query to Fauna to check if the account we are trying to create currently exists with the `get` method of the FQL client which fetches any database entry that matches the username we provided using the Fauna index we created earlier.

```Python
client.query(q.get(q.match(q.index(index_name), search_query)))
```

We also used the `create` method of the FQL client to store the `username` and `password` provided by the user along with `account creation date` in our database collection.

```Python
client.query(q.create(q.collection(collection_name), data))
```

When we run our server again then submit the form, we should get a response similar to the image below:

![](https://dev-to-uploads.s3.amazonaws.com/i/pt43yi3782q0zz3wxhsy.png)![](https://dev-to-uploads.s3.amazonaws.com/i/b2j5hkp74voqcdrv3t0o.png)

## Step 4: Design Our Login Page

The login page is where users are provided with a form to authenticate their accounts on our application so they can manage their elections and create new ones.

Create a file named `login.html` which will be stored inside the templates folder and save the code below in it.

```html
{% extends "bootstrap/base.html" %}

{% block content %}
<div class="container">
  <div class="row justify-content-center">
    <div class="col-lg-12">
      <div class="jumbotron text-center">
        <h2>Online Election Voting with Python and Fauna</h2>
        <p>Login to Manage Your Elections and Create New Ones</p>
      </div>
    </div>

    <div class="col-lg-6">
      {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
      {% for category, message in messages %}
      <p class="text-{{ category }}">{{ message }}</p>
      {% endfor %}
      {% endif %}
      {% endwith %}
      <form method="POST">
        <div class="form-group">
          <label for="username">Username</label>
          <input type="text" class="form-control" id="username" name="username" placeholder="Enter Your Username" required>
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input type="password" class="form-control" id="password" name="password" placeholder="Enter Your Password" required>
        </div>
        <button type="submit" class="btn btn-primary">Authenticate Account</button>
      </form>
    </div>
  </div>
</div>
{% endblock %}
```

We also need to create a `login` route in our Flask app that will render the template with the login form. Add the following code below to your `app.py` file:

```Python
@app.route("/login/", methods=["GET", "POST"])
def login():
    return render_template("login.html")
```

When we run our `app.py` file we should get a response quite close to the image below when we open the `login` route in our browser:

![](https://dev-to-uploads.s3.amazonaws.com/i/t6lxleyzrp9ko960wz2g.png)

## Step 5: Build Our Login Logic

Now, we want to write a route that collects usernames and passwords from our form and verify the account exists and the provided information is correct. Update your `login` route with the code below:

```Python
@app.route("/login/", methods=["GET", "POST"])
def login():
    if "user" in session:
        return redirect(url_for("dashboard"))

    if request.method == "POST":
        username = request.form.get("username").strip().lower()
        password = request.form.get("password")

        try:
            user = client.query(
                q.get(q.match(q.index("users_index"), username)))
            if hashlib.sha512(password.encode()).hexdigest() == user["data"]["password"]:
                session["user"] = {
                    "id": user["ref"].id(),
                    "username": user["data"]["username"]
                }
                return redirect(url_for("dashboard"))
            else:
                raise Exception()
        except:
            flash(
                "You have supplied invalid login credentials, please try again!", "danger")
        return redirect(url_for("login"))

    return render_template("login.html")


@app.route("/dashboard/", methods=["GET"])
def dashboard():
    return "Hello World"
```

Like we did earlier, we made a query to Fauna to get any account with the username provided in the form, then we verified if the password is correct. If the password is correct, save the user details in our session, else tell the user their credentials are not valid.

## Step 6: Design Our Create Election Page

The Create Election page is where users are provided with a form to create new elections on our application.

Create a file named `create-election.html` which will be stored inside the templates folder and save the code below in it.

```html
{% extends "bootstrap/base.html" %}

{% block content %}
<div class="container">
  <div class="row justify-content-center">
    <div class="col-lg-12">
      <div class="jumbotron text-center">
        <h2>Online Election Voting with Python and Fauna</h2>
        <p>Create a New Election</p>
      </div>
    </div>

    <div class="col-lg-6">
      <form method="POST">
        <div class="form-group">
          <label for="title">Election Title</label>
          <input type="text" class="form-control" id="title" name="title" placeholder="Enter Election Title" required>
        </div>
        <div class="form-group">
          <label for="voting-options">Voting Options</label>
          <textarea class="form-control" rows="5" id="voting-options" name="voting-options" placeholder="Enter the next voting option on a new line" required></textarea>
        </div>
        <button type="submit" class="btn btn-primary">Create Election</button>
      </form>
    </div>
  </div>
</div>
{% endblock %}
```

We also need to create a route in our Flask app that will render the template with the `create_election` form. Before that, we need to create a decorator that only allows authenticated users to access certain routes on our application. Add the code below to your `app.py` file

```Python
from functools import wraps


def login_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if "user" not in session:
            return redirect(url_for("login"))
        return f(*args, **kwargs)

    return decorated
```

Now let’s add the route for creating election secured with our authentication wrapper

```Python
@app.route("/dashboard/create-election/", methods=["GET", "POST"])
@login_required
def create_election():
    return render_template("create-election.html")
```

When we run our `app.py` file we should get a response similar to the image below when we open the `create_election` route in our browser:

![](https://dev-to-uploads.s3.amazonaws.com/i/vffd1wxh04az6bpkpiqc.png)

## Step 7: Build Our Create Election Logic

Now, we want to write a route that collects election titles and options from our form and saves them in our database. To do this, we first need to create a Fauna collection as we did earlier named `elections` and an index to browse through our data.

![](https://dev-to-uploads.s3.amazonaws.com/i/7nvus5c9mbev0qa926ih.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/3wlvx5a45a3vk7nwyh27.png)

Add the code below to your `app.py` file

```Python
@app.route("/dashboard/create-election/", methods=["GET", "POST"])
@login_required
def create_election():
    if request.method == "POST":
        title = request.form.get("title").strip()
        voting_options = request.form.get("voting-options").strip()

        options = {}
        for i in voting_options.split("\n"):
            options[i.strip()] = 0

        election = client.query(q.create(q.collection("elections"), {
            "data": {
                "creator": session["user"]["id"],
                "title": title,
                "voting_options": options,
                "date": datetime.now(pytz.UTC)
            }
        }))
        return redirect(url_for("vote", election_id=election["ref"].id()))

    return render_template("create-election.html")


@app.route("/election/<int:election_id>/", methods=["GET", "POST"])
def election(election_id):
    return "You are accessing election ID " + str(election_id)
```

Here, we made a query to our Fauna database to create a database entry in our `elections` collection with the election details provided by the user. We also created a route that will be responsible for handling the viewing of an election and voting.

## Step 8: Design Our Vote Page

The Vote page is where users can view an election that has been created and cast their votes. Create a file named `vote.html` which will be stored inside the templates folder and save the code below in it.

```html
{% extends "bootstrap/base.html" %}

{% block content %}
<div class="container">
  <div class="row justify-content-center">
    <div class="col-lg-12">
      <div class="jumbotron text-center">
        <h2>Online Election Voting with Python and Fauna</h2>
        <p>Cast Your Votes for <strong>'{{ election.title }}'</strong> Election</p>
      </div>
    </div>

    <div class="col-lg-6">
      {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
      {% for category, message in messages %}
      <p class="text-{{ category }}">{{ message }}</p>
      {% endfor %}
      {% endif %}
      {% endwith %}
      <form method="POST">
        <div class="form-group">
          <label for="voting-options">Voting Options</label>
          <select class="form-control" id="voting-options" name="vote" required>
            {% for i in election.voting_options %}
            <option>{{ i }}</option>
            {% endfor %}
          </select>
        </div>
        <button type="submit" class="btn btn-primary">Cast Vote</button>
      </form>
    </div>
  </div>
</div>
{% endblock %}
```

Also, update your `app.py` file with the code below

```Python
@app.route("/election/<int:election_id>/", methods=["GET", "POST"])
def vote(election_id):
    try:
        election = client.query(
            q.get(q.ref(q.collection("elections"), election_id)))
    except:
        abort(404)

    return render_template("vote.html", election=election["data"])
```

When we run our server again, we should get a response quite like the image below when we open the `vote` route in our browser:

![](https://dev-to-uploads.s3.amazonaws.com/i/8kqpaa7olv4y3maa3emb.png)

## Step 9: Build Our Voting Logic

Now, we want to write a route that collects votes from our database then update the results. Update your `vote` route with the code below:

```Python
@app.route("/election/<int:election_id>/", methods=["GET", "POST"])
def vote(election_id):
    try:
        election = client.query(
            q.get(q.ref(q.collection("elections"), election_id)))
    except:
        abort(404)

    if request.method == "POST":
        vote = request.form.get("vote").strip()
        election["data"]["voting_options"][vote] += 1
        client.query(q.update(q.ref(q.collection("elections"), election_id), {
                     "data": {"voting_options": election["data"]["voting_options"]}}))
        flash("Your vote was successfully recorded!", "success")
        return redirect(url_for("vote", election_id=election_id))

    return render_template("vote.html", election=election["data"])
```

We made a query to Fauna to update our election results with the `update` method of the FQL client which updates any data that is previously stored in our database with the following code:

```Python
client.query(q.update(q.ref(q.collection(collection_name), document_id), new_data))
```

When we run our server again, we should get a response close to the image below when we cast our vote:

![](https://dev-to-uploads.s3.amazonaws.com/i/t7ay4jcjlwggecb08qju.png)

## Step 10: Build Our View Elections Page

The View Elections page is where election creators can view elections they have created and see their results. Create a file named `view-elections.html` which will be stored inside the templates folder and save the code below in it.

```html
{% extends "bootstrap/base.html" %}

{% block content %}
<div class="container justify-content-center">
  <div class="row">
    <div class="col-lg-12">
      <div class="jumbotron text-center">
        <h2>Online Election Voting with Python and Fauna</h2>
        <p>View Election Results</p>
      </div>
    </div>

    {% for i in elections %}
    <div class="col-lg-6">
      <div class="card">
        <div class="card-header">
          <h5 class="card-title">{{ i.data.title }}</h5>
        </div>
        <div class="card-body">
          {% for j in i.data.voting_options %}
          <p class="card-text">{{ "{} - {} vote(s)".format(j, i.data.voting_options[j]) }}</p>
          {% endfor %}
          <a href="{{ url_for('vote', election_id=i.ref.id()) }}" class="btn btn-primary" target="_blank">View Election</a>
        </div>
      </div>
    </div>
    {% endfor %}
  </div>
</div>
{% endblock %}
```

We will also be updating our `dashboard` route in our `app.py` file to fetch the elections data from Fauna then render it on the user interface.

```Python
@app.route("/dashboard/", methods=["GET"])
@login_required
def dashboard():
    elections = client.query(q.paginate(
        q.match(q.index("election_index"), session["user"]["id"])))

    elections_ref = []
    for i in elections["data"]:
        elections_ref.append(q.get(q.ref(q.collection("elections"), i.id())))

    return render_template("view-elections.html", elections=client.query(elections_ref))
```

Here, we made a query to Fauna to paginate all database entries in the `elections` collection that were created by the current user that is logged in. Then a second query to fetch the data in each of the references returned.

When we run our server again, we should get a response similar to the image below when we open the dashboard route (right after logging in).

![](https://dev-to-uploads.s3.amazonaws.com/i/jeqtfw5kjb24u73dphpm.png)

# Conclusion

In this article, we built an electoral voting application similar to [electionrunner.com](https://electionrunner.com) with Fauna's serverless database and Python. We saw how easy it is to integrate Fauna into a Python application and got the chance to explore some of its core features and functionalities.

The source code of our electoral voting application is available on [GitHub](https://github.com/LordGhostX/fauna-voting-app/). If you have any questions, don't hesitate to contact me on Twitter: [@LordGhostX](https://twitter.com/LordGhostX)