Preface
An Online Judge (OJ) is a platform where users submit code to solve problems and receive immediate feedback. DMOJ is a flexible, open-source OJ that supports customization. I’m a TA for a CS course covering Java and data structures, and I set up DMOJ to host practice problems and auto-grade submissions. This post documents the environment, installation notes, and a few customizations that worked well for our class.
Environments
- Server: 8 vCPU, 16 GB RAM
- OS: Ubuntu 24.04 LTS
Installation
The official DMOJ docs provide a thorough step-by-step guide. The points below highlight gotchas and tweaks I encountered on Ubuntu 24.04.
Use a non-root user
Create a dedicated user and elevate only when necessary.
sudo adduser oj --ingroup sudo
su - oj
Python virtual environment
When you reach the step:
python3 -m venv dmojsite
Ubuntu may prompt you to install the venv package. On 24.04 (Python 3.12), run:
apt install python3.12-venv
Common build/install fixes
mysqlclient build error (missing
pkg-config):apt install pkg-config pip install mysqlclientMissing
pkg_resourcesduringmanage.py check:pip install setuptoolslxml.html.cleanmoved error duringmanage.py migrate:ImportError: lxml.html.clean module is now a separate project lxml_html_clean.Fix:
pip install lxml_html_cleanCelery “NoneType has no attribute ‘Redis’” when testing workers:
pip install redis
Selected settings.py changes
Disable public registration (we’ll batch-create accounts):
REGISTRATION_OPEN = FalseRestrict UI language to English:
LANGUAGE_CODE = 'en-us' LANGUAGES = [ ('en', _('English')), ]Editable problem data (allow uploads via the site UI):
DMOJ_PROBLEM_DATA_ROOT = '/mnt/problems/'Ensure the path exists and is writable by the DMOJ process:
sudo mkdir -p /mnt/problems sudo chown -R oj:oj /mnt/problems
Follow the remaining steps in the docs to start the Django site, Nginx, etc.
Judge server on Python 3.12 (build from source)
As of this deployment, the PyPI judge package doesn’t provide wheels compatible with Python 3.12, so you’ll need to build the judge from source.
# Build essentials
apt-get install -y build-essential python3-dev
# Isolated venv for the judge build
python3 -m venv ~/judge-src-venv && source ~/judge-src-venv/bin/activate
# Tooling for building
pip install -U pip setuptools wheel "Cython>=3.0"
# Clone and install the judge (recursive for submodules)
git clone --recursive https://github.com/DMOJ/judge-server.git
cd judge-server
pip install -e .
Create your judge.yml according to the DMOJ docs.
At this point, you should have a functional OJ.
Customizations
This instance is for internal course use, so I made a couple of tweaks.
Batch-create users
Because REGISTRATION_OPEN = False, we’ll create accounts ourselves. In the Django shell:
python manage.py shell
from django.contrib.auth.models import User
from judge.models.profile import Profile
username = "jdoe123"
user = User.objects.create_user(
username=username,
email="student@example.edu",
password="changeme123",
first_name="Jane",
last_name="Doe",
)
Profile.objects.create(user=user)
To scale this up, read a CSV (e.g., username,first_name,last_name,email) and loop over rows to create users and profiles. You can have an LLM generate a quick script or contact me if you’d like the version I used.
Show full names on the leaderboard
By default, the leaderboard shows only usernames. Since we use unique IDs as usernames, adding full names helps instructors and students recognize who’s who. In our install, we edited the table cell rendering the username. Your path may vary slightly by version, in ours it was:
site/templates/user/base-users-table.html
Replace the username cell with:
<td class="user-name">
{{ link_user(user) }} — ({{ user.user.first_name }} {{ user.user.last_name }})
</td>
(Note: link_user(user) yields the linked username. The user.user.\* chain accesses the related Django User model from the DMOJ profile object.)
Create multi-choice problems for assignment/exam
The DMOJ provides Text language for submission and Easy check for case insensitive judging, which can be used for a multi-choice problem. But during the assignment/exam, we don’t want student get the result immediately. In order to hide the result, I’ve added a customized checker called AllAC, as the name suggests, it always return AC. The code of the checker is as below:
# judge-server/dmoj/checkers/all_ac.py
def check(process_output: bytes, judge_output: bytes, **kwargs) -> bool:
return True
The file should be stored at judge-server/dmoj/checkers, and make sure import this at __init__.py. If you want to select this check on the frontend, you can change the site/judge/models/problem_data.py.
Use this checker as the pretest, and make sure the real answer is not pretested. Then it’s good to use for the assignment/exam.