Skip to the content.

Gradelog Blog

Overview of Website

Cantella is a site that was created with the intention to give students various resources in which they could use in order to succeed in school. We have flashcards, quizzes, competitive leaderboards, a way to keep track of your grades and study sessions, and more.

Individual Feature

The individual feature in which I have been tasked to create was a program that allows a user to input their grade for any given subject and additional notes to describe why or for what assignment they received this grade for.

Input / Output Requests

The grade log feature can be accessed by the following path: /cantella_frontend/gradelog.

Here you can input data through the frontend which will subsequently be stored into the backend.

Frontend Demo

Frontend View:

Backend View:

Postman

In order to first retrieve raw data from Postman, we first have to send a POST request to localhost:8887/api/authenticate and input the data:

{
  "uid": "niko",
  "password": "123niko"
}

Sending this request will output: Authentication for niko successful.

This signifies that we are now logged into the site and hence have access to the grade log feature; a feature only accessible when a user is logged in.

Once logged in, one should now send a GET request to localhost:8887/api/gradelog

In the case of the example above, this would be the output:

[
    {
        "date": "2025-01-27 00:06:57",
        "grade": 100.0,
        "id": 1,
        "notes": "Assignment was first Unit Test. It was really easy.",
        "subject": "Math",
        "user_id": 3
    }
]

Database Management

Having the ability to clear, backup, and restore a database are useful, if not essential, for a functioning database to have. The following will demonstrate the usage of db_init (for clearing a database), db_backup (for backing up a database), and db_restore (for restoring a database).

db_backup (Backup)

To run db_backup, once must go to their backend repository and run the following in their VS Code terminal: scripts/db_backup.py. This “command” is not so much a command as it is a path. When we enter the path to the db_backup file, the terminal assumes we want to run the file and so it does. Inputting this filepath outputs the following:

Data backed up to the backup directory.
Database backed up to instance/volumes/user_management_bak.db

This output is telling us that all the entries in our main database, user_management.db, have been stored to the backup database, user_management_bak.db. Now when we clear the database with db_init or sending DELETE requests from the frontend or postman, all our data will still be protected and stored in our backup database, regardless of the changes made to the main one.

db_init (Clear)

To run db_init, once must go to their backend repository and run the following in their VS Code terminal: scripts/db_init.py. To reiterate from before, we are essentially typing in the path to db_init as it not invoke the running of the file. Inputting this filepath outputs the following:

Warning, you are about to lose all data in the database!
Do you want to continue? (y/n)

In our case, we type y, as we want to continue and clear all the data in the database.

Doing so outputs:

Database backed up to instance/volumes/user_management_bak.db
All tables dropped.
Generating data.

This output is telling us that all tables or, more pragmatically speaking, all our data in our databases, have been cleared. Additionally, new data is being generated, assuming you have default data that needs to be automatically added. When it comes to default data, there can be extra output depending if one has added such or not.

Now that we have backed up our data and cleared it from the main database, it’s time to restore it back.

db_restore (Restore)

Same song, different tune. Input scripts/db_restore.py to the terminal and you will be outputted with:

Data restored to the new database.

The data that we backed up to user_management_bak.db is now copied back into user_management.db, allowing one to retrieve any data they may have deleted or lost.

List Requests

JSON to DOM (HTML)

In order to first convert JSON into DOM (aka into an HTML format), we first have to fetch that stored JSON data from the backend. The code below outlines how this is done:

// Filepath: /cantela_frontend/navigation/gradelog.md

// Function to load grade logs from the backend
async function loadGradeLogs() {
    try {
      // Make a GET request to the backend API to fetch grade logs
      const response = await fetch('http://127.0.0.1:8887/api/gradelog', {
        credentials: 'include', // Include credentials for authentication
      });

      // Check if the response is successful
      if (response.ok) {
        // Parse the JSON data from the response
        const logs = await response.json();
        // Call the function to display the grade logs, passing the JSON data
        displayGradeLogs(logs);
      } else {
        // Log an error message if the response is not successful
        console.error('Failed to load grade logs.');
      }
    } catch (error) {
      // Log any errors that occur during the fetch operation
      console.error('Error:', error);
    }
  }

The last thing that is done, given the response is successful, is passing that now retrieved JSON data in the next function, displayGradeLogs(logs);

