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 mysqlclient
    
  • Missing pkg_resources during manage.py check:

    pip install setuptools
    
  • lxml.html.clean moved error during manage.py migrate:

    ImportError: lxml.html.clean module is now a separate project lxml_html_clean.
    

    Fix:

    pip install lxml_html_clean
    
  • Celery “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 = False
    
  • Restrict 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.