The new LMS for Fitzroy Academy!
Bugs and issues go here: https://github.com/fitzroyacademy/web-app/issues
First up, clone the repo into a directory somewhere, e.g. ~/dev/web-app
Then, install a bunch of basic stuff:
xcode-select --install
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
brew install node
brew upgrade openssl
brew install postgresql
npm install sass -g
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/opt/openssl/lib/
python3 -m venv env
Add this to ~/.bash_profile
# this stuff is for the Fitzroy python app
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/opt/openssl/lib/
export FLASK_ENV=development
source ~/dev/web-app/env/bin/activate
This application requires Python 3 to work as well as the libraries listed in requirements.txt.
Assuming a Python 3 installation, typing this should work.
pip3 install -r ./requirements.txt
export FLASK_ENV=development
python3 app.py
source ./env/bin/activate
python3 app.py
To reseed the DB - this takes everything from stubs.py
and puts it in the database dev_db.sqlite
, which is our local databse:
- To change the database, edit
stubs.py
, and rmdev_db.sqlite
- Ensure your virtualenv is active and packages are installed, then
flask utils reseed-database
to reseed from stubs - Then reset the app via
python3 app.py
- Then hit localhost:5000.
- For an example lesson: http://localhost:5000/course/fitzroy-academy/how-to-have-good-ideas/seg_a
Here's the reseed code on all one line for copy/pasting:
rm dev_db.sqlite; flask utils reseed-database; python3 app.py
sass --watch static/assets/scss/fit.scss static/css/fit.css
In your /etc/hosts
file add the following lines:
127.0.0.1 fitz-dev.com
127.0.0.1 jeditemple.fitz-dev.com
127.0.0.1 some_fancy_subdomain.fitz-dev.com
...
Having such entries in hosts
file, the following urls are valid:
fitz-dev.com
jeditemple.fitz-dev.com
some_fancy_subdomain.fitz-dev.com
Note that for local development two domains are obligatory in hosts
file, namely fitz-dev.com
and jeditemple.fitz-dev.com
https://github.com/fitzroyacademy/web-app/issues
There's also a Dockerfile for your convenience which can be used instead. All of the application code, including Sass, will be live-reloaded if you edit any files locally with the Docker container running.
From the root repository directory, type:
docker build -t fitzroy-academy .
docker run -p 5000:5000 -e FLASK_ENV=development fitzroy-academy
Server will then be available here: localhost:5000 Example lesson will be available here: localhost:5000/course_intro/01
There is also a docker-compose.yml file which launches an instance of the app and a Postgres database.
When you change requirements.txt, you will need to rebuild the containers before running like so:
docker-compose build
docker-compose up # add -d to detach
docker-compose down
While docker-compose is running:
docker exec -it $(docker ps -f name="fitzroy-academy-app" -q) flask utils reseed-database
You can open a psql shell like the following:
docker exec -it $$(docker ps -f name="postgres" -q) psql -U postgres
If docker-compose is running, it will also be available via localhost on port 5001.
The previous method will work for both Docker and non-Docker local development with sqlite. If you're using docker-compose, you will need to run the script on the application container:
docker exec -it $(docker ps -f name="fitzroy-academy" -q) flask utils reseed-database
- Ensure the containers aren't running (
docker-compose down
anddocker ps
) - Run
docker volume list
, and find the postgres-container volume (should be likeweb_app_dbdata
) - Run
docker volume rm [volume name]
We use Flask-Migrate (Alembic) for database migrations. There are instructions on their page on how to use it properly, but here are the important ones when making DB changes:
flask db migrate [--message MESSAGE] [--sql] [--head HEAD] [--splice] [--branch-label BRANCH_LABEL] [--version-path VERSION_PATH] [--rev-id REV_ID]
Equivalent to revision --autogenerate. The migration script is populated with changes detected automatically. The generated script should to be reviewed and edited as not all types of changes can be detected automatically. This command does not make any changes to the database, just creates the revision script.
Check in your changes and migrations as usual in a PR.
# install latest versions of requirements
pip install -r requirements-minimal.txt
# update the pinned dependencies
pip freeze > requirements.txt
You can run tests with make check
before you might want to run make format
which will run black
formatting.
For profiling just run python3.7 profiler
and you will get 30 most time consuming methods for each request.
Here's the nomenclature and structure for lessons, in hopefully plain English, from top to bottom:
E.g. "Monash University", or "SHE Investments"
- UID
- Name
- Subdomain (unique, i.e. schoolname.fitzroyacademy.com)
- Wide logo (image upload, for horizontal logos)
- Square logo (image upload, for links + header)
- Colour choice (hex code)
- One or many Programs
- One or many users
- Institute admins
- Program managers
- Teachers (inhereted from programs)
E.g. "Global Challenges" or "Leave No-one Behind"
- UID
- Banner picture
- One or many Courses
- Program managers
- Teachers
E.g. "Accelerator", or "Global Challenges"
- UID
- Name
- Picture (forced to 16:9, user-uploadable)
- Slug
- Order
- Year: So we're not naming courses "My course 2020" and "my course 2021".
- ID code, for courses with school codes like
SCI2502
- One or many
Lessons
- One 16:9 Cover image
- 'Who it's for' (plain text / simple markdown) w/ translations?
- 'Length and workload' (plain text / simple markdown) w/ translations?
- 'What you'll learn (plain text / simple markdown) w/ translations?
- Paid or free (beta = everything is free)
- Access code (optional)
- Guest access? (i.e. can anonymous users access this course?)
- On the home page (i.e. does this show up on the home page?) NB: This option is only for super admins
- Parent Institute
- Whitelabel (removes FA branding from course)
- One or many affiliated users with access levels including:
- Enrolled students
- Teachers (admins)
These can be set by the admin, and control access via a short code.
- 4+ characters, letters and numbers
- Controls student access
- Most be unique
- Public listed: Shows up on public listings
- Public: Anonymous access allowed
- Public + login --> log in form --> play first lesson
- Public + code --> type in the code --> access
- Public + code + needs login --> type in the code --> check login --> enter course
- Public to certain domain: Needs login with certain domain(s), e.g. @harvard.edu
- Private: Doesn't show up on the site unless you type in the course code.
- Private + Needs login: Doesn't show up on the site unless you type in the course code, AND needs login
This means we have these options in course edit page:
- Listed | Public | Private
- Code (optional)
- Secure by domain(s)
- Featured or not (shows up on home page, only set by superadmin)
- When teacher is editing a course, can set one or many domains (or subdomains) for access
- Course shows "log in with your @domain email"
- This will require users to confirm their email address - how the hell do we deal with this?
This will require some sort of fancy "invite all these email addresses" thing? Do it later.
Codes are central to user login: They are UIDs for courses. If a teacher wishes to constrain access to a course, they can add a code AND demand that only logged in users can access.
- UID
- Title (short text) w/ translations?
- Image (16:9 ratio)
- Total time (from video segments)
- Order
- Strapline (slightly longer text) w/ translations?
- One or many Segments
- Resources
Resources are links, text and other stuff the student uses while completing lessons. They're at the lesson
level within courses.
A secondary table for translations of default resources is available to provide localized override of all applicable resource data fields.
- Resource links:
- Title
- Url
- Type:
- google_doc
- google_spreadsheet
- google_slide
- google_drawing
- youtube_video
- file_pdf
- file_image (for jpg/gif/etc)
- file_doc (.doc and .docx etc)
- medium_article
- url
- Language (EN | KH | PH | etc) - can be blank for unset.
- Featured (boolean)
- WYSIWYG resource chunk (just a big slab of HTML)
Links are added by pasting a url in the lesson editor
, the app scrapes the title, figures out the type, and guesses the language. The user can then modify from there.
The featured
flag means that link will show up in the right hand nav for easy access, while any non-featured
links and the HTML appear in the main resources pane.
No controls for uploading file at the moment. We'll let teachers manage that via dropbox, their own hosting, etc. Just links! :)
Segments are the chunks of video or text, which students watch / complete within lessons. Each has:
- UID
- Title w/ translations?
- Source
- Type
- Time (if source = video)
- Order
- Status
- Language (EN | KH | PH | etc)
- Subtitle languages availabile (EN | KH | PH | etc) (if source = video)
- ???
A secondary table for translations of default segments is available to provide localized override of all applicable segment data fields.
Segments can be one of the following types:
- Video (wistia)
- Video (YouTube)
- HTML (i.e. WYSIWYG page)
- Survey
Segments are of different types to indicate to the user their utility, i.e. a "practical video" is a screengrab of someone using a spreadsheet, wheras a "bonus" video is a nice extra and not needed to complete the course.
- Video
- Intro
- Standard
- Resource
- Practical
- Story
- Interview
- Case
- Bonus
- HTML
- Survey
Functionality for segment types: Type is mostly used just to set the correct icon type, however some functionality will be attached to type.
Locked
segments can only be watched after all previous segments are complete.Barrier
segments are used for disclaimers, etc, and must be completed before any subsequent segments can be accessed.Hidden
segments are accessible via the backend, but completely hidden to the student (i.e. so a teacher can hide segments without deleting them, and reveal them later)
These are changed through student interaction, and indicate progress:
- Untouched (not started or looked at in any way)
- Touched (started but not completed)
- Active (current item)
- Complete
The untouched and touched states are not shown to the student, but are used by teachers and admins.
Completeness is set by a % watched on a segment by segment basis.
Lesson creators will have to upload separate videos if they want differently-languaged audio. Each video within a segment may have one or more subtitle languages.
For YouTube, the subtitle language can be set in the embed, which should integrate with our preferred subtitle
language preference; for Wistia, it's based upon the user's browser, with a fallback to the first availabile language for the video.
The text and their translations will be stored in the database.
The options for surveys / questionnaires are:
- 1-6 happiness score, plus free text
- 1-10 net promoter score with free text
- boolean "stuck" (stuck or fine), with free text on stuck
- Free text
Each questionnaire part is either "mandatory" or "optional".
- Publicly accessible
- Locked with
course code
- Locked with
registered user
- Locked with
course code
ANDregistered user
And in the future, we will implement:
- Locked to
registered user whitelist
(requires invite codes, argh) - Locked to
domain
(i.e. only @institute.edu emails, or only @company.com emails)
This is expressed in the course edit with:
- Requires
course code
Y/N - Requires
registered user
Y/N
- UID
- Phone number
- First name
- Last name
- Primary email
- More emails
- Date of birth
- Display pic
- Color (randomly chosen from a list)
Everything but UID is optional (woah! really? I think so! Wow.)
- First name
- Last ssname
- Password
- Attach Google account
- Attach Facebook account
We're going to encourage users to log in via Google / FB, and this will attach to whatever email we can snag from that login.
This creates a weird flow: If a user does a Google auth, then later tries to sign in via that email without Google, they'll have to a) sign in via Google, or b) do a password reset, as they don't have a password yet.
NB: We need to figure out some clever way of allowing users to auth without email, especially in SEA on phones. Eek.
For kids (i.e. <16), less technical users, and public courses, it's likely users will never make an account with an email. At this point, we need a very simple way to auth them. Could we rember their device fingerprint perhaps? Or let them log back in with their first name and date of birth? Super simple?
We also want anon users to be able to very easily 'upgrade' to a registered user, and keep whatever watch / progress they've completed in a course.
In order, user permissions are:
Super
Dev team / FA staffInstitute
e.g. Melbourne University, or Tondo FoundationProgram
e.g. Leave No-one Behind, or SCI2502Teacher
e.g. 2021 Summer socent courseRegistered
studentGuest
student
Plus the weird, wonderful extra user: Researcher
!
All admins have full view and edit access to everthing below their perm level.
- Both
Guest
andRegistered
users can access locked courses with acourse code
, and the guests appear as "anon" in admin panels - If a course has the "guest access" flag set to 'no', only registered users can access.
- A
program
admin can create lessons, see all users activity within their program, but not other programs within that institute. - An
institute
level user can create programs and invite program admins, as well as dive into programs, lessons, etc, with full edit permissions
We'll implement this later coz is tricksy. Researchers exist outside of the typical 'school' structure, and can access anonymised, aggregate data at various levels.
This will be a delicately released feature, because it might expose lots of scary private data if we do it badly. There will probably be huge amounts of logging and tracking on who's seeing which data sets, and what they're doing with it.
For each user, the app should remember certain variables:
- Course level (left side menu):
- Overall hiding
- Which segments are completed or not
- Current, active segment
- Last segment viewed, within a lesson
- Last MM:SS viewed of the last viewed lesson
- Last course viewed (inherited by the lesson, above)
- Main viewing pane (segment level):
- Resources
- FAQs
- Transcript
- Teacher
- Analytics (admins only)
- Lesson level (sidebar on the right):
- Overall hiding
- Resources
- FAQs
- Transcript
- Teacher
- Analytics (admins only)
Other user preferences:
- Selected
app
language (buttons etc) - Display subtitles or not
- Preferred
video
language - Preferred
subtitle
language - Preferred
transcript
language
The above means that the app will attempt to find the preferred video and transcript language, and display that, or fall back to English.
This means a Cambodia user could choose an app language
of KH
, while watching videos in EN
, with subtitles displayed in KH
, and just for fun, transcripts
in EN
.
The above example has Khmer buttons, English spoken in the videos with Khmer subtitles, while reading transcripts happens in English. ARGH!
We translate the app at these levels:
- Overall chrome, i.e. app buttons / labels / etc
- Lesson level
- Resources
- FAQs
- Teacher
- Segment
- Video
- Transcript
- Survey
English is our fallback language. If a 'preferred' language is unvailable, fall back to English. This means some parts of a lesson may or may not be transcribed.
KH means Khmer, EN means English, PH means Tagolog:
- Selected
app
language: KH - Display subtitles or not Yes
- Preferred
video
language KH - Preferred
subtitle
language EN - Preferred
transcript
language EN
The above might read as "I speak Khmer as a first language, but want to see subtitles and read transcripts in English, to improve my English."
Let's mash this against an example lesson with its translations:
- Overall chrome: KH / EN / PH
- Lesson level
- Resources: EN
- FAQs: EN / KH
- Teacher: EN / KH
- Segment 1
- Video: EN
- Transcript
- Segment 2
- Video: EN
- Transcript: EN
- Segment 3
- Video: EN
- Transcript: EN / KH
- fitzroy.tech
- fitzroy.space
- fitzroy.design
- fitzroy.academy
- fitzroy.io
- Brainstorm feature with everyone
- Wireframe all the bits
- Wireframe all the bits
- Do a blog post with screen shots
- Show it to
dev
to see how hard it is - Change blog post until people are happy
- Do wireframes in XD or paper
- ???
- As a course editor I want to add youtube link as a video segment type
- As a course viewer I want to watch a youtube video
- As a analytics viewer I want to watch a youtube video
Bits:
- Biz logic
- User stories
- Brainstorm
- Wireframe
- Time budget
- What happened last sprint?
- Check error bars for last months retro
- Reprioritising existing features
- Change processes?
- Stakeholder update
- Debate this months work
- Write / update blog posts for next months
- Figure out hourly estimates for defined features
- Any curly problems noted down
- Backlog grooming
- Tech debt
- Cheap wireframing
- Present ideas and problems (what we learned)
- Match stories with hourly budget
- Prioritise tasks
- Put all the tickets in
- Make tickets + assignemnt make sense
- Updates from the boss on what else is going on
- What stuff do I want the app to do before this month?
- Testing: Do one day, or 2x .5 days
- Role based testing for a few days?
- See how much is left to burnt?
- Tech debt?