// Filepath: /cantela_frontend/navigation/gradelog.md
// Function to display grade logs in the DOM
  function displayGradeLogs(logs) {
    // Clear the container that holds the grade logs
    gradeLogContainer.innerHTML = '';

    // Check if there are no logs to display
    if (logs.length === 0) {
      // Display a message indicating no grade logs were found
      gradeLogContainer.innerHTML = '<p>No grade logs found.</p>';
      return;
    }

    // Group logs by subject
    const groupedLogs = logs.reduce((acc, log) => {
      // If the subject is not already a key in the accumulator, add it
      if (!acc[log.subject]) {
        acc[log.subject] = [];
      }
      // Push the current log into the array for its subject
      acc[log.subject].push(log);
      return acc;
    }, {});

    // Iterate over each subject in the grouped logs
    Object.keys(groupedLogs).forEach((subject) => {
      // Create a container element for the subject group
      const subjectElement = document.createElement('div');
      subjectElement.className = 'subject-group'; // Add a class for styling
      subjectElement.innerHTML = `<h3>${subject}</h3>`; // Add the subject title

      let totalGrades = 0; // Initialize variables for calculating average grade
      let gradeCount = 0;

      // Iterate through the logs for the current subject
      groupedLogs[subject].forEach((log) => {
        // Create a container element for the individual grade log
        const logElement = document.createElement('div');
        logElement.className = 'grade-log'; // Add a class for styling

        // Convert the JSON data into DOM elements with structured information
        logElement.innerHTML = `
          <p><strong>Grade:</strong> <span class="grade">${log.grade}</span></p>
          <p>${log.notes}</p>
          <p><small>${new Date(log.date).toLocaleString()}</small></p>
          <button class="edit-log-btn" data-id="${log.id}">Edit</button>
          <button class="delete-log-btn" data-id="${log.id}">Delete</button>
        `;
        // Append the log element to the subject group container
        subjectElement.appendChild(logElement);

        // Accumulate the total grades and count for calculating the average
        totalGrades += parseFloat(log.grade);
        gradeCount++;
      });

There is of lot of fluff in this code, but the main thing that one should focus their attention is where that JSON data is now being converted to DOM elements.

Queries

In this snippet of code, we are defining how the API will retrieve the grade logs. This is done by using the third party SQL .query method that allows us to essentially send a request to the database in order to extract the grade logs which are stored in a list. Once this is done, the list of the gradelog instances are converted to a list of dictionaries as to establish a key value pair for each of the respective categories such as grade and subject that is provided by Gradelog.

# Filepath: cantela_backend/api/gradelog.py
class GradelogAPI:
    class _CRUD(Resource):
        @token_required() # Authentication token
        def get(self):
            """Get all grade entries for the current user."""
            current_user = g.current_user
            # Query the database to get all grade logs for the current user
            all_gradelogs = GradeLog.query.filter_by(user_id=current_user.id).all()
            # Convert the list of GradeLog instances to a list of dictionaries
            gradelog = [log.read() for log in all_gradelogs]
            return jsonify(gradelog)

As mentioned before The get method and query method are both functions by third party SQL libraries, making it relatively easier to interact with databases.

CRUD Methods

These methods provide us the most essential tools when manipulating a database. The ability to create, read, update, and delete the data to which is stored in such a database.

# Filepath: cantela_backend/model/gradelog.py
def create(self):
    """
    Add the current GradeLog instance to the database.
    
    Returns:
        self: The created GradeLog instance if successful, None otherwise.
    """
    try:
        db.session.add(self)
        db.session.commit()
        return self
    except IntegrityError:
        db.session.rollback()
        return None

def read(self):
    """
    Convert the GradeLog instance to a dictionary.
    
    Returns:
        dict: A dictionary representation of the GradeLog instance.
    """
    return {
        "id": self.id,
        "user_id": self.user_id,
        "subject": self.subject,
        "grade": self.grade,
        "notes": self.notes,
        "date": self.date.strftime('%Y-%m-%d %H:%M:%S')
    }

def update(self, data):
    """
    Update the GradeLog instance with new data.
    
    Args:
        data (dict): A dictionary containing the new data for the GradeLog instance.
    
    Returns:
        self: The updated GradeLog instance.
    """
    for key, value in data.items():
        if key == "subject":
            self.subject = value
        if key == "grade":
            self.grade = value
        if key == "notes":
            self.notes = value
    db.session.commit()
    return self

def delete(self):
    """
    Delete the current GradeLog instance from the database.
    """
    db.session.delete(self)
    db.session.commit()

Algorithmic Code Request

Methods: The methods in this case would be the various CRUD methods defined in the api file

Sequencing: The steps in this method are executed in a specific order:

  1. Retrieve the authenticated user.
  2. Query the database for grade logs.
  3. Initialize an empty list.
  4. Return a JSON response at the end.

Selection: The selection happens implicitly when filtering grade logs by user_id: all_gradelogs = GradeLog.query.filter_by(user_id=current_user.id).all()

Iteration:

for log in all_gradelogs:
    gradelog.append(log.read())

Jsonify: The list of grade log dictionaries are returned as JSON response

# Filepath: cantela_backend/api/gradelog.py

# Define the GradelogAPI class
class GradelogAPI:
    # Define an inner class _CRUD that inherits from Resource
    class _CRUD(Resource):
        @token_required()
        def post(self):
            """
            Create a new grade entry.
            
            This method handles POST requests to create a new grade log entry.
            It requires the user to be authenticated and expects a JSON payload
            with 'subject' and 'grade' fields. Optionally, 'notes' can also be provided.
            """
            # Get the current authenticated user
            current_user = g.current_user
            # Get the JSON data from the request body
            data = request.get_json()

            # Validate input: Check if 'subject' and 'grade' are present in the data
            if not data or 'subject' not in data or 'grade' not in data:
                return {"message": "Subject and grade are required"}, 400
            
            # Create a new GradeLog instance with the provided data
            new_grade_log = GradeLog(
                user_id=current_user.id,
                subject=data['subject'],
                grade=data['grade'],
                notes=data.get('notes', '')  # Use an empty string if 'notes' is not provided
            )
            # Save the new grade log to the database
            created_log = new_grade_log.create()
            if created_log:
                # Return a success message with the ID of the created grade log
                return {"message": "Grade logged successfully", "grade_log_id": created_log.id}, 201
            # Return an error message if the grade log could not be created
            return {"message": "Failed to create grade log"}, 500

        @token_required()
        def get(self):
            """
            Get all grade entries for the current user.
            
            This method handles GET requests to retrieve all grade log entries
            for the authenticated user. It returns a JSON array of grade logs.
            """
            # Get the current authenticated user (Sequencing)
            current_user = g.current_user
            # Query the database to get all grade logs for the current user (Sequencing)
            all_gradelogs = GradeLog.query.filter_by(user_id=current_user.id).all()
            # Initialize an empty list to hold the grade logs (Sequencing)
            gradelog = []

            # Iterate over the list of GradeLog instances (Iteration)
            for log in all_gradelogs:
                # Convert each GradeLog instance to a dictionary and append to the list (Iteration)
                gradelog.append(log.read())

            # Return the list of grade logs as a JSON response (Sequencing)
            return jsonify(gradelog)


        @token_required()
        def put(self):
            """
            Update an existing grade log.
            
            This method handles PUT requests to update an existing grade log entry.
            It requires the user to be authenticated and expects a JSON payload
            with 'id' and the fields to be updated ('subject', 'grade', 'notes').
            """
            # Get the JSON data from the request body
            data = request.get_json()
            # Validate input: Check if 'id' is present in the data
            if not data or 'id' not in data:
                return {"message": "Grade Log ID is required"}, 400
            # Query the database to get the grade log by ID
            grade_log = GradeLog.query.get(data['id'])
            # Check if the grade log exists and belongs to the current user
            if not grade_log or grade_log.user_id != g.current_user.id:
                return {"message": "Grade Log not found or unauthorized"}, 404
            # Update the grade log with the new data
            updated_log = grade_log.update(data)
            # Return the updated grade log as a JSON response
            return updated_log.read(), 200

        @token_required()
        def delete(self):
            """
            Delete a grade log.
            
            This method handles DELETE requests to delete an existing grade log entry.
            It requires the user to be authenticated and expects the 'id' of the grade log
            to be provided as a query parameter.
            """
            # Get the grade log ID from the query parameters
            grade_log_id = request.args.get('id')
            # Validate input: Check if the grade log ID is provided and is a digit
            if not grade_log_id or not grade_log_id.isdigit():
                return {"message": "A valid Grade Log ID is required"}, 400

            # Query the database to get the grade log by ID
            grade_log = GradeLog.query.get(int(grade_log_id))
            # Check if the grade log exists and belongs to the current user
            if not grade_log or grade_log.user_id != g.current_user.id:
                return {"message": "Grade Log not found or unauthorized"}, 404

            try:
                # Delete the grade log from the database
                grade_log.delete()
                # Return a success message
                return {"message": "Grade Log deleted successfully"}, 200
            except Exception as e:
                # Return an error message if an exception occurs
                return {"message": f"An error occurred: {str(e)}"}, 500

# Register the _CRUD resource with the API
api.add_resource(GradelogAPI._CRUD, '/gradelog')

Call to Algorithm Request

// Filepath: /cantela_frontend/navigation/gradelog.md
try {
      const response = await fetch('http://127.0.0.1:8887/api/gradelog', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
        credentials: 'include',
      });

      if (response.ok) {
        alert('Grade Log added successfully!');
        gradeLogForm.reset();
        loadGradeLogs(); // Refresh logs
      } else {
        const errorText = await response.text();
        alert('Failed to add grade log: ' + errorText);
      }
    } catch (error) {
      console.error('Error:', error);
      alert('An error occurred while adding the grade log.');
    }