The bridge pattern

The single most important architectural idea in EduPrime: apps never import each other directly. Instead, a tiny bridge module joins two apps, and the base app exposes no-op stubs that the bridge fills in. This is what lets you install any subset of the suite and still get full integration.

The problem it solves

If ep_transport imported ep_fees directly, you could never install Transport without Fees, and a fee-model change would break Transport. EduPrime avoids this entirely.

How a bridge works (real example)

Take billing a bus assignment. In the base ep_transport module, the assignment model has no-op stub methods:

# ep_transport (base) — stub, does nothing on its own
def _create_fee_line(self):
    return  # real logic only exists if the fees bridge is installed

Install ep_fees_transport_bridge (which depends on both ep_transport and ep_fees, and is auto_install: True) and it overrides the stub with the real implementation and contributes the linking field:

# ep_fees_transport_bridge — real implementation
class EpTransportAssignment(models.Model):
    _inherit = "ep.transport.assignment"
    fee_assignment_line_id = fields.Many2one("ep.fee.assignment.line")

    def _create_fee_line(self):          # overrides the stub
        if self.staff_commuter: return   # staff ride free
        fa = self.env["ep.fee.assignment"].search([...])
        line = self.env["ep.fee.assignment.line"].create({
            "category_id": self.env.ref("ep_fees.fee_cat_transport").id,
            "amount": self.effective_fee, "transport_assignment_id": self.id})
        self.fee_assignment_line_id = line.id
        fa._compute_totals()

When it fires

The assignment's own lifecycle buttons call the hook: action_activate_create_fee_line(), and action_pause / action_cancel_remove_fee_line(). With no bridge installed those calls hit the no-op stubs and nothing happens — Transport runs standalone.

Every bridge in the suite

Bridge moduleJoinsSource → target objectTrigger
ep_fees_transport_bridgeTransport ↔ Feesep.transport.assignmentep.fee.assignment.lineactivate/pause/cancel
ep_fees_hostel_bridgeHostel ↔ Feesep.hostel.allocationep.fee.assignment.lineallocation
ep_fees_meals_bridgeMeals ↔ Feesep.meals.subscriptionep.fee.assignment.linesubscribe
ep_fees_library_bridgeLibrary ↔ Feesoverdue fine → ep.fee.assignment.linefine raised
ep_academic_library_bridgeLibrary ↔ Academicep.library.course.reserve → class reading listsreserve created
ep_assessment_gradebook_bridgeExams ↔ Gradebookep.assessment.attemptep.gradebook.entryattempt graded
Why auto_install — Each bridge is auto_install: True, so the moment both apps it joins are installed, the bridge installs itself. You never tick a box; integration just appears.
Was this page helpful?