Skip to content

a-tsagkalidis/swappr

Repository files navigation

Swappr

Author: Argyrios Tsagkalidis

Location: Thessaloniki, Greece

License: BSD-3-Clause

Video Demo: CS50X Final Project - Swappr

Description

Swappr is a web app project that helps the users to swap their rented houses.

The users create an account, uploads their rented house's specifications, and explore potential matches for a hassle-free swap experience.

The app uses an intelligent algorithm that suggests compatible house swap options, ensuring that the user's preferences align with potential matches. The algorithm uses a matching score based on house characteristics, house location, and desired move destination.

Running the app

Swappr repo comes with no .db or .log files. These necessities will be created on program initialization. To initiate swappr run:

python app.py

But before you run the program for the first time, make sure that you have created a virtual environment and have installed all Python dependencies.

Create a virtual environment

Before you run Swappr it is recommended to create a virtual environment where all Python dependencies will be installed. While in working directory run:

python -m venv .venv

Notice that a .venv folder has been created.

Install Python dependencies

After your venv creation, you should install all swappr Python dependencies. While in working directory run:

pip install -r requirements.txt

Argument parser

Swappr comes with an embedded argument parser and developers can check its options using python app.py -h. Optinal arguments are:

  • -d or --debug enables Flask's embedded debug mode for testing purposes
  • -b or --backup runs automated backup routines for the database and log files
  • -l or --limiter enables a request limiter to protect the backend from spam
  • -p or --premademockups inserts a premade JSON of mockup users and submitted houses into the database. The premade mockups are tailored in such way so that developers can test the matching algorithm for every probable location/desired move location scenario
  • -m MOCKUPSGEN MOCKUPSGEN or --mockupsgen MOCKUPSGEN MOCKUPSGEN creates mockup users and submitted houses to test the matching algorithm. First integer is the number of users to be created, while the second is the number of the submitted houses. Submitted houses can't exceed the number of the mockup users - if so the program will limit submitted houses equal to users. The higher the number of mockups created, the higher the chance of catching a matching house during the test. Recommended 3000+ Mockups

Backup

Running app.py with -b or --backup argument initiates a simple backup functionality by triggering fbak.sh file. Doing two directories are created if not already present. First is bak.db and the second is bak.log. In there swappr.db and app.log are backed up into subfolders named by datetime logic. Backup functionality is usable only once per minute.

Developing tools

Swappr uses the following programming languages, frameworks, and dev tools:

  • Python3 and Flask mini framework package for the backend
  • HTML/CSS Bootstrap 5.2/JavaScript for the frontend
  • Jinja2 for the HTML templates
  • AJAX for asynchronous request logic
  • SQLite3 for the database handling
  • Bash for backup functionalities
  • noUiSlider JavaScript package to control input values for searching filters
  • jQuery.validate package for extra frontend validation

Frontend

Frontend convention relies on HTML Jinja2 templates that are extensions of the prototype layout.html file, as well as in JavaScript functionalities from script.js file and CSS styling from styles.css.

Frontend also relies on the following list of well known JavaScript libraries:

  • Bootstrap 5.2
  • jQuery 3.7.1
  • jQuery Validation v1.19.5
  • Popper
  • noUiSlider

Backend

Backend convention relies on splitted .py files (aka modules) where the proper code for every related section is contained. All modules and their functions are imported in a main module named as swapprfunctions which in turn is imported in app.py. For more information regarding backend functionality check section Custom modules, but first you should read some information about app.py - the main file where Flask framework get initialized.

app.py

This is the main file where Swappr Initialization and the Route Creation for all sections take place.

Swappr Initialization

When app.py initiates the following functionalities take action:

  • Configures a logger using loguru library
  • Checks for given arguments such as --backup, --limiter, and/or --premademockups/--mockupsgen MOCKUPSGEN MOCKUPSGEN to call corresponding functions.
  • Checks if SQL database is present or else it creates it with the proper schema in swappr.db
  • Checks if locations table in swappr.db matches with all the data in locations.json file. If new locations are present in the JSON file, then they are imported as new entries in swappr.db locations table

