Poll Feature Review Blog

Purpose of My Individual Feature

  • Stores a person’s name and their interests (or any poll-related data)
  • Supports full CRUD operations through API endpoints
  • Purpose of the group project is to connect people through shared interests and connect people in this way

Using postman to show raw API request and RESTful response (error code(s) and JSON)

Create: Read: Update: Delete:

Using db_init, db_restore, db_backup to show tester data creation and data recovery.

Initialize Database

# model/poll.py

def initPolls():
    """
    Initialize the Poll table with default data.
    """
    polls = [
        Poll("Toby", "Jazz"),
        Poll("Niko", "Rock")
    ]
    for poll in polls:
        try:
            db.session.add(poll)
            db.session.commit()
            print(f"Added poll: {poll.name}")
        except Exception as e:
            db.session.rollback()
            print(f"Error adding poll: {poll.name} - {e}")

$ ./scripts/db_init.py
Warning, you are about to lose all data in the database!
Database backed up to instance/volumes/user_management_bak.db
All tables dropped.
Generating data.

...


Added poll: toby
Added poll: niko

Restore Database

# model/poll.py

@staticmethod
def restore(data):
    # Restore polls using a list of dictionaries.
    restored_polls = {}
    for poll_data in data:
        try:
            _ = poll_data.pop('id', None) # remove id column from poll_data
            name = poll_data.get("name", None)
            interests = poll_data.get("interests", None)

            poll_key = name
            poll = Poll.query.filter_by(name=name).first()
            if poll:
                poll.update(poll_data)
            else:
                poll = Poll(**poll_data)
                poll.create()

            restored_polls[poll_key] = poll
        except Exception as e:
            print(f"Error processing poll data: {poll_data} - {e}")
            continue

    return restored_polls

Backup Database

# main.py

def backup_database(db_uri, backup_uri):
    """Backup the current database."""
    if backup_uri:
        db_path = db_uri.replace('sqlite:///', 'instance/')
        backup_path = backup_uri.replace('sqlite:///', 'instance/')
        shutil.copyfile(db_path, backup_path)
        print(f"Database backed up to {backup_path}")
    else:
        print("Backup not supported for production database.")



def save_data_to_json(data, directory='backup'):
    if not os.path.exists(directory):
        os.makedirs(directory)
    for table, records in data.items():
        with open(os.path.join(directory, f'{table}.json'), 'w') as f:
            json.dump(records, f)
    print(f"Data backed up to {directory} directory.")

def backup_data():
    data = extract_data()
    save_data_to_json(data)
    backup_database(app.config['SQLALCHEMY_DATABASE_URI'], app.config['SQLALCHEMY_BACKUP_URI'])


Input/Output Requests

Formatting response data (JSON) from API into DOM:

<!-- navigation/worlds/polls.md -->

<table class="submit-answer-container">
    <thead>
        <tr>
            <th>Name</th>
            <th>Result</th>
        </tr>
    </thead>
    <tbody id="poll-data">
        <!-- Data will be dynamically inserted here -->
    </tbody>
</table>


<script type="module">
    import { pythonURI, fetchOptions } from "/adi_student//assets/js/api/config.js";

    try {
        var response = await fetch(`${pythonURI}/api/poll`, fetchOptions);
    }
    catch (error) {
        console.error('There has been a problem with your fetch operation:', error);
    }

    var data = await response.json();

    const pollData = document.getElementById('poll-data');
    pollData.innerHTML = '';

    data.forEach(item => {
        const row = document.createElement('tr');

        const nameCell = document.createElement('td');
        nameCell.textContent = item.name;

        const interestsCell = document.createElement('td');
        interestsCell.textContent = item.interests;

        row.appendChild(nameCell);
        row.appendChild(interestsCell);
        pollData.appendChild(row);
    });

</script>

Discuss queries from database where you extract a Python List (rows). Mention how these queries are provide by a 3rd. party library.

This code snippet uses SQLAlchemy, which makes it easier to use OOP to communicate with a database. It also uses methods in a class to work with columns in the database using CRUD operations.

