Source code for the website
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 KiB

Contributing Guide

This document shows you how to get the project and run various local dev tasks.

About The Application

Before diving into the setup, a quick note about how the application is designed:

Although it uses a database, the site is actually 100% static. There's no runtime way to alter the database content, and every page is cached in production.

This means that when you make a change to the data, you simply wipe the database out and redo the "reset" process (see below) to regenerate your data.

PostgreSQL specifically is required due to the usage of JSON data types.

Docker Setup

Docker can be used to simplify the setup of the infrastructure required to run the application locally.

  1. Install GNU make and git.

  2. Install docker

  3. Install docker-compose

  4. Clone the source code locally:

     cd src
     git clone
  5. cd into the source directory:

     cd momw
  6. Start the application:

     sudo make compose-up

Once you see Starting development server at in the terminal output, you can open up that URL in your browser to see the local copy of the application running. It will say "docker" in the site title.

There's a hardcoded wait in the service command; this is to ensure that the database container is ready before the application tries to run. If the wait isn't long enough, simply edit that line.

If any data changes are made, that volume will need to be deleted (note the psql container needs to be deleted first):

sudo make docker-reset

Then simply re-run sudo make compose-up.

Manual Setup

These steps should work for any unix-based system (Linux, macOS).


To make sure that the following instructions work, please install the following dependencies on your machine:

  • Git
  • chromedriver (for tests)
  • geckodriver (for tests)
  • GNU make
  • PostgreSQL
  • Python3 (the latest 3.X release is desireable)
  • python3-pip


To get the source, clone the git repository via:

$ git clone

This will clone the complete source to your local machine. Navigate to the project folder and install all needed dependencies via pip3:

$ pip3 install --user --upgrade -r requirements.txt

If desired, the --user flag may be omitted but this is not advised.

Create a psql database, role, and user called momwdb with login and createdb privileges.

Next, a database user (and related unix account) are created:

$ sudo useradd momwdb
$ sudo -u postgres psql -c 'create user momwdb with encrypted password \'postgresqlpassword\';'

Create the required databases:

$ sudo -u postgres createdb momwdb
$ sudo -u postgres createdb test_momwdb

Add privileges to the app's database user:

$ sudo -u postgres psql -c 'alter user momwdb createdb;'
$ sudo -u postgres psql -c 'alter user momwdb login;'

And finally, set the owner on the databases that were created:

$ sudo -u postgres psql -c 'alter database momwdb owner to momwdb;'
$ sudo -u postgres psql -c 'alter database test_momwdb owner to momwdb;'

If you want to not require a password to do things like psql as the app's database user, add the following to your sudoers config:

<your username> ALL=(momwdb) NOPASSWD: /bin/psql,/bin/createdb,/bin/dropdb

Adjust the paths to the binaries as needed.

Running The Application

With that, you should now me able to run the app and related tasks:

make reset
make server

If you just want to run a local version of the site, you should now be set.


Tests depend on a working install of both the chrome and gecko webdrivers for selenium, as well as a working local manual install of the application. Docker-based setups are not yet supported. Their installation and setup is (for now) outside the scope of this document, but all that's necessary is to have chromedriver and geckodriver binaries for your OS in your $PATH.

To run tests:

make test

After a short time, a chrome or chromium instance will pop up with a note about it being controlled by software. After a bit more time it will close and then firefox will open and do the same. This is normal and part of the test suite.

Source formatting/linting

Source is automatically formatted as part of the make test target, using python black. Please ensure it's installed for your system.

Linting also happens as part of the test target, be sure the following rules are set at minimum:

ignore = E501, E402, W503
max-line-length = 160


Any commit to any branch on the primary repository will kick off a buildbot build.

Contributing/Submitting changes

  • Check out a new branch based on beta and name it to what you intend to do:

      $ git checkout -b my-cool-feature origin/beta
  • Use one branch per fix/feature

  • Make your changes

    • Run your tests with make test.
    • When all tests pass, everything's fine.
  • Commit your changes

    • Please provide a git message that explains what you've done.
    • Commit to a forked repository on our Gitea instance.
  • Make a pull request

    • Make sure you send the PR to the beta branch.
    • A passing Buildbot build is required before any changes are merged.

Tests are expected for any submitted changes. If it's specific to a particular page, or adds a new page, related selenium tests should be written.

How do i...?

Add a new mod