Route Creation

Most routes have their corresponding .py file where their dependent functions are present. For instance /search route calls some functions that are imported from fsearch.py, while /signup route draws functions from fsignup.py, and so on.

Validation

Swappr uses validation logic for whatever input data the user's may request. Validation takes place in frontend using browser's embedded validation, jQuery Validation Plugin methods, and custom JavaScript functions, while backend is being protected by custom Python functions.

Proper backend error handling prevents Swappr from crushes, where the errors are logged thanks to loguru Python package.

Limiter

Running app.py with -l or --limiter argument initiates flask_limiter - a ready to go package that can protect backend from spam requests. The default request limits per user are 2000 per day or 500 per hour. Limits for specific routes are given below:

Table of limits

Route Requests per minute
/signup 60
/signin 60
/update_exposure 30
/edit_submission 60
/search 100
/password_reset 30
/update_username 30
/delete_account 30

Unit tests

Currently Swappr has no unit tests. In the future proper testing should be executed using pytest library.

Nevertheless, a python script named tcreatemocksubmissions.py is designed to create mockups (that is random fake users and submitted houses), and then import them into swappr.db for testing purposes.

To do that run:

python app.py -m MOCKUPSGEN MOCKUPSGEN

or

python app.py --mockupsgen MOCKUPSGEN MOCKUPSGEN

where the first MOCKUPSGEN is an integer that determines the number of mockup users, while the second one determines the number of mockup submitted houses. Submitted houses number can't exceed the number of users - if so the script handles the arguments to set them as equals.

By default the only static username that is created is admin and developers should use it for debugging.

Testing credentials

Username Password
admin admin

It is recommended to generated some thousands of mockups to have a chance in getting matching houses

Python dependencies

Below are enlisted all the Python frameworks, libraries and packages needed for this application.

  • json
  • secrets
  • subproccess
  • loguru
  • datetime
  • argparse
  • flask
  • flask_limiter
  • werkzeug.security
  • string
  • sqlite3
  • re
  • functools
  • tqdm
  • random
  • fictional_names

Frameworks, libraries, and packages that are not embedded in Python3 should be installed using requirements.txt as previously in this article mentioned.

Custom modules

Swappr uses custom modules that hold constant variables and functions to be called in app.py. What follows is the description of these modules in separate section parts.

argparser.py

This module uses argparse Python package to create argument option instances. Added argument are stored in argparser variable, which is imported in whatever .py module is needed. You may check the argument options on section Running the app, in Argument parser block.


faccount.py

Contains functions needed in /account route.

Table of functions

Function name Parameters Description Child of Parent of
password_reset_validation (oldpassword, new_password, confirm_new_password, hash) Checks password reset input validityn None check_password_hash1 strong_password2
update_username_validation (new_username, user_id) Checks password reset input validity None username_exists2
delete_account_validation (delete_account_confirmation, email) Checks email input validity for account deletion None None

Sources: 1werkzeug.security package, 2fhelpers.py


fhelpers.py

Contains functions needed in many routes.

Table of functions

Function name Parameters Description Child of Parent of
cursor_execute (query, *args) Dynamically executes SQLite3 queries create_database_tables None
cursor_fetch (query, *args) Dynamically fetches data SQLite3 database create_database_tables tuples_to_dict
tuples_to_dict (keys_tuple, values_tuple) Converts two tuples into one dictionary cursor_fetch None
strong_password (password) Checks if a password is strong enough signup_validation1 password_reset_validation2 None
email_exists (email) Checks if email already exists in the database signup_validation1 None
username_exists (username, *args) Checks if username already exists in the database signup_validation1 update_username_validation2 None
get_list_of_values (json_data, column_name) Converts a json file into a list with values of the selected column validate_submitted_location3 validate_searched_location4 None
check_submitted_location (submitted_value, valid_values, error_message) Checks submitted location validity validate_submitted_location3 validate_searched_location4 None
login_required (f) Decorate routes to require login None decorated_function
comma (integer) Formats an integer by placing commas between thousands None None
whitespace (text) Formats a snakecase string into a readable title None None