class PollAPI:
    """
    Define the API endpoints for the Poll model.
    """

    class _Read(Resource): # R = Read
        """
        GET request handler: Read all polls.
        """
        @token_required()
        def get(self):
            try:
                # Retrieve all poll records
                polls = Poll.query.all()
                poll_list = []
                for poll in polls:
                    poll_list.append(poll.read())
                return jsonify(poll_list)
            except Exception as e:
                print(f"Poll Read Error: {e}")
                return {'message': f'Error retrieving poll data: {str(e)}'}, 500

    class _Create(Resource): # C = Create
        """
        POST request handler: Create a new poll.
        """
        @token_required()
        def post(self):
            try:
                data = request.get_json()
                if not data:
                    return {'message': 'No input data provided'}, 400

                name = data.get('name')
                interests = data.get('interests')

                # Basic validation
                if not name or interests is None:
                    return {'message': 'name and interests fields are required.'}, 422

                # Create and save the new Poll
                new_poll = Poll(name, interests)
                new_poll.create()

                return {'message': 'Poll data inserted successfully'}, 201

            except KeyError as e:
                return {'message': f'Missing field: {str(e)}'}, 400
            except Exception as e:
                print(f"Poll Create Error: {e}")
                return {'message': f'Error inserting poll data: {str(e)}'}, 500

    class _Update(Resource): # U = Update
        @token_required()
        def put(self):
            try:
                data = request.get_json()
                if not data:
                    return {'message': 'No input data provided'}, 400

                poll_id = data.get('id')
                if not poll_id:
                    return {'message': 'Poll ID is required.'}, 400

                poll = Poll.query.get(poll_id)
                if not poll:
                    return {'message': 'Poll not found.'}, 404

                name = data.get('name')
                interests = data.get('interests')

                if name:
                    poll.name = name
                else:
                    poll.name = poll.name

                if interests is not None:
                    poll.interests = interests
                else:
                    poll.interests = poll.interests

                poll.update({
                    "name": poll.name,
                    "interests": poll.interests
                })
                return {'message': 'Poll updated successfully'}, 200

            except KeyError as e:
                return {'message': f'Missing field: {str(e)}'}, 400
            except Exception as e:
                print(f"Poll Update Error: {e}")
                return {'message': f'Error updating poll: {str(e)}'}, 500

    class _Delete(Resource): # D = Delete
        @token_required()
        def delete(self):
            try:
                data = request.get_json()
                if not data:
                    return {'message': 'No input data provided'}, 400

                poll_id = data.get('id')
                if not poll_id:
                    return {'message': 'Poll ID is required.'}, 422

                poll = Poll.query.get(poll_id)
                if not poll:
                    return {'message': 'Poll not found.'}, 404

                poll.delete()
                return {'message': 'Poll deleted successfully'}, 200

            except KeyError as e:
                return {'message': f'Missing field: {str(e)}'}, 400
            except Exception as e:
                print(f"Poll Delete Error: {e}")
                return {'message': f'Error deleting poll: {str(e)}'}, 500

# Map the resources to their endpoints
api.add_resource(PollAPI._Read, '/poll')
api.add_resource(PollAPI._Create, '/poll')
api.add_resource(PollAPI._Update, '/poll')
api.add_resource(PollAPI._Delete, '/poll')

For the GET function:

Sequencing: retrieves poll data, creates empty list, iterates through poll data and appends to end of list, returns JSON output.
Selection: The try-except code block selects the error if an exception occurs and prints the error and returns output.
Iteration: Iteration through a for loop to add poll data into an empty array

For the PUT function:

JSON parameters are taken and converted into python list for usability and processing. The GET function returns a JSON response using jsonify which converts the list of poll data into JSON format.

Function to add a new poll

async function addPoll() {
    const name = document.getElementById('addPollName').value;
    const interests = document.getElementById('addPollInterests').value;
    const payload = { name, interests };

    try {
        const response = await fetch(`${pythonURI}/api/poll`, {
            ...fetchOptions,
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });
        if (!response.ok) {
            throw new Error('Network response was not ok ' + response.statusText);
        }
        const data = await response.json();
        console.log('Poll added:', data);
        location.reload();
    } catch (error) {
        console.error('Error adding poll:', error);
    }
}

Function to update an existing poll

async function updatePoll() {
    const id = document.getElementById('updatePollId').value;
    const name = document.getElementById('updatePollName').value;
    const interests = document.getElementById('updatePollInterests').value;
    const payload = { id, name, interests };

    try {
        const response = await fetch(`${pythonURI}/api/poll`, {
            ...fetchOptions,
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });
        if (!response.ok) {
            throw new Error('Network response was not ok ' + response.statusText);
        }
        const data = await response.json();
        console.log('Poll updated:', data);
        location.reload();
    } catch (error) {
        console.error('Error updating poll:', error);
    }
}

Function to delete a poll

async function deletePoll() {
    const id = document.getElementById('deletePollId').value;
    const payload = { id };

    try {
        const response = await fetch(`${pythonURI}/api/poll`, {
            ...fetchOptions,
            method: 'DELETE',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });
        if (!response.ok) {
            throw new Error('Network response was not ok ' + response.statusText);
        }
        const data = await response.json();
        console.log('Poll deleted:', data);
        location.reload();
    } catch (error) {
        console.error('Error deleting poll:', error);
    }
}

Function to fetch and display poll data

async function fetchPollData() {
    try {
        const response = await fetch(`${pythonURI}/api/poll`, fetchOptions);
        if (!response.ok) {
            throw new Error('Network response was not ok ' + response.statusText);
        }
        const data = await response.json();
        const pollData = document.getElementById('poll-data');
        pollData.innerHTML = '';

        data.forEach(item => {
            const row = document.createElement('tr');

            const nameCell = document.createElement('td');
            nameCell.textContent = item.name;

            const interestsCell = document.createElement('td');
            interestsCell.textContent = item.interests;

            row.appendChild(nameCell);
            row.appendChild(interestsCell);
            pollData.appendChild(row);
        });
    } catch (error) {
        console.error('Error fetching poll data:', error);
    }
}

Call the function and display the data on page load

document.addEventListener("DOMContentLoaded", function() {
    fetchPollData();
  });

Call/Request method with algorithm:

Frontend fetches from backend using PythonURI then takes then, depending on the method being used, either outputs it to DOM or uses the backend to add to database. During error conditions, the use of alert functions has been minimized to ensure a smooth UI when an exception occurs.

In conclusion, the Poll feature effectively demonstrates the integration of CRUD operations, database management, and frontend-backend communication to create a seamless user experience.