Mods exist in .py files in this directory. They are divided by category, each file is named after their respective category.

As for how to decide what a mod's category is: use your best judgement. If you think a new category is needed, you can add those via the file.

Here's a sample of what mod data might look like, fully annotated with comments:

  # Optional; a list of strings which are mod names. Any strings that aren't mod names are ignored.
  "alt_to": ["Mod Name 1", "Mod Name 2"],
  # Required; this can be a raw string, or you can use the nexus_user() helper as seen below.
  "author": nexus_user("1234567", "Some User Name"),
  # Required; the category is always defined in each file. Refer to existing data to see how it should be.
  "category": some_category,
  # Required; A string, one of: "fully working", "partially working", "not working", or "unknown".
  "compat": "fully working",
  # Required; a string, formatted: YYYY-MM-DD HH:MM:SS -OFFSET
  "date_added": "2090-04-11 21:00:00 -0500",
  # Optional; also a string, formatted: YYYY-MM-DD HH:MM:SS -OFFSET
  "date_updated": "2090-08-21 21:00:00 -0500",
  # Required; a potentially very long string. Use python triple quotes("""Hello""") for long/multi-line descriptions.
  "description": "A very good description of this mod.",
  # Optional; A string for the URL at which the mod can be directly downloaded.
  "dl_url": "",
  # Optional; HTML-formatted string to be viewed as "extra cfg".
  # See this as an example:
  # Note that when you use "extra_cfg" you should also use "extra_cfg_raw" (see below).
  "extra_cfg": "HTML-formatted extra cfg",
  # Optional; raw text string used by the CFG Generator for a mod's extra_cfg.
  # See this as an example:
  "extra_cfg_raw": "Text only extra cfg",
  # Optional; if a mod has multiple folder paths you can represent them like this.
  "folder_paths": {"01": "00 Core", "02": "01 Extra Stuff"},
  # Optional; Set this to True (no quotes) if the mod requires a BSA. Otherwise this can be omitted.
  "has_bsa": False,
  # Optional; Set this to True (no quotes) if the mod requires a plugin. Otherwise this can be omitted.
  "has_plugin": False,
  # Required; either True or False (no quotes) if the mod is or isn't active, respectively.
  "is_active": True,
  # Required; a string that is the name of the mod.
  "name": "A Very Awesome Mod",
  # Optional; Set this to True (no quotes) if the mod's plugin needs to be cleaned with tes3cmd.
  # Otherwise, this can be omitted.
  "needs_cleaning": False,
  # Optional; similar to category, tags are defined in each file.
  # You may need to define a tag if it isn't already.
  "tags": [tag_high_res],
  # Required; similar to "author" above, you may provide a raw string for the URL or use the nexus_mod() helper.
  "url": nexus_mod("98765"),
  # Optional; a potentially large string (like description) for the mod's usage notes.
  "usage_notes": "Here is how to use this mod ...",

You can verify your formatting with the black command (either run make test-black or invoke black by hand). If your formatting is wrong, black will automatically fix it, unless there's a major syntax problem.

Edit Mod Data

As noted above, mods exist in .py files in this directory. They are divided by category, each file is named after their respective category.

Open the file that corresponds to the category for the mod you wish to edit, find the mod in the file, and edit as needed. See above for a fully annotated sample of mod data.

If you are making a change that will alter the URL of a mod (e.g. a name change that affects the slug), please see below for a note on preserving old links.

Add a non-mod page

Non-mod pages (such as the main index of the website) are defined in various ways.

First, there's the view function. If the page is to be totally static HTML, then you can use the static_view() view function.

If the page needs to be dynamic in some way, then you would define a new view function in the file.

In either case, you will also need a .html file in the templates directory.

Then you have the URL route, defined in

This is how URLs are matched to view functions, see the file for examples of this in action in various ways.

Edit a non-mod page

Editing a non-mod page isn't much different from adding one. The same patterns apply, except you'd be working with existing content.

If you are making a change that will alter the URL of a page, please see below for a note on preserving old links.

While developing this website, care is taken to not cause any link rot.

This codebase contains a legacy_url_redirect() view function for ensuring that old URLs stick around while pointing to updated content as needed. Please see here and throughout that file for several examples of how to use it.

The main thing to remember is: if you're changing a URL, make sure the old one still works after the change. Either via the helper view mentioned above or some other means as needed.