Sources: 1fsignup.py, 2faccount.py, 3fsubmit.py, 4faccount.py


flog.py

Contains constant variables and functions needed for history logger.

Table of functions

Function name Parameters Description Child of Parent of
log (message, level='INFO', indent=28) Dynamically handles log indentations and message level None None
initialize_logger None Initializes loguru history logger None None
log_new_locations (locations_update, new_locations_flag) Add brief log info about newly inserted location entries in the database None None

fsearch.py

Contains the algorithmic functions and constant variables needed in /search route.

Table of functions

Function name Parameters Description Child of Parent of
room_tolerance_factor (tolerance, room) Returns the tolerance factor that adjusts the maximum value of the criteria search ranges tolerance_factors None
tolerance_factors (tolerance) Returns varied multipliers that affect the criteria ranges to be used in the matching score algorithm None room_tolerance_factor
criteria_ranges (primary_submission, TOLERANCE_FACTORS) Returns varied ranges to be used in the calculation of the matching score for each search house result None None
validate_searched_digits (square_meters, rental, bedrooms, bathrooms) Validates digit-required and ranged-required fields search_validation None
validate_searched_location (city, municipality, region) Validates location input search_validation cursor_fetch1 get_list_of_values1 check_submitted_location1
search_validation (exposure, house_type, square_meters, rental, bedrooms, bathrooms, city, municipality, region) Validates input data None validate_searched_digits validate_searched_location
calculate_location_matching_score (result, primary_submission) Calculates and returns a location matching score location_matching None
location_matching (primary_submission, search_results) Iterates all search results and adds into them the calculated location matching score None calculate_location_matching_score
calculate_house_matching_score (result, CRITERIA_RANGES) Calculates and returns a house characteristics matching score None house_matching
house_matching (search_results, CRITERIA_RANGES) Iterates all search results and adds into them the calculated house characteristics matching score None calculate_house_matching_score
matching_summary (search_results) Iterates all search results and adds into them the summary matching score None None

Source: 1fhelpers.py


fsignin.py

Contains a function needed in /signin route.

Table of functions

Function name Parameters Description Child of Parent of
signin_validation (username, password, user_data) Validates login credentials None 1check_password_hash

Source: 1werkzeug.security package


fsignup.py

Contains a function needed in /signup route.

Table of functions

Function name Parameters Description Child of Parent of
signup_validation (email, username, password, confirm_password) Validates signup credentials None 1strong_password 1email_exists 1username_exists

Source: 1fhelpers.py


fSQL.py

Contains functions needed in database structuring.

Table of functions

Function name Parameters Description Child of Parent of
create_database_tables None Creates database schema None 1cursor_execute
import_locations None Syncs new entries between location.json and swappr.db None None

fsubmit.py

Contains constant variables and functions needed in both /submit and edit_submission routes.

Table of functions

Function name Parameters Description Child of Parent of
validate_submitted_digits (square_meters, rental, bedrooms, bathrooms) Creates database schema None 1cursor_execute
import_locations None Validates digit-required and ranged-required fields None None
validate_submitted_location (city, municipality, region) Validates location input search_validation get_list_of_values1 check_submitted_location1 cursor_fetch1
submission_validation (all_field_values, exposure, house_type, square_meters, rental, bedrooms, bathrooms, city, municipality, region, city_destination, municipality_destination, region_destination) Validates input data None validate_submitted_digits validate_submitted_location
user_submissions_exist (user_id) Checks if user has at least one primary submission determine_primary_submission_status cursor_fetch
determine_primary_submission_status (primary_submission, primary_submission_locked, user_id) Determines primary submission status None user_submissions_exist cursor_execute
handle_primary_submission_upon_deletion (submission_id, user_id) Handles primary submission in database upon submission deletion None cursor_fetch1 cursor_execute1

Source: 1fhelpers.py