Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem Set 2 Q1: Circle Calculator in Flask app #11

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Data Structures & Algorithms Spring 2024
# Problem Set 2
# Lonny Chen, 216697
# circle.py

import math

class Circle:
rounding_int_default = 2

def __init__(self, radius):
'''Constructs Circle with instance attributes.'''
self.radius = radius

def area(self, rounding_int=rounding_int_default):
'''Returns area of this Circle with input rounding.'''
result = math.pi * (self.radius ** 2)
if rounding_int == 0:
rounded_result = round(result)
else:
rounded_result = round(result, rounding_int)
return rounded_result

def perimeter(self, rounding_int=rounding_int_default):
'''Returns perimeter of this Circle with input rounding.'''
result = 2 * math.pi * self.radius
if rounding_int == 0:
rounded_result = round(result)
else:
rounded_result = round(result, rounding_int)
return rounded_result
69 changes: 63 additions & 6 deletions flask_app.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
from flask import Flask, render_template, request
# Data Structures & Algorithms Spring 2024
# Problem Set 2
# Lonny Chen, 216697
# flask_app.py

from flask import Flask, render_template, request
from helper import perform_calculation, convert_to_float
from circle import Circle

app = Flask(__name__) # create the instance of the flask class


# route() decorator tells Flask: base URL triggers index "home page"
@app.route('/')
@app.route('/home')
def home():
'''Home page template'''
return render_template('home.html')


# route() decorator tells Flask: '/calculate' URL triggers calculation
@app.route('/calculate', methods=['GET', 'POST']) # associating the GET and POST method with this route
def calculate():
'''Calculation of mathematical operation from HTML form values, includes input error checking.'''

if request.method == 'POST':
# using the request method from flask to request the values that were sent to the server through the POST method
value1 = request.form['value1']
value2 = request.form['value2']
operation = str(request.form['operation'])
rounding = request.form['rounding']

# make sure the input is one of the allowed inputs (not absolutely necessary in the drop-down case)
if operation not in ['add', 'subtract', 'divide', 'multiply']:
Expand All @@ -27,14 +36,62 @@ def calculate():
try:
value1 = convert_to_float(value=value1)
value2 = convert_to_float(value=value2)
rounding_int = int(float(rounding))
assert rounding_int >= 0
except ValueError:
return render_template('calculator.html', printed_result="Cannot perform operation with this input")
return render_template('calculator.html', printed_result="Entered values should be numbers!")
except AssertionError:
return render_template('calculator.html', printed_result="Rounding should not be negative!")

try:
result = perform_calculation(value1=value1, value2=value2, operation=operation)
return render_template('calculator.html', printed_result=str(result))
result = perform_calculation(value1=value1, value2=value2, operation=operation, rounding_int=rounding_int)
printed_result = f'The result of {value1} {operation} {value2} is: {result} (rounded to {rounding_int} decimals)'
return render_template('calculator.html', printed_result=printed_result)

except ZeroDivisionError:
return render_template('calculator.html', printed_result="You cannot divide by zero")

return render_template('calculator.html')

# route() decorator tells Flask: '/circle' URL triggers circle calculation
@app.route('/circle', methods=['GET', 'POST']) # associating the GET and POST method with this route
def circle():
'''Calculation of circle calculation from HTML form values, includes input error checking.'''

if request.method == 'POST':
# using the request method from flask to request the values that were sent to the server through the POST method
radius = request.form['radius']
operation = str(request.form['operation'])
rounding = request.form['rounding']

# make sure the input is one of the allowed inputs (not absolutely necessary in the drop-down case)
if operation not in ['perimeter', 'area']:
return render_template('circle.html',
printed_result='Operation must be one of "perimeter" or "area".')

try:
radius = convert_to_float(value=radius)
rounding = convert_to_float(value=rounding)
assert radius >= 0 and rounding >= 0
except ValueError:
return render_template('circle.html', printed_result="Entered values should be numbers!")
except AssertionError:
return render_template('circle.html', printed_result="Radius or rounding should not be negative!")

# Circle class calculations
rounding_int = int(rounding)
circle = Circle(float(radius))
if operation == 'perimeter':
result = circle.perimeter(rounding_int)
else:
result = circle.area(rounding_int)
printed_result = f'For a circle with radius of {radius}, the {operation} is: {result} (rounded to {rounding_int} decimals)'

return render_template('circle.html', printed_result=printed_result)

return render_template('circle.html')

# So web app can be refreshed in browser after changes
# Can just enter on command line: "python flask_app.py"
if __name__ == '__main__':
app.run(debug=True)
18 changes: 14 additions & 4 deletions helper.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# create helper functions for calculations
# Data Structures & Algorithms Spring 2024
# Problem Set 2
# Lonny Chen, 216697
# helper.py

# create helper functions for calculations

