Contributing

Contributions to Pengin-Pi-3 are welcome — bug fixes, improvements to the core, and new util/ modules that other projects can use.

This page covers what we accept, where code belongs, how to submit it, and the conventions the codebase follows. Read Where code belongs before writing anything; it decides whether a contribution can be accepted at all.


Before you start

Security issues never go in a pull request. Pull requests on a public repository are public — the diff, the description, and every comment. A PR that fixes a vulnerability publishes it before anyone has updated. Report security issues to support@tobupengin.com; see Security.

For anything substantial, open an issue first. Describe what you want to change and why. A short discussion up front saves you writing a large change that doesn't fit the project. Typo fixes, small bug fixes, and documentation corrections can go straight to a pull request.

Read the Code of Conduct. It's short.


Where code belongs

Pengin-Pi-3 ships as one Django app, main, alongside util/ and templates/. Every contribution lands in one of those, and each has a strict rule.

main — only what is truly universal: code the core's own components use and that other apps can subscribe to. The user model, the RBAC framework, the Slug CMS, the wiki, history auditing. Test: would the core, or an unrelated app, call this?

util/ — middleware, custom libraries, helper scripts and functions, and utilities that main and apps both subscribe to. Generic by definition. Test: could an app with nothing in common with yours use it unchanged? Nothing in util/ implements auth or permission logic — that belongs in main/auth/.

Application features belong in applications, not in the core. A blog, a forum, a ticket queue, a job board — those are apps, built on the core, and they stay in their own package. We don't accept app features into main.

So the most common reason a contribution is declined isn't quality — it's placement. A well-written feature that only one kind of site needs is an app, not a core change.

See Architecture for how the core is organized.


What we accept

Welcome

  • Bug fixes anywhere in the core
  • Security hardening — via support@, not a PR
  • Improvements to existing core systems: the slug editor, wiki, RBAC, SEO, dynamic content types
  • New util/ modules that are broadly useful — a helper you wrote for your own project may well be one
  • Additions to main that are genuinely universal
  • Documentation fixes, on this wiki or in the repository
  • Deployment and configuration improvements

Declined

  • Application features proposed for the core
  • Changes that tie the core to one organization, one deployment, or one cloud provider without a fallback
  • New hard dependencies on external services — the core must still run with every optional integration unconfigured
  • Large rewrites without a prior issue and discussion

Application branches on the public mirror are outside the supported surface and carry no maintenance commitment. Pull requests against them may not be reviewed. See Info.


How to contribute

Pengin-Pi-3 is developed on Tobu Pengin's internal GitLab. GitHub is a public mirror of it, and it's where outside contributions come in.

  1. Fork the repository on GitHub
  2. Branch from main in your fork, named for the change — fix/slug-delete-redirect, util/csv-export
  3. Make the change, following the conventions below
  4. Open a pull request against main, describing what changed and why

A maintainer reviews it on GitHub. Review happens in the PR's comments — expect questions and change requests; they're normal, not a rejection.

When it's accepted, it's merged on GitLab, not on GitHub. A maintainer applies your commits to the internal repository, and the mirror syncs them back. The GitHub PR is then closed with a note saying where it landed. Your authorship is preserved in the commits.

This is why an accepted PR shows as closed rather than merged on GitHub. That's expected.

Who decides

Pull requests are reviewed and merged by the Pengin-Pi support team, which maintains the project with sponsorship from Tobu Pengin, L.L.C.


Licensing

Pengin-Pi-3 is GPLv3. By submitting a contribution, you agree it may be distributed under that license, and you confirm you have the right to submit it.

There is no CLA and no copyright assignment. You keep the copyright on your contribution.

Don't submit code copied from projects under incompatible licenses. If you bring in a third-party library or snippet, say so in the PR and name its license. See License.


Conventions

The codebase is consistent on purpose. Match it.

Models

  • UUID primary keys on every model:

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    
  • HistoryMixin plus a paired AbstractHistory model where edits should be audited, with save_history() called before the save. See History and Auditing.

  • SitemapEntry on models with public pages. See SEO and Sitemaps.

  • No hardcoded user ids. User has a UUID key.

Views

  • Class-based views, composed from mixins rather than reimplementing behavior. View last in the bases. See Mixins.
  • Rate limiting on every POST that changes state.
  • reCAPTCHA on anonymous-facing forms with real consequences.
  • Permission checks go through main.auth. Never re-derive "is this user a manager" yourself — call the framework.
  • New access-gating decorators set requires_auth = True on the wrapped function, or the sitemap will advertise the pages they protect. See RBAC.

Graceful degradation

The core runs with every optional integration unconfigured — no SES, no S3, no reCAPTCHA, no Postgres. Keep it that way. A new integration must detect missing configuration at startup and fall back to a safe local or no-op default, printing one line saying so. See Configuration.

Templates

  • Bootstrap 5 throughout
  • Extend layout.html or a shipped layout, using the existing block names
  • {% load static %} and {% static %} — never Jinja's url_for

URLs

  • App and core routes go above the wiki suffix routes in main/urls.py. Anything ending create/ or edit/ registered below them is captured by the wiki. See Wiki Module.
  • Name every route. Unnamed routes are invisible to the sitemap.

Migrations

The core has one migrations directory, main/migrations/.

  • Include the migration your change needs in the same PR

  • Never edit an existing migration — add a new one

  • Check nothing is missing before you push:

    python manage.py makemigrations --check --dry-run
    

Comments

The codebase explains why, not what. Most non-obvious decisions carry a comment saying what problem they solve and what went wrong without them — see util/slug_dynamic_data.py or main/models/mixins.py for the style. If your change makes a non-obvious choice, write the comment that would have saved the next person from undoing it.

Commits

A short subject line saying what the commit does, and a body explaining why when it isn't obvious:

Fix HistoryMixin leaking encrypted field plaintext into snapshots

value_from_object() runs from_db_value(), so the snapshot loop saw
decrypted plaintext and wrote it straight into the history table.
Re-encrypt before storing.

One logical change per commit. Don't mix a fix with unrelated formatting.


Testing your change

There is no automated test suite yet. Until there is, verify by hand, and say in the PR what you checked.

python manage.py check
python manage.py makemigrations --check --dry-run
python manage.py runserver

Then exercise the change the way a user would. For anything touching permissions, check it as root, as a department manager, as a plain staff member, and as an anonymous visitor — python manage.py check_auth <email> reports what the framework thinks a given user can do.

Check it with integrations off. Run once with no SES, S3, or reCAPTCHA keys in .env. If your change breaks the unconfigured path, it breaks every fresh install.

Management commands under main/management/commands/ are the current home for test and diagnostic scripts — check_auth.py is the pattern to follow.


Documentation

If your change alters behavior described on this wiki, update the page, or say in the PR which pages are affected and a maintainer will. A change that contradicts the docs leaves both wrong.


Tobu Pengin interns

Interns in the Tobu Pengin program contribute through the internal GitLab rather than GitHub. See Contribution Workflow and Program Training.


Next

Pages Here

No sub-pages yet.

Page Info

Wiki: Docs

Last edited on Sep 21, 2026 by Stuart Anderson

Maintainers

Editor Last Activity
Stuart Anderson creator Sep 21, 2026