Structured Project Code Repository
Structured code is a programming concept that refers to a codebase that has a clear and well-defined organization, with different parts of the code serving specific purposes and interacting with one another in a well-defined way. Structured code typically has a clear separation of concerns, with different parts of the codebase dedicated to different functions, such as user interfaces, data storage, business logic, and so on.
Structured code can be achieved through the use of various programming concepts and tools, such as modules, classes, functions, and namespaces. By using these tools effectively, developers can create code that is easier to understand, modify, and maintain. A well-structured codebase can also be more scalable and reusable, allowing developers to build more complex applications with less effort and fewer errors.
Structured code is important for several reasons:
Readability
Structured code is easier to read and understand for developers. With a clear and consistent structure, code can be quickly comprehended and modified as needed. This is especially important in collaborative projects, where multiple developers are working on the same codebase.
Maintainability
Structured code is easier to maintain in the long term. With a clear separation of concerns and a well-defined structure, changes can be made to specific parts of the codebase without affecting other areas. This reduces the risk of introducing bugs or breaking existing functionality.
Scalability
Structured code is more scalable than unstructured code. As a project grows in complexity, a well-organized structure can help prevent the codebase from becoming unmanageable. This allows developers to add new features or make changes to the codebase without introducing unnecessary complexity.
Reusability
Structured code is more reusable than unstructured code. With a clear separation of concerns, individual components of the codebase can be reused in other parts of the project or in other projects altogether. This reduces the amount of duplicate code and improves overall code quality.
Example Code Repository Structure
Here’s an example code repository structure:
my-project/
README.md
LICENSE
.gitignore
requirements.txt
app/
__init__.py
config.py
models/
__init__.py
user.py
post.py
repositories/
__init__.py
user_repository.py
post_repository.py
controllers/
__init__.py
user_controller.py
post_controller.py
views/
__init__.py
user_view.py
post_view.py
templates/
base.html
user.html
post.html
static/
css/
main.css
js/
main.js
tests/
__init__.py
test_user.py
test_post.py
docs/
index.md
user.md
post.md
In this structure, the main project directory is named my-project
. The root directory contains a README.md
file, a LICENSE
file, a .gitignore
file, and a requirements.txt
file, which specifies the required dependencies for the project.
The app
directory contains the main application code, including a config.py
file for storing configuration settings, a models
directory for storing data models, a views
directory for storing the application's views, a templates
directory for storing HTML templates, and a static
directory for storing static files like CSS and JavaScript.
The tests
directory contains unit tests for the application, while the docs
directory contains documentation for the project.
The repository and controller layers are responsible for handling the business logic and data access of the application.
Here’s an example of how you might implement some simple repositories and controllers for a user and post model in Python using Flask:
# models/user.py
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email
Now, for the example code structure mentioned above, here is some simple example code for each of the files:
app/init.py
This is some sample code for the init file in the app directory:
# app/__init__.py
from flask import Flask
from app.models import db
def create_app():
app = Flask(__name__)
app.config.from_object('app.config.Config')
db.init_app(app)
with app.app_context():
db.create_all()
from app.views import user_view, post_view
app.register_blueprint(user_view.bp)
app.register_blueprint(post_view.bp)
return app
- The code creates a Flask application object using the
Flask
class from theflask
module. - The configuration for the application is set using the
app.config.from_object
method, which loads theConfig
class from theapp.config
module. - The
db
object from theapp.models
module is initialized with the application object using thedb.init_app(app)
method. - A database is created using the
db.create_all()
method within an application context usingwith app.app_context():
. This context is necessary for thedb
object to work properly. - Two views are registered using
app.register_blueprint()
method. These views are located in theapp.views
module, and are nameduser_view
andpost_view
. - The application object is returned from the
create_app()
function, which can then be used to run the Flask application.
app/config.py
The config file in the app directory has all the secret tokens.
# app/config.py
class Config:
SQLALCHEMY_DATABASE_URI = 'sqlite:///mydatabase.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = 'mysecretkey'
- The
Config
class is defined with three class attributes:SQLALCHEMY_DATABASE_URI
,SQLALCHEMY_TRACK_MODIFICATIONS
, andSECRET_KEY
. SQLALCHEMY_DATABASE_URI
is a string that specifies the URI for the database that the Flask application will use. In this case, it is set to a SQLite database namedmydatabase.db
.SQLALCHEMY_TRACK_MODIFICATIONS
is a boolean value that indicates whether or not the Flask-SQLAlchemy extension should track modifications to objects and emit signals when changes are made. In this case, it is set toFalse
to disable this feature.SECRET_KEY
is a string that is used to encrypt and secure data in the application. It should be a long and random string that is kept secret. In this case, it is set tomysecretkey
.
app/models/__init__.py
The init file in the models directory has code to SQLAlchemy initialization.
# app/models/__init__.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
- The
flask_sqlalchemy
module is imported. - The
SQLAlchemy
class from theflask_sqlalchemy
module is instantiated and assigned to thedb
variable. - The
db
object can be used to interact with a database in a Flask application. - The
db
object is typically used to define models (i.e., database tables) using SQLAlchemy's ORM (Object-Relational Mapping) functionality. - The
db
object is also used to execute queries against the database, such as inserting, updating, and retrieving records. - By initializing the
db
object in this way, it becomes available to other modules within the Flask application, allowing them to use the same instance of thedb
object to interact with the database. This helps to ensure consistency and avoid conflicts when accessing the database from different parts of the application.
app/models/user.py
Here is some sample code for the User model, which includes the database columns:
# app/models/user.py
from datetime import datetime
from app.models import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
def __repr__(self):
return '<User %r>' % self.username
- The
datetime
module is imported to enable working with timestamps. - The
db
object is imported from theapp.models
module. This object is an instance of theSQLAlchemy
class, which is used to define the model. - The
User
class is defined with four attributes:id
,username
,email
, andcreated_at
. - The
id
attribute is defined as an integer column with theprimary_key
attribute set toTrue
, indicating that it is the primary key for the table. - The
username
attribute is defined as a string column with a maximum length of 80 characters, and theunique
andnullable
attributes set toTrue
andFalse
, respectively. This means that the column must have a unique value and cannot be null. - The
email
attribute is defined similarly to theusername
attribute, but with a maximum length of 120 characters. - The
created_at
attribute is defined as aDateTime
column with thenullable
attribute set toFalse
, indicating that it cannot be null, and thedefault
attribute set todatetime.utcnow
, which sets the default value to the current UTC time. - The
__repr__
method is defined to provide a string representation of theUser
object. In this case, it returns a string containing theusername
attribute.
This User
model can now be used to interact with the database using SQLAlchemy's ORM functionality. For example, to create a new user in the database, you could do something like this:
new_user = User(username='johndoe', email='johndoe@example.com')
db.session.add(new_user)
db.session.commit()
This would create a new User
object with the specified username
and email
attributes and add it to the database using the db.session.add()
method. The changes are then committed to the database using the db.session.commit()
method.
Why are Repository layers required?
Dependancy injections.
app/models/repositories/user_repository.py
This is the user repository, containing all the functions which interact with the database:
# app/models/repositories/user_repository.py
from app.models import db
from app.models.user import User
class UserRepository:
def get_all(self):
return User.query.all()
def get_by_id(self, id):
return User.query.get(id)
def create(self, username, email):
user = User(username=username, email=email)
db.session.add(user)
db.session.commit()
return user
- The
db
object is imported from theapp.models
module, as well as theUser
model from theapp.models.user
module. - The
UserRepository
class is defined with three methods:get_all()
,get_by_id()
, andcreate()
. - The
get_all()
method returns a list of allUser
objects in the database using theUser.query.all()
method, which generates a SELECT statement to retrieve all rows from theusers
table. - The
get_by_id()
method takes anid
parameter and returns theUser
object with the correspondingid
using theUser.query.get(id)
method, which generates a SELECT statement with a WHERE clause to retrieve the row with the specifiedid
. - The
create()
method takesusername
andemail
parameters, creates a newUser
object with those attributes, adds the object to the database session using thedb.session.add()
method, and commits the changes to the database using thedb.session.commit()
method. Finally, the method returns the newly createdUser
object.
The UserRepository
class provides an abstraction layer for accessing the User
model in the database, which can be useful for encapsulating database access logic and promoting code modularity. For example, instead of writing SQL queries directly in the application code, you can use the UserRepository
methods to perform common database operations on the User
model.
app/models/post.py
This is the post database model which defines a Post model that can be used to store and retrieve blog posts in the database.
from datetime import datetime
from app.models import db
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey(‘user.id’), nullable=False)
author = db.relationship(‘User’, backref=db.backref(‘posts’, lazy=True))
def repr(self):
return ‘<Post %r>’ % self.title
- The
datetime
module is imported to provide the current timestamp when creating newPost
objects. - The
db
object is imported from theapp.models
module. - The
Post
class is defined, which is a subclass ofdb.Model
. This means that thePost
class inherits from SQLAlchemy'sModel
class and is a SQLAlchemy model that can be used to interact with the database. - The
Post
class has several attributes defined using SQLAlchemy's column types, includingid
,title
,content
,created_at
,author_id
, andauthor
. id
is an integer primary key column, which uniquely identifies eachPost
object in the database.title
is a string column that stores the title of the blog post.content
is a text column that stores the body of the blog post.created_at
is a datetime column that stores the date and time when the post was created. It is set to the current time by default when a newPost
object is created.author_id
is an integer foreign key column that references theid
column of theUser
model. This establishes a one-to-many relationship betweenUser
andPost
, where eachPost
object is associated with oneUser
.author
is a relationship that defines theUser
object associated with eachPost
object. Thebackref
parameter specifies that theUser
object should have aposts
attribute that contains a list of all posts created by that user. Thelazy
parameter specifies that the relatedPost
objects should be loaded lazily, meaning that they will only be fetched from the database when accessed.- The
repr()
method is defined to provide a string representation of aPost
object. It returns a string that includes the title of the post.
app/models/repositories/post_repository.py
The post repository contains methods which interact with the session:
from app.models import db
from app.models.post import Post
class PostRepository:
def get_all(self):
return Post.query.all()
def get_by_id(self, id):
return Post.query.get(id)
def create(self, title, content, author_id):
post = Post(title=title, content=content, author_id=author_id)
db.session.add(post)
db.session.commit()
return post
- The
db
object andPost
class are imported from theapp.models
module. - The
PostRepository
class is defined, which has three methods:get_all()
,get_by_id()
, andcreate()
. get_all()
returns a list of allPost
objects in the database, using thequery.all()
method provided by SQLAlchemy's ORM.get_by_id()
takes anid
parameter and returns thePost
object with thatid
, using thequery.get()
method provided by SQLAlchemy's ORM.create()
takestitle
,content
, andauthor_id
parameters, creates a newPost
object with those values, and adds it to the database using thedb.session.add()
method. The changes are then committed to the database using thedb.session.commit()
method. Finally, the newPost
object is returned.
These methods provide a convenient interface for interacting with Post
objects in the database, without having to write raw SQL queries. By using the PostRepository
class, other parts of the Flask application can easily create, read, update, and delete Post
objects as needed.
app/controllers/user_controller.py
The controller layer contains business logic. This is the code for user_controller.py :
from flask import Blueprint, request, jsonify
from app.models.repositories.user_repository import UserRepository
bp = Blueprint(‘user’, name, url_prefix=’/user’)
userRepository = UserRepository()
@bp.route(‘/’)
def get_users():
users = userRepository.get_all()
return jsonify([{‘id’: user.id, ‘username’: user.username, ‘email’: user.email} for user in users])
@bp.route(‘/’, methods=[‘POST’])
def create_user():
data = request.get_json()
user = userRepository.create(data[‘username’], data[‘email’])
return jsonify({‘id’: user.id, ‘username’: user.username, ‘email’: user.email})
- A
Blueprint
object is created with the name 'user' and the URL prefix '/user'. - An instance of
UserRepository
is created. - A route is defined for handling GET requests to the ‘/user’ URL. When a GET request is received, the
get_all()
method of theUserRepository
object is called to retrieve a list of all users from the database. The list of users is then returned as a JSON response. - Another route is defined for handling POST requests to the ‘/user’ URL. When a POST request is received, the JSON data sent in the request body is retrieved using
request.get_json()
. Thecreate()
method of theUserRepository
object is then called to create a new user in the database with the data provided. The newly created user is returned as a JSON response. - The
bp
object and the two routes are returned from the module, so that they can be registered with the Flask application in another module.
By defining routes for handling GET and POST requests to the ‘/user’ URL, this blueprint provides a simple API for retrieving and creating user objects in the application. The UserRepository
class is used to interact with the database, so the controller module is not directly responsible for managing the database connection.
app/controllers/post_controller.py
from flask import Blueprint, request, jsonify
from app.models.repositories.post_repository import PostRepository
bp = Blueprint(‘post’, name, url_prefix=’/post’)
postRepository = PostRepository()
@bp.route(‘/’)
def get_posts():
posts = postRepository.get_all()
return jsonify([{‘id’: post.id, ‘title’: post.title, ‘content’: post.content, ‘author_id’: post.author_id} for post in posts])
@bp.route(‘/’, methods=[‘POST’]) def create_post(): data = request.get_json()
post = postRepository.create(data[‘title’], data[‘content’], data[‘author_id’])
return jsonify({‘id’: post.id, ‘title’: post.title, ‘content’: post.content, ‘author_id’: post.author_id})
The code defines a blueprint named bp
for handling POST and GET requests related to posts. It imports the PostRepository
class from the app.models.repositories.post_repository
module to handle database operations related to posts.
The blueprint has two routes:
/post/
: This route handles GET requests to retrieve all posts in the database. It calls theget_all()
method of thePostRepository
class to fetch all posts and returns a JSON response with the data./post/
: This route handles POST requests to create a new post in the database. It first retrieves the JSON data from the request usingrequest.get_json()
, then calls thecreate()
method of thePostRepository
class to create a new post with the given data. It then returns a JSON response with the newly created post's information, including the ID, title, content, and author ID.
app/views/user_view.py
The views folder contains the different views, i.e. the post view and the user view :
from flask import Blueprint, jsonify
from app.controllers.user_controller import UserController
user_controller = UserController()
user_view = Blueprint('user_view', __name__, url_prefix='/user')
@user_view.route('/')
def get_users():
users = user_controller.get_users()
return jsonify(users)
@user_view.route('/', methods=['POST'])
def create_user():
data = request.get_json()
user = user_controller.create_user(data)
return jsonify(user)
@user_view.route('/<int:user_id>')
def get_user(user_id):
user = user_controller.get_user(user_id)
if user:
return jsonify(user)
else:
return '', 404
from flask import Blueprint, jsonify
: Importing theBlueprint
andjsonify
modules from Flask.from app.controllers.user_controller import UserController
: Importing theUserController
class fromapp.controllers.user_controller
.user_controller = UserController()
: Creating an instance of theUserController
class.user_view = Blueprint('user_view', __name__, url_prefix='/user')
: Creating aBlueprint
object nameduser_view
with the name of'user_view'
, the current module name as the second argument (__name__
), and a URL prefix of'/user'
.@user_view.route('/')
: Decorating theget_users
function below it with a route that handles HTTP GET requests to the URL'/user/'
.def get_users():
: Defining a function that retrieves all users and returns them in JSON format.users = user_controller.get_users()
: Calling theget_users
method of theUserController
instance created earlier to retrieve all users from the database.return jsonify(users)
: Converting theusers
list to a JSON string using Flask'sjsonify
function and returning it as an HTTP response.@user_view.route('/', methods=['POST'])
: Decorating thecreate_user
function below it with a route that handles HTTP POST requests to the URL'/user/'
.def create_user():
: Defining a function that creates a new user with the data provided in the request and returns the new user in JSON format.data = request.get_json()
: Retrieving the JSON data from the request.user = user_controller.create_user(data)
: Calling thecreate_user
method of theUserController
instance created earlier to create a new user in the database.return jsonify(user)
: Converting the newuser
object to a JSON string using Flask'sjsonify
function and returning it as an HTTP response.@user_view.route('/<int:user_id>')
: Decorating theget_user
function below it with a route that handles HTTP GET requests to the URL'/user/<user_id>'
, whereuser_id
is an integer.def get_user(user_id):
: Defining a function that retrieves a single user with the givenuser_id
and returns it in JSON format.user = user_controller.get_user(user_id)
: Calling theget_user
method of theUserController
instance created earlier to retrieve a single user from the database.if user:
: Checking if a user was found with the givenuser_id
.return jsonify(user)
: Converting theuser
object to a JSON string using Flask'sjsonify
function and returning it as an HTTP response.else:
: If no user was found with the givenuser_id
, returning an empty string with a 404 status code to indicate a "Not Found" error.
app/views/post_view.py
from flask import Blueprint, request, jsonify
from app.controllers.post_controller import PostController
bp = Blueprint('post_view', __name__, url_prefix='/post')
postController = PostController()
@bp.route('/')
def get_posts():
posts = postController.get_all_posts()
return jsonify([{'id': post.id, 'title': post.title, 'content': post.content, 'author_id': post.author_id} for post in posts])
@bp.route('/', methods=['POST'])
def create_post():
data = request.get_json()
post = postController.create_post(data['title'], data['content'], data['author_id'])
return jsonify({'id': post.id, 'title': post.title, 'content': post.content, 'author_id': post.author_id})
This code creates a Flask Blueprint for handling requests related to posts, and connects it to a PostController
object that handles the business logic for posts.
The Blueprint
object is created with the name "post_view" and the URL prefix "/post". The PostController
object is also instantiated.
There are two routes defined for this blueprint:
- A
GET
route at the URL "/" that retrieves all posts by callingpostController.get_all_posts()
. The retrieved posts are then formatted as JSON and returned to the client. - A
POST
route at the URL "/" that creates a new post by callingpostController.create_post()
. The data for the new post is obtained from the request body as JSON, and the new post is created with the provided information. The newly created post is then formatted as JSON and returned to the client.
templates/base.html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8">
<title>{% block title %}My App{% endblock %}</title>
<link rel=”stylesheet” href=”{{ url_for(‘static’, filename=’css/main.css’) }}”>
</head>
<body>
<nav>
<ul>
<li><a href=”{{ url_for(‘user.get_users’) }}”>Users</a></li>
<li><a href=”{{ url_for(‘post.get_posts’) }}”>Posts</a></li>
</ul>
</nav>
<main>
{% block content %}
{% endblock %}
</main>
<script src=”{{ url_for(‘static’, filename=’js/main.js’) }}”></script>
</body>
</html>
- This is an HTML file with the
lang
attribute set to "en". - The
head
section contains ameta
tag specifying the character encoding and atitle
tag that can be overridden by child templates using theblock
syntax. - The
link
tag loads a CSS file located in thestatic/css
directory. - The
body
section contains anav
element with an unordered list of navigation links, including links to the "Users" and "Posts" pages generated using Flask'surl_for
function. - The
main
element contains the main content of the page, which can be overridden by child templates using theblock
syntax. - The
script
tag loads a JavaScript file located in thestatic/js
directory.
templates/user.html
{% extends 'base.html' %}
{% block content %}
<h1>Users</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<form id="create-user-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username"><br>
<label for="email">Email:</label>
<input type="text" id="email" name="email"><br>
<button type="submit">Create User</button>
</form>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<script>
// Handle form submission to create new user
const form = document.getElementById('create-user-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const username = form.elements.username.value;
const email = form.elements.email.value;
createUser(username, email);
form.reset();
});
</script>
{% endblock %}
templates/post.html
- The
{% block content %}
and{% endblock %}
tags define the content of the page, which will be placed inside the{% block content %}
tag in the base.html file. - The page displays a table with three columns (ID, Username, Email) and rows of user data.
- The
{% for user in users %}
and{% endfor %}
tags define a loop that iterates over the list of users passed to the template from the backend. - The
<form>
element at the bottom of the page allows the user to create a new user by entering a username and email address. - The
{% block scripts %}
and{% endblock %}
tags define a section for scripts to be included at the bottom of the page. - The script in this section listens for a form submission event and extracts the values entered by the user, which are then passed to a JavaScript function
createUser()
defined in the main.js file included in the base.html file.
{% extends 'base.html' %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<article>
<header>
<h1>{{ post.title }}</h1>
<p>By {{ post.author.username }}</p>
</header>
<div>
{{ post.content }}
</div>
</article>
{% endblock %}
static/css/main.css
/* Global styles */
body {
font-family: sans-serif;
line-height: 1.5;
margin: 0;
padding: 0;
}
header {
background-color: #333;
color: #fff;
padding: 1rem;
}
nav {
display: flex;
justify-content: space-between;
}
nav a {
color: #fff;
text-decoration: none;
}
nav a:hover {
text-decoration: underline;
}
main {
margin: 2rem;
}
/* Page-specific styles */
/* User view */
.user {
margin-bottom: 1rem;
padding: 1rem;
border: 1px solid #ccc;
}
.user h2 {
margin: 0 0 1rem;
}
.user p {
margin: 0;
}
/* Post view */
article {
margin-bottom: 2rem;
}
article h1 {
margin: 0 0 1rem;
}
article p {
margin: 0 0 1rem;
}
article div {
font-size: 1.2rem;
line-height: 1.5;
}
The main.css
file is a Cascading Style Sheet (CSS) file that contains styles to define the look and feel of the HTML elements in the web application. Here's a breakdown of the code:
/* Global styles */
body
: sets the font family, line height, margin, and padding for the entire web page.header
: sets the background color, text color, and padding for the header element.nav
: sets the display to flex and justifies the content between the navigation links.nav a
: sets the color and removes text decoration for the navigation links.nav a:hover
: sets text decoration to underline when hovering over the navigation links.main
: sets the margin around the main content area.
/* Page-specific styles */
.user
: sets the margin, padding, and border of the user element..user h2
: sets the margin of the user header element..user p
: sets the margin of the user paragraph element.article
: sets the margin of the article element.article h1
: sets the margin of the article header element.article p
: sets the margin of the article paragraph element.article div
: sets the font size and line height of the article div element.
static/js/main.js
// get the form and input fields
const postForm = document.querySelector('#post-form');
const titleInput = document.querySelector('#title');
const contentInput = document.querySelector('#content');
// add event listener for form submission
postForm.addEventListener('submit', (event) => {
// prevent the default form submission behavior
event.preventDefault();
// get the input values
const title = titleInput.value.trim();
const content = contentInput.value.trim();
// make a POST request to create a new post
fetch('/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: title,
content: content,
author_id: 1 // replace with the actual author ID
})
})
.then(response => response.json())
.then(data => {
// clear the form inputs
titleInput.value = '';
contentInput.value = '';
// display a success message
const successMsg = document.querySelector('#success-msg');
successMsg.innerHTML = 'Post created successfully!';
successMsg.classList.remove('hidden');
// add the new post to the page
const postList = document.querySelector('#post-list');
const postHTML = `
<div class="card">
<div class="card-header">
<h2>${data.title}</h2>
</div>
<div class="card-body">
<p>${data.content}</p>
<p>Author: ${data.author_id}</p>
</div>
</div>
`;
postList.innerHTML += postHTML;
})
.catch(error => {
console.error(error);
// display an error message
const errorMsg = document.querySelector('#error-msg');
errorMsg.innerHTML = 'An error occurred while creating the post.';
errorMsg.classList.remove('hidden');
});
});
This code is a JavaScript file called main.js
which is used in a web application. Here is an explanation of the code:
const postForm = document.querySelector('#post-form');
: This line selects the HTML form with theid
ofpost-form
and assigns it to a constant calledpostForm
.const titleInput = document.querySelector('#title');
: This line selects the HTML input field with theid
oftitle
and assigns it to a constant calledtitleInput
.const contentInput = document.querySelector('#content');
: This line selects the HTML input field with theid
ofcontent
and assigns it to a constant calledcontentInput
.postForm.addEventListener('submit', (event) => { ... });
: This line adds an event listener to thepostForm
element that listens for asubmit
event. When the form is submitted, the callback function is called.event.preventDefault();
: This line prevents the default form submission behavior, which would cause the page to reload.const title = titleInput.value.trim();
: This line gets the value of thetitleInput
field and trims any leading or trailing whitespace. The result is assigned to a constant calledtitle
.const content = contentInput.value.trim();
: This line gets the value of thecontentInput
field and trims any leading or trailing whitespace. The result is assigned to a constant calledcontent
.fetch('/post', { ... });
: This line makes aPOST
request to the/post
endpoint of the web application, sending a JSON payload that includes thetitle
,content
, andauthor_id
fields..then(response => response.json())
: This line extracts the JSON data from the response object..then(data => { ... });
: This line defines a callback function that is called when the data has been successfully extracted from the response object. It clears the form inputs, displays a success message, and adds the new post to the page..catch(error => { ... });
: This line defines a callback function that is called if an error occurs during thefetch()
request. It logs the error to the console and displays an error message.
Tests
Inside the tests folder, it is great to have different test folders for the views, controllers and the data model. This is an example structure of how the files and folders should be structured.
tests\
init.py
views\
test_user_view.py
test_post_view.py
controller\
test_user_controller.py
test_post_controller.py
models\
test_user.py\
test_post.py\
repository\
test_user_repository.py\
test_post_repository.py\
Here are a few examples of the code for the repository tests:
tests/init.py
# empty file
tests/test_user.py
- The
test_user.py
file contains two unit test functions for theUserRepository
class. - The first function
test_get_all_users()
tests theget_all()
method of theUserRepository
class by creating a new instance of the class and calling theget_all()
method to retrieve all users. The test then checks if the length of the users list is equal to zero. - The second function
test_create_user()
tests thecreate()
method of theUserRepository
class by creating a new instance of the class and calling thecreate()
method to create a new user with the username 'testuser' and email 'testuser@example.com’. The test then checks if the username and email of the created user object match the expected values 'testuser' and 'testuser@example.com’.
These unit tests ensure that the UserRepository
class is working correctly and that it can retrieve all users and create new users with the expected values.
from app.models.repositories.user_repository import UserRepository
def test_get_all_users():
repo = UserRepository()
users = repo.get_all()
assert len(users) == 0
def test_create_user():
repo = UserRepository()
user = repo.create(‘testuser’, ‘testuser@example.com’)
assert user.username == ‘testuser’
assert user.email == ‘testuser@example.com’
tests/test_post.py
from app.models.repositories.post_repository import PostRepository
def test_get_all_posts():
repo = PostRepository()
posts = repo.get_all()
assert len(posts) == 0
def test_create_post():
repo = PostRepository()
post = repo.create(‘Test Post’, ‘This is a test post.’)
assert post.title == ‘Test Post’
assert post.body == ‘This is a test post.’
The test_post.py
file contains two test functions for testing the functionality of the PostRepository
class in the app.models.repositories.post_repository
module.
- The first test function
test_get_all_posts
checks whether theget_all()
method ofPostRepository
returns an empty list when no posts have been created yet. It does this by creating an instance of thePostRepository
class, calling theget_all()
method to retrieve all posts, and then checking whether the length of the resulting list is 0. If the test passes, it means that theget_all()
method is working correctly and returning an empty list when there are no posts in the database. - The second test function
test_create_post
checks whether thecreate()
method ofPostRepository
correctly creates a new post with the given title and body. It does this by creating an instance of thePostRepository
class, calling thecreate()
method with the title and body of the test post, and then checking whether the resulting post object has the correct title and body values. If the test passes, it means that thecreate()
method is working correctly and creating a new post with the specified title and body.
docs/index.md
This is the main documentation page.
docs/user.md
This page documents the user-related functionality.
docs/post.md
This page documents the post-related functionality.
Conclusion
Writing structured code is essential for creating programs that are easy to understand, maintain, and extend. Structured code allows developers to write clean, readable, and efficient code that is less prone to errors and bugs. By using meaningful variable and function names, commenting code, and breaking code into logical units, developers can make it easier for themselves and others to understand and modify the code as needed. Structured code also helps to promote code reuse and makes it easier to test and debug. Writing structured code is an important skill for any programmer to master, and it can lead to more robust and reliable software applications.
That’s it for this article! Feel free to leave feedback or questions in the comments. If you found this an exciting read, leave some claps and follow! I love coffee, so feel free to buy me a coffee at https://bmc.link/pycodenemesis XD. Cheers!