UlasKelas Backend runs course reviews, catalog sync and SLCM import on Django REST Framework
Backend Repository for Ulas Kelas RISTEK Fasilkom UI
At a glance
- What is it?
- The UlasKelas backend is a Django REST Framework service for a campus review and study-planner app, with a Postgres 15 container, APScheduler reminders, a SunJad course catalog sync command and a Playwright based SLCM import.
- Who is it for?
- UlasKelas Backend suits a team maintaining an Indonesian university review and study-planning app that must pull course data from SSO and SunJad and import a student's IRS period from SLCM, since those two integrations are the hard part and are already solved here.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A campus review backend with a directory of features
The UlasKelas backend is described as the final project backend of a Mobile Development SIG 2021, written with Django Rest Framework. The application domain is visible in the README and in the source layout: course reviews, a course calculator, a TanyaTeman feature, a leaderboard, device tokens for push notifications, an SSO integration, a course catalog updater and a leaderboard updater.
The top-level entries show how that domain is split. There is a main directory, a UlasKelas directory that the setup instructions refer to for the .env sample, and separate sso, courseUpdater, leaderboard_updater and live_config directories, plus manage.py at the root. Deployment is handled by a Dockerfile, a docker-compose.yml, a docker-compose-prod.yml and a deployment.sh script.
The README also points at a database schema image and describes it as guidance to the data used in the project. That is worth noting for anyone extending the code, because the schema is documented as a picture rather than as migration history, and the migrations themselves are the real source of truth for the current tables.
For a reader, the shape of the project is a mature student project that has grown well past its original scope. The feature list in the README covers notifications, catalog synchronisation, cleanup of invalid course codes and a browser-driven import from the university system, which is far beyond a course review CRUD service.
Local setup: virtualenv, Postgres container, migrate, run
The local workflow is documented step by step, and every step maps to a file in the repository, which makes it easy to reproduce.
You move into the backend directory first:
cd backendThen you create a virtual environment and activate it:
python3 -m venv envThe environment file comes from a sample that the README says to duplicate and rename to .env, which is how the database URL and the service keys reach the settings. The database is a Postgres container started with:
docker-compose up -dWith the container running, the Python dependencies are installed and the development server is started:
source env/bin/activate
pip install -r requirements.txtpython manage.py runserverSchema changes are handled with the standard Django pair, and the README is explicit that the database container has to be running first:
python manage.py makemigrations
python manage.py migrateThe compose file for development is small and only defines one service. The Postgres service is named postgres, the container is called ulas-pg, the image is postgres:15-alpine, the data lives in a named volume ulas_db, and the credentials and port mapping are all set to simple defaults:
postgres:
container_name: ulas-pg
image: postgres:15-alpine
volumes:
- ulas_db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- "5432:5432"
restart: unless-stoppedThose defaults are fine for a laptop and wrong for a server, and the README says as much in a different context when it tells you to use database management software and the credentials from settings.py. Anyone deploying this should treat the compose file as a development fixture.
The image runs deployment.sh, and production swaps the compose file
The Dockerfile is short enough to read in full, and it explains how the service starts:
FROM python:3.8
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
RUN chmod +x ./deployment.sh
CMD ["./deployment.sh"]The entrypoint is not a web server command but a shell script, deployment.sh, which the image makes executable. Whatever that script does, and the README does not reproduce its contents, it is the process that keeps the container alive.
The Python version choice deserves attention. The image is python:3.8, and the pinned dependency set matches that era. Django is pinned to 3.1.2 and djangorestframework to 3.12.2, with psycopg2 at 2.9.1, gunicorn at 20.0.4 and APScheduler at 3.8.0. The rest of the list covers the expected pieces of a Django service, django-environ for settings, django-cors-headers, django-filter, sentry-sdk, whitenoise for static files, python-cas and django-sso-ui for the SSO side, plus firebase-admin, selenium and rapidfuzz for the newer features.
Production uses a separate compose file, and the README documents the rebuild procedure for when code changes:
docker-compose -f docker-compose-prod.yml down && sudo docker-compose -f docker-compose-prod.yml build && docker-compose -f docker-compose-prod.yml up -dThat line tears the stack down, rebuilds the images and starts them again, and it needs sudo for the middle step on a typical Linux host. The database access instructions point at the same container names, with a shell into the database and a psql session, and the README notes you can also use database management software with the credentials from settings.py.
sync_courses pulls the catalog from SunJad without wiping bad data
The course catalog is not maintained by hand. The README documents a management command that populates every supported S1, D3 and D4 program after migrations have been applied:
python manage.py migrate
python manage.py sync_courses --allA single organization can be refreshed on its own, which is what the org-code form is for:
python manage.py sync_courses --org-code 01.00.12.01The failure handling is the interesting part, because it is where a sync against an external university system usually goes wrong. SunJad requests use a 5-second connection timeout, a 20-second read timeout, and up to three attempts with exponential backoff for transient failures. Course mappings are deactivated only after a valid catalog has been fetched, and failed or untrusted responses never cause mass deactivation. Inactive courses stay in the database so that existing reviews and calculator history remain accessible, which means a catalog change never breaks a user's saved data.
The catalog also has a validation layer. Before the first sync after the course code validation was deployed, the README asks you to audit invalid legacy courses with a dry run:
python manage.py cleanup_invalid_course_codesAnd to apply the cleanup only after reviewing the output and taking a database backup:
python manage.py cleanup_invalid_course_codes --applyThat cleanup deactivates invalid catalog mappings, and deletes an invalid course only when it has no review, bookmark, calculator, course-semester or TanyaTeman references. Referenced courses are kept for manual remediation. A failed program is reported at the end without stopping the remaining programs, and a program with no SunJad catalog yet is reported as unavailable without changing its existing course statuses.
Reminder notifications and a scheduler you can run elsewhere
Push reminders are delivered through Firebase, and the device tokens are registered over a single API route, POST /api/device-tokens. In production the service needs FIREBASE_ENABLED=true and either FIREBASE_CREDENTIALS_BASE64 or Application Default Credentials, and Firebase's APNs integration has to be configured for iOS delivery.
The reminders themselves are managed commands on a scheduler configured for the Asia/Jakarta timezone:
0 16 * * 5 python manage.py send_notification_reminders --type calculator
5 0 * * * python manage.py send_notification_reminders --type reviewThe calculator reminder runs on Friday at 16:00 Jakarta time, and the review reminder runs at 00:05 every day but safely skips every day except the final calendar day of the month. Both commands use database deduplication and can be retried, which is the property that matters for a scheduler. A cron entry that fires twice, or a job that was killed halfway through, will not double-send.
That retriability is also what makes an alternative worth considering. Instead of running these commands from the managed scheduler inside the application, the same management commands can be driven by an external cron or a job runner on the host, since they are idempotent and take a --type argument rather than reading state from a web request. The trade-off is that the external runner needs the environment and the database credentials in place, and the README notes for the catalog sync that the scheduler must retain stdout and stderr and alert on a non-zero exit status, which is an operational requirement you own in that arrangement.
The catalog sync has the same shape, with the daily schedule given as 02:00 Asia/Jakarta, or 0 19 * * * in UTC when the scheduler cannot take an explicit timezone, and the README asks for the all-program command to be run once immediately after the first deployment so the catalog is populated without waiting for the schedule.
SLCM import: a browser session you log into yourself
The SLCM IRS import is the most unusual feature, and the README is careful about its boundaries. It runs only as a local management command. The temporary browser it drives comes from a separate requirements file:
pip install -r requirements-slcm.txt
python -m playwright install chromiumA preview run for a specific profile and semester takes a username, a semester, the URL of the IRS page and the dry-run flag:
python manage.py import_slcm_irs \
--username example.username \
--semester 1 \
--irs-url "https://slcm.ui.ac.id/path-to-irs" \
--dry-runThe command opens an isolated browser at the given page and the person using it completes the SLCM login themselves, with no terminal confirmation needed. After authentication the command previews the latest academic period that contains courses, and removing --dry-run confirms the import in the same browser session. Existing calculator courses are skipped, and SLCM codes that are missing from the local catalog are reported. A --login-timeout option overrides the default five-minute login window. The README states plainly that the command does not save SLCM credentials, cookies, page HTML or browser storage.
For the frontend, the same capability is exposed over HTTP. The configuration takes SLCM_IRS_URL for the fixed IRS page and SLCM_BROWSER_PUBLIC_URL for the remote browser, and the production Compose file includes a single session Chromium and noVNC service for the login popup. A session is created with POST /api/slcm-autofill/sessions, where the given_semester field is optional and the backend derives the student's current semester from their NPM entry year and the current academic period. The client polls GET /api/slcm-autofill/sessions/{session_id}, displays the preview once the status is ready, and finishes with POST /api/slcm-autofill/sessions/{session_id}/confirm, while an unfinished session is cancelled with DELETE on the same path. The remote browser defaults to a touch-enabled 430x932 kiosk viewport, so the Chromium tabs and address bar do not eat the mobile login area, and noVNC scales that fixed portrait framebuffer to whatever space the frontend has.
No licence, no releases and a pinned dependency set
Three things a team should settle before building on this repository.
The first is the licence. The repository does not state one, so there is no clear grant of rights to copy, modify or redistribute the code, and no copyright header information is given either. For a student project that is forgivable. For anything commercial it is a blocker until the author publishes a licence.
The second is the release history. There are no GitHub releases, so there is no version to pin in a deployment manifest and no changelog to read before an upgrade. The last push was on 2026-09-22, which is recent enough that the README describes features such as the slcm autofill sessions that are newer than the original project, and also recent enough that the code can change under you. Pinning to a commit hash is the only repeatable option the repository offers.
The third is the dependency pinning. Django 3.1.2, djangorestframework 3.12.2, requests 2.25.0, urllib3 1.26.2 and lxml 4.9.1 are all pinned to versions from before the current generation, and the container image is python:3.8, which is itself past its upstream support window. Those pins make the build reproducible, which is the point of a requirements file written this way, but they also mean the service is running on a stack that receives no security updates from upstream unless someone deliberately upgrades it. That upgrade work is part of the cost of adopting this code, and it is the first thing to put on the list if the team is serious about running it in production.
Editorial conclusion
UlasKelas Backend suits a team maintaining an Indonesian university review and study-planning app that must pull course data from SSO and SunJad and import a student's IRS period from SLCM, since those two integrations are the hard part and are already solved here. It is the wrong starting point for a general production service, because the dependency set is pinned to Django 3.1.2 on a python:3.8 image, the licence is not stated in the repository, and the project publishes no releases, so every deployment is built straight from a commit. Before adopting it, read the Dockerfiles and docker-compose files for the deployment shape you need, confirm the Postgres credentials in the compose file are changed before any exposure, and check the pinned Django version against your own security requirements.
Frequently asked questions
How do I run the UlasKelas backend locally?
Move into the backend directory, create and activate a virtual environment, duplicate the .env sample as .env, start the database with docker-compose up -d, install the requirements with pip install -r requirements.txt, then start the server with python manage.py runserver.
What database does the UlasKelas backend use in development?
A Postgres 15 Alpine container named ulas-pg, defined in docker-compose.yml with a named volume ulas_db and port 5432 published on the host. The compose file ships default postgres credentials, so they should be changed before the database is reachable by anything else.
How does the backend update its course catalog?
Through the sync_courses management command, run with --all for every supported program or with --org-code for one SSO and SunJad organization. It uses a 5-second connection timeout, a 20-second read timeout and up to three attempts with exponential backoff, and it deactivates mappings only after a valid catalog has been fetched.
How do the push notification reminders get scheduled?
With the send_notification_reminders management command, using a --type argument such as calculator or review, run on a managed scheduler in the Asia/Jakarta timezone. Both commands use database deduplication and can be retried without sending duplicates.
Does the SLCM import save my university credentials?
No. The import_slcm_irs command opens an isolated browser and you complete the SLCM login yourself, and the README states that it does not save SLCM credentials, cookies, page HTML or browser storage. A --dry-run previews the period before anything is written to the database.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ristekoss-ulaskelas-backend)Was this useful?