def perform_calculation(value1: float, value2: float, operation: str) -> float:
def perform_calculation(value1: float, value2: float, operation: str, rounding_int: int) -> float:
"""
Perform a mathematical operation on two values.

Parameters:
value1 (float): The first value.
value2 (float): The second value.
operation (str): The operation to perform. Can be 'add', 'subtract', 'divide', or 'multiply'.
rounding_int (int): Number of decimal places to round result to.

Returns:
float: The result of the operation.
float or int: The result of the operation.

Raises:
ZeroDivisionError: If attempting to divide by zero.
Expand All @@ -25,7 +30,12 @@ def perform_calculation(value1: float, value2: float, operation: str) -> float:
else:
result = value1 * value2

return result
if rounding_int == 0:
rounded_result = round(result)
else:
rounded_result = round(result, rounding_int)

return rounded_result


def convert_to_float(value: str) -> float:
Expand Down
7 changes: 5 additions & 2 deletions templates/calculator.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends 'layout.html' %}
{% block content %}
<h1>Calculator</h1>
<h1>General Calculator</h1>
<form method="post">
<input type="text" name="value1" placeholder="Enter the first number" required="required" />
<input type="text" name="value2" placeholder="Enter the second number" required="required" />
Expand All @@ -9,10 +9,13 @@ <h1>Calculator</h1>
<select id="operation" name="operation">
<option value="add">Add</option>
<option value="subtract">Subtract</option>
<option value="divide">Divide</option>
<option value="multiply">Multiply</option>
<option value="divide">Divide</option>
</select>

<label for="rounding">Round to decimals</label>
<input type="text" name="rounding" value="2" required="required" />

<button type="submit">Calculate</button>
</form>

Expand Down
23 changes: 23 additions & 0 deletions templates/circle.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends 'layout.html' %}
{% block content %}
<h1>Circle Calculator</h1>
<form method="post">
<input type="text" name="radius" placeholder="Enter the circle's radius" required="required" />

<label for="operation">Operation</label>
<select id="operation" name="operation">
<option value="perimeter">Perimeter</option>
<option value="area">Area</option>
</select>

<label for="rounding">Round to decimals</label>
<input type="text" name="rounding" value="2" required="required" />

<button type="submit">Calculate</button>
</form>

<br>

{{ printed_result }}

{% endblock %}
1 change: 1 addition & 0 deletions templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ <h1 class="logo"><a href="{{ url_for('home') }}">Home</a></h1>
<strong><nav>
<ul class="menu">
<li><a href="{{ url_for('calculate') }}">General Calculator</a></li>
<li><a href="{{ url_for('circle') }}">Circle Calculator</a></li>
</ul>
</nav></strong>
</div>
Expand Down
67 changes: 67 additions & 0 deletions test_circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Data Structures & Algorithms Spring 2024
# Problem Set 2
# Lonny Chen, 216697
# test_circle.py
# Usage: pytest test_circle.py or Run in PyCharm IDE

from circle import Circle
import math
import random

def check_circle_area(radius, rounding):
'''A Circle with input radius returns the expected area with input rounding.'''
circle = Circle(radius)
expected = round(math.pi * (radius ** 2), rounding)
result = circle.area(rounding)
print(f'Testing area of circle with radius {radius}, rounding {rounding}')
print(f'Expected: {expected}')
print(f'Result: {result}')
assert expected == result

def check_circle_perimeter(radius, rounding):
'''A Circle with input radius returns the expected perimeter with input rounding.'''
circle = Circle(radius)
expected = round(2 * math.pi * radius, rounding)
result = circle.perimeter(rounding)
print(f'Testing perimeter of circle with radius {radius}, rounding {rounding}')
print(f'Expected: {expected}')
print(f'Result: {result}')
assert expected == result

def generate_random_seed(min, max):
'''Generate a random seed between input min and max.'''
seed = random.randint(min, max)
print(f'\nRandom seed is: {seed}') #note for reproducability
random.seed(seed)

def test_directed_circle_area(radius_list = [0, 1, 2, math.pi, 10, 100, 1e6]):
'''Test Circle::area() with interesting radius values.'''
generate_random_seed(1, 100)
for r in radius_list:
rounding = random.randint(0, 10)
check_circle_area(r, rounding)

def test_directed_circle_perimeter(radius_list = [0, 1, 2, math.pi, 10, 100, 1e6]):
'''Test Circle::perimeter() with interesting radius values.'''
generate_random_seed(1, 100)
for r in radius_list:
rounding = random.randint(0, 10)
check_circle_perimeter(r, rounding)

def test_random_circle_area(iterations=10):
''' Test Circle::area() with random radius and rounding for input number of iterations.'''
generate_random_seed(1, 100)
for i in range(0, iterations):
print(f'\nIteration #{i}')
radius = random.uniform(0,1000)
rounding = random.randint(0, 10)
check_circle_area(radius, rounding)

def test_random_circle_perimeter(iterations=10):
''' Test Circle::perimeter() with random radius and rounding for input number of iterations.'''
generate_random_seed(1, 100)
for i in range(0, iterations):
print(f'\nIteration #{i}')
radius = random.uniform(0,1000)
rounding = random.randint(0, 10)
check_circle_perimeter(radius, rounding)