Welcome to SignCoding: Where Programming Meets Sign Language Tutoring! At SignCoding, we bridge the gap between ambition and achievement, providing an inclusive environment tailored to your unique needs. Whether you are starting your coding journey or looking to enhance your skills, our dedicated sign language tutors are here to ensure seamless and effective communication. Join us today and transform your learning experience!
The primary goal of SignCoding is to create an empowering, inclusive platform that connects aspiring programmers with expert sign language tutors. By fostering a supportive learning environment, SignCoding aims to help deaf and hard-of-hearing individuals achieve their full potential in the programming world.
SignCoding is designed for aspiring programmers and developers who are deaf or hard of hearing. Our platform is perfect for individuals who want to learn programming languages like HTML, CSS, JavaScript, Python, and more in a supportive and inclusive environment. Whether you are a beginner eager to start your coding journey or an experienced coder looking to refine your skills, SignCoding welcomes you to connect, learn, and grow with us.
At SignCoding, users can benefit from a streamlined learning experience focused on programming and effective communication. Our verified sign language tutors are experts in programming languages and are here to help you grasp concepts quickly and efficiently. The platform is designed to empower deaf and hard-of-hearing students by providing a curriculum that equips them with the skills needed to thrive in today’s tech landscape.
Join SignCoding today and unlock your potential in a learning environment where programming meets sign language tutoring!
source: amiresponsive
In this project, I follow the Five Planes of User Experience model invented by Jesse James Garrett.
This model aids in transforming from abstract ideas, such as creating objectives of the project and identifying the user needs, to concrete concepts, such as assembling visual elements to produce the visual design of the idea to meet the project’s objectives and users’ needs.
The vision for SignCoding is to be a unique, inclusive learning platform where aspiring programmers can connect with expert sign language tutors and seamlessly learn programming languages. Unlike other platforms, SignCoding creates an accessible and empowering environment for deaf and hard-of-hearing students, ensuring effective communication and support throughout their coding journey.
In this context, the core value of SignCoding lies in providing a streamlined, inclusive learning experience focused on programming education and effective communication through sign language.
Based on the main objective and goals set out in the Strategy Plane, these requirements for developing the website are broken down into two categories:
The requirements outlined in the Scope Plane were then used to create a structure for the website. A site map below shows how users can navigate the website easily.
Please refer to the Wireframes section for more detailed wireframing.
Click here to view the live site.
I used Color Hunt to generate my colour palette.
#F9F6EE
is used for the primary background, inverted font colour and secondary button.#3C0008
is used for the primary navbar, main button, and footer colour.#FFC107
is used for the secondary button.#000
is used for the main font colour.The colour palette for SignCoding represents the harmony between coding and sign language education, using vibrant colours to symbolise inclusivity and communication. However, the colour palette needed to pass the minimum colour contrast set by the Web Content Accessibility Guide (WCAG). The colour palette was tested using Coolors’ Color Contrast Checker. The result below shows that these colours passed the minimum WCAG contrast ratio.
Bootstrap’s native font stack was used throughout the site.
Font Awesome icons were used throughout the site, such as the social media icons in the footer and buttons in a detailed gram view.
As a returning site user, I would like to view my booking history, so that I can keep track of my past sessions.
As a returning site user, I would like to check tutor profiles’ date and time availability, so that I can book new sessions as per my schedule.
As a returning site user, I would like to update my profile information, so that my details remain current.
As a site administrator, I should be able to manage user accounts, so that I can ensure a secure and efficient platform.
As a site administrator, I should be able to monitor tutor availability, so that I can maintain an up-to-date list of available sessions.
As a site administrator, I should be able to handle booking requests, so that I can ensure smooth scheduling and session management.
To follow best practice, wireframes were developed for mobile, tablet, and desktop sizes. I’ve used Balsamiq to design my site wireframes.
git add
, git commit
, git push
)Entity Relationship Diagrams (ERD) help to visualise database architecture before creating models. Understanding the relationships between different tables can save time later in the project.
class ProgrammingLanguage(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class SignLanguage(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class DayAvailability(models.Model):
name = models.CharField(max_length=20, unique=True)
order = models.PositiveIntegerField(default=0)
def __str__(self):
return self.name
class Meta:
ordering = ['order']
verbose_name = "Day Availability"
verbose_name_plural = "Day Availabilities"
class TimeSlot(models.Model):
start_time = models.TimeField()
end_time = models.TimeField()
def __str__(self):
return f"{self.start_time.strftime('%H:%M')} - {self.end_time.strftime('%H:%M')}"
def clean(self):
if self.start_time >= self.end_time:
raise ValidationError("Start time must be before end time.")
if self.end_time <= self.start_time:
raise ValidationError("End time must be after start time.")
class Tutor(models.Model):
tutor_firstname = models.CharField(max_length=50, verbose_name="Tutor's First Name")
tutor_lastname = models.CharField(max_length=50, verbose_name="Tutor's Surname")
tutor_email = models.EmailField(max_length=254, unique=True, verbose_name="Tutor's Email Address", validators=[EmailValidator()])
programming_languages = models.ManyToManyField(ProgrammingLanguage, related_name="tutors")
sign_languages = models.ManyToManyField(SignLanguage, related_name="tutors")
day_availability = models.ManyToManyField(DayAvailability)
time_availability = models.ManyToManyField(TimeSlot, related_name="tutors")
price = models.DecimalField(max_digits=6, decimal_places=2)
photo = CloudinaryField('profile_picture', default='images/default_tutor.jpg')
def __str__(self):
return f"{self.tutor_firstname} {self.tutor_lastname} | {self.tutor_email}"
def get_full_name(self):
return f"{self.tutor_firstname} {self.tutor_lastname}"
class Profile(models.Model):
personal_details = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name="Username")
personal_firstname = models.CharField(max_length=50, blank=True, null=True, verbose_name="User's First Name")
personal_lastname = models.CharField(max_length=50, blank=True, null=True, verbose_name="User's Last Name")
def __str__(self):
return self.personal_details.username
def get_full_name(self):
return f"{self.personal_firstname} {self.personal_lastname}"
@receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(
personal_details=instance,
personal_firstname=instance.first_name,
personal_lastname=instance.last_name
)
else:
profile = Profile.objects.get(personal_details=instance)
profile.personal_firstname = instance.first_name
profile.personal_lastname = instance.last_name
profile.save()
class Booking(models.Model):
PAYMENT_STATUS_CHOICES = [
('pending', 'Pending'),
('paid', 'Paid'),
('failed', 'Failed'),
]
booking_id = models.CharField(max_length=32, unique=True, editable=False, verbose_name="Booking ID")
booking_date = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Booking Date")
stripe_pid = models.CharField(max_length=254, unique=True, null=False, blank=False, default='', editable=False, verbose_name="Stripe Payment ID")
user = models.ForeignKey(Profile, on_delete=models.SET_NULL, related_name="bookings", null=True)
user_fullname = models.CharField(max_length=101, null=True, blank=False, verbose_name="User's Full Name")
user_email = models.CharField(max_length=254, null=True, blank=False, verbose_name="User's Email address")
tutor = models.ForeignKey(Tutor, on_delete=models.SET_NULL, related_name="bookings", null=True)
tutor_fullname = models.CharField(max_length=101, null=True, blank=False, verbose_name="Tutor's Full Name")
tutor_email = models.CharField(max_length=254, null=True, blank=False, verbose_name="Tutor's Email address")
total_price = models.DecimalField(max_digits=6, decimal_places=2)
session_date = models.DateField()
session_time = models.ManyToManyField(TimeSlot, related_name="bookings")
payment_status = models.CharField(max_length=10, choices=PAYMENT_STATUS_CHOICES, default='pending')
class Meta:
ordering = ['-booking_date']
def _generate_booking_id(self):
return uuid.uuid4().hex.upper()
def save(self, *args, **kwargs):
if not self.booking_id:
self.booking_id = self._generate_booking_id()
if not self.stripe_pid:
self.stripe_pid = self._generate_stripe_pid()
super().save(*args, **kwargs)
def __str__(self):
return f"Booking Number {self.booking_id}"
def is_available(tutor, session_date, time_slot):
bookings = Booking.objects.filter(
tutor=tutor,
session_date=session_date,
session_time=time_slot
)
return not bookings.exists()
class NewsletterSubscription(models.Model):
email = models.EmailField(unique=True)
def __str__(self):
return self.email
class Contact(models.Model):
ticket_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
date_submitted = models.DateTimeField(auto_now_add=True)
full_name = models.CharField(max_length=255)
email = models.EmailField()
message = models.TextField()
def __str__(self):
return f"Ticket {self.ticket_id} - {self.full_name}"
I have used pygraphviz
and django-extensions
to auto-generate an ERD.
The steps taken were as follows:
sudo apt update
sudo apt-get install python3-dev graphviz libgraphviz-dev pkg-config
Y
to proceedpip3 install django-extensions pygraphviz
settings.py
file, I added the following to my INSTALLED_APPS
:
INSTALLED_APPS = [
...
'django_extensions',
...
]
python3 manage.py graph_models -a -o erd.png
erd.png
file into my documentation/
folder'django_extensions',
from my INSTALLED_APPS
pip3 uninstall django-extensions pygraphviz -y
source: medium.com
I have used Mermaid
to generate an interactive ERD of my project.
erDiagram
Booking {
string booking_id
datetime booking_date
string stripe_pid
string user_fullname
string user_email
string tutor_fullname
string tutor_email
decimal total_price
date session_date
char payment_status
}
Profile {
string personal_firstname
string personal_lastname
}
Contact {
uuid ticket_id
datetime date_submitted
string full_name
email email
text message
}
NewsletterSubscription {
email email
}
ProgrammingLanguage {
string name
}
SignLanguage {
string name
}
DayAvailability {
string name
integer order
}
TimeSlot {
time start_time
time end_time
}
Tutor {
string tutor_firstname
string tutor_lastname
email tutor_email
decimal price
image photo
}
Booking ||--o{ Profile : "user"
Booking ||--o{ Tutor : "tutor"
Booking }o--o{ TimeSlot : "session_time"
Tutor }o--o{ ProgrammingLanguage : "programming_languages"
Tutor }o--o{ SignLanguage : "sign_languages"
Tutor }o--o{ DayAvailability : "day_availability"
Tutor }o--o{ TimeSlot : "time_availability"
source: Mermaid
GitHub Projects served as an Agile tool for this project. It isn’t a specialised tool, but with the right tags and project creation/issue assignments, it can work.
Through it, user stories, issues, and milestone tasks were planned and tracked weekly using the basic Kanban board.
GitHub Issues served as another Agile tool. I used my own User Story Template to manage user stories there.
It also helped with milestone iterations weekly.
I’ve decomposed my Epics into stories before prioritising and implementing them. Using this approach, I could apply the MoSCow prioritisation and labels to my user stories within the Issues tab.
Furthermore, based on MoSCoW prioritisation, I calculated the story points based on the Estimation Matrix to score each MoSCoW prioritisation based on my knowledge and skills and the requirements of the project.
Finally, I assigned each MoSCoW prioritisation into one of four sprint weeks.
SignCoding operates on a B2C (Business to Customer) model, offering an innovative platform that combines programming education with sign language tutoring. By focusing on empowering deaf and hard-of-hearing individuals, SignCoding creates a direct connection between expert tutors and students, ensuring seamless, individualised learning experiences.
This business model prioritises one-on-one tutoring sessions without requiring long-term subscriptions. Students can book sessions tailored to their specific goals, enabling flexibility and accessibility in their educational journey.
Although developing, SignCoding has implemented features like a newsletter and social media integration to foster community engagement. Social media platforms such as Facebook and Instagram help increase visibility and create a sense of belonging among users. The newsletter serves as a channel to provide updates, such as new tutors joining the platform, special offers, new programming courses, and community events.
By combining these features with its unique value proposition, SignCoding is building a dynamic and inclusive ecosystem to support aspiring programmers in achieving their goals.
I’ve identified some appropriate keywords to align with my site, which should help users find my page easily from a search engine when searching online. This included a series of the following keyword types
I also played around with Word Tracker a bit to check the frequency of some of my site’s primary keywords (only until the free trial expired).
I’ve used XML-Sitemaps to generate a sitemap.xml file. This was generated using my deployed site URL: https://signcoding-d529cc1ebf99.herokuapp.com
After crawling the entire site, it created a sitemap.xml, which I’ve downloaded and included in the repository.
I’ve created the robots.txt file at the root-level. Inside, I’ve included the default settings:
User-agent: *
Disallow:
Sitemap: https://signcoding-d529cc1ebf99.herokuapp.com/sitemap.xml
Further links for future implementation:
Creating a strong social base (with participation) and linking that to the business site can help drive sales. Using more popular providers with a wider user base, such as Facebook, typically maximises site views.
I’ve created a mockup Facebook business account:
I have incorporated a newsletter sign-up form on my application to allow users to supply their email addresses if they want to learn more right above the footer section.
[!NOTE]
For all testing, please refer to the TESTING.md file.
The live deployed application can be found deployed on Heroku.
This project uses a Code Institute PostgreSQL Database.
To obtain my own Postgres Database from Code Institute, I followed these steps:
[!CAUTION]
- PostgreSQL databases by Code Institute are only available to CI Students.
- You must acquire your own PostgreSQL database through some other method if you plan to clone/fork this repository.
- Code Institute students are allowed a maximum of 8 databases.
- Databases are subject to deletion after 18 months.
This project uses the Cloudinary API to store media assets online, due to the fact that Heroku doesn’t persist this type of data.
To obtain your own Cloudinary API key, create an account and log in.
CLOUDINARY_URL=
as part of the API value; this is the key.This project uses Stripe to handle the ecommerce payments.
Once you’ve created a Stripe account and logged-in, follow these series of steps to get your project connected.
STRIPE_PUBLIC_KEY
= Publishable Key (starts with pk)STRIPE_SECRET_KEY
= Secret Key (starts with sk)As a backup, in case users prematurely close the purchase-order page during payment, we can include Stripe Webhooks.
https://signcoding-d529cc1ebf99.herokuapp.com/checkout/wh/
STRIPE_WH_SECRET
= Signing Secret (Wehbook) Key (starts with wh)This project uses Gmail to handle sending emails to users for account verification and purchase order confirmations.
Once you’ve created a Gmail (Google) account and logged-in, follow these series of steps to get your project connected.
EMAIL_HOST_PASS
= user’s 16-character API keyEMAIL_HOST_USER
= user’s own personal Gmail email addressThis project uses Heroku, a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud.
Deployment steps are as follows, after account setup:
[!IMPORTANT]
This is a sample only; you would replace the values with your own if cloning/forking my repository.
Key | Value |
---|---|
CLOUDINARY_URL |
user’s own value |
DATABASE_URL |
user’s own value |
DISABLE_COLLECTSTATIC |
1 (this is temporary, and can be removed for the final deployment) |
EMAIL_HOST_PASS |
user’s own value |
EMAIL_HOST_USER |
user’s own value |
SECRET_KEY |
user’s own value |
STRIPE_PUBLIC_KEY |
user’s own value |
STRIPE_SECRET_KEY |
user’s own value |
STRIPE_WH_SECRET |
user’s own value |
Heroku needs three additional files in order to deploy properly.
You can install this project’s requirements (where applicable) using:
pip3 install -r requirements.txt
If you have your own packages that have been installed, then the requirements file needs updated using:
pip3 freeze --local > requirements.txt
The Procfile can be created with the following command:
echo web: gunicorn app_name.wsgi > Procfile
The runtime.txt file needs to know which Python version you’re using:
python3 --version
in the terminal.python-3.9.19
For Heroku deployment, follow these steps to connect your own GitHub repository to the newly created app:
Either:
Or:
heroku login -i
heroku git:remote -a app_name
(replace app_name with your app name)add
, commit
, and push
to GitHub, you can now type:
git push heroku main
The project should now be connected and deployed to Heroku!
This project can be cloned or forked in order to make a local copy on your own system.
For either method, you will need to install any applicable packages found within the requirements.txt file.
pip3 install -r requirements.txt
.You will need to create a new file called env.py
at the root-level,
and include the same environment variables listed above from the Heroku deployment steps.
[!IMPORTANT]
This is a sample only; you would replace the values with your own if cloning/forking my repository.
Sample env.py
file:
import os
os.environ.setdefault("CLOUDINARY_URL", "user's own value")
os.environ.setdefault("DATABASE_URL", "user's own value")
os.environ.setdefault("EMAIL_HOST_PASS", "user's own value")
os.environ.setdefault("EMAIL_HOST_USER", "user's own value")
os.environ.setdefault("SECRET_KEY", "user's own value")
os.environ.setdefault("STRIPE_PUBLIC_KEY", "user's own value")
os.environ.setdefault("STRIPE_SECRET_KEY", "user's own value")
os.environ.setdefault("STRIPE_WH_SECRET", "user's own value")
# local environment only (do not include these in production/deployment!)
os.environ.setdefault("DEBUG", "True")
Once the project is cloned or forked, in order to run it locally, you’ll need to follow these steps:
python3 manage.py runserver
CTRL+C
or ⌘+C
(Mac)python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py createsuperuser
python3 manage.py loaddata file-name.json
(repeat for each file)python3 manage.py runserver
If you’d like to backup your database models, use the following command for each model you’d like to create a fixture for:
python3 manage.py dumpdata your-model > your-model.json
You can clone the repository by following these steps:
git clone https://github.com/RoBizMan/SignCoding.git
Alternatively, if using Gitpod, you can click below to create your own workspace using this repository.
Please note that in order to directly open the project in Gitpod, you need to have the browser extension installed. A tutorial on how to do that can be found here.
By forking the GitHub Repository, we make a copy of the original repository on our GitHub account to view and/or make changes without affecting the original owner’s repository. You can fork this repository by using the following steps:
No difference between local and deployment was noticed.
Source | Location | Notes |
---|---|---|
Markdown Builder | README and TESTING | tool to help generate the Markdown files |
Chris Beams | version control | “How to Write a Git Commit Message” |
W3Schools | entire site | responsive HTML/CSS/JS navbar |
W3Schools | contact page | interactive pop-up (modal) |
W3Schools | entire site | how to use CSS :root variables |
Flexbox Froggy | entire site | modern responsive layouts |
Grid Garden | entire site | modern responsive layouts |
strftime | CRUD functionality | helpful tool to format date/time from string |
WhiteNoise | entire site | hosting static files on Heroku temporarily |
W3Schools | entire site | responsive HTML/CSS/JS navbar |
W3Schools | detailed gram page | interactive pop-up (modal) to confirm the deletion of the gram |
freeCodeCamp | version control | “How to Write Better Git Commit Messages - A Step-By-Step Guide” |
W3Schools | entire site | box shadow property |
Logo | entire site | the company’s logo was generated by Logo and used throughout the website, including favicon |
Aurélien Delogu | entire site | “How to solve a 401 error on a webmanifest file” |
GitHub Issues | entire site | “Mixed Content warning when serving over HTTPS” |
Cloudinary Support | entire site | Debugging the issue with mixed content warning over HTTPS |
Cloudinary Community | entire site | Debugging the issue with mixed content warning over HTTPS |
StackOverflow | entire site | Force Cloudinary images to load HTTPS instead of HTTP |
Code Institute Slack | entire site | The solution to the mixed content warning over HTTPS issue |
DBDiagram | entire site | The ERD tool to design models for the project’s database |
Stackoverflow | entire site | Show/hide pages based on user authentication |
Coderbook | entire site | Restrict non-logged in users from access to login required pages |
Django Andy | entire site | Redirecting an already logged in user to different pages |
Code Institute Slack | backend only | Automatic switch between debug true and false |
LambdaTest | Backend only - JavaScript | JavaScript unit testing using Jest guide |
Real Python | Backend only - Python | Testing in Django best practices and examples |
django | Backend only - Python | django documentation writing and running tests |
Alice Ridgway | Backend only - Python | Django Testing for Beginners |
mermaid.live | README.md ERD diagram | Live ERD diagram for markdown |
flatpickr | Booking page | Flatpickr datetime picker |
Select2 | Booking and Tutor pages | Multi-choice select boxes in the dropdown menu |
Stripe Web Elements | Booking page | Stripe card payment element |
Stripe Testing | Booking page | Stripe testing cards |
Steemit | Booking page | Setting up Flatpickr in the project |
YouTube | Booking page | How to initialise the datetime picker in the project |
StackOverflow | Booking page | How to grab selected dates from Flatpickr |
Code Institute Slack | Sign Up page | Save user’s first and last name in the database through the Sign Up page form |
YouTube | Booking and Tutor pages | Django Select2 Tutorial |
Webwizard | Booking and Tutor pages | How to Create a Checkbox Inside a Dropdown Django |
Anjanesh Consulting | Booking page | Use JavaScript’s fetch to connect to a Django REST API URL via AJAX |
Django | Booking page | Django Endpoint to accept string, boolean, and list |
Booking page | Is there a way to add Django URL in AJAX? | |
educative | Booking page | How to work with AJAX requests and responses in Django |
JustDjango | Booking page | Django and Stripe Payments Tutorial |
AI Saas Template | Booking page | Integrating Stripe with Django: A Comprehensive Guide |
TestDriven.io | Booking page | Django Stripe Tutorial |
Episyche | Booking page | How to integrate Stripe Payment Gateway in Django and React for the checkout use case? |
Rudrastyh | Booking page | Setup Stripe Payments on your Website with Payment Intents |
Dev.to | Booking page | Fundamentals of the PaymentIntents and PaymentMethods APIs |
Fireship.io | Booking page | Stripe Payment Basics |
StackOverflow | Booking page | How to use Stripe payment_intent_data to send email to client after successful payment? |
StackOverflow | Booking page | How customer gets an email receipt from Stripe after successful paymentIntent |
Scaler | the entire site | How to Create a Hamburger Menu in CSS? |
YouTube | the entire site | Build a Hamburger Menu with HTML, CSS, and JavaScript |
MailTrap | Contact page/Newsletter app | Send Emails with Gmail API |
Endgrate | Contact page/Newsletter app | Using the Gmail API to Send Messages (with PHP examples) |
Source | Location | Type | Notes |
---|---|---|---|
Pexels | Tutor pages | image | profile pictures |
Tutor pages | image | default profile picture placeholder | |
iStock | Home page | image | students and study groups |