Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions docs/working_with_templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,32 @@ SQLAdmin and in the `content` block it adds custom HTML tags:

```python title="admin.py"
class UserAdmin(ModelView, model=User):
details_template = "custom_details.html"
details_template = "sqladmin/custom_details.html"
```

## Overriding default templates

If you need to change one of the existing default templates in SQLAdmin such that it affects multiple pages, you can do so by copying the existing template from `templates/sqladmin` into your `templates/sqladmin` directory. It will then be used instead of the one in the package. For example if there is some Javascript you want to run on every page you may want to do it in layout.html like so:
The recommended way to customize existing default templates (like adding a script to every page) without redefining the entire HTML structure is to extend the original template using the `sqladmin_original/` prefix. This allows you to selectively override or append to specific Jinja blocks using `{{ super() }}` while preserving the rest of the template.

For example, if there is some Javascript you want to run on every page, you can extend the original `layout.html` and append to the `tail` or `tail_js` block like so:

!!! example

```html title="myproject/templates/sqladmin/layout.html"
...
</div>
</div>
{% endblock %}
{% extends "sqladmin_original/layout.html" %}

{% block tail %}
{% block tail_js %}
{{ super() }}
<script type="text/javascript">
console.log('hello world');
</script>
{% endblock %}

```

**Alternative method (Full override):**

If your customizations are so extensive that using blocks isn't sufficient, you can completely replace a default template. To do this, copy the existing template from SQLAdmin's `templates/sqladmin` into your project's `templates/sqladmin` directory without using `extends`. It will then be loaded instead of the one in the package, bypassing the original entirely.

## Customizing Jinja2 environment

You can add custom environment options to use it on your custom templates. First set up a project:
Expand Down Expand Up @@ -123,3 +126,28 @@ Usage in templates:
{{ value }} is not file path
{% endif %}
```

# Template Blocks
The SQLAdmin templates use blocks to allow easy customization and extension of the templates. Here is a list of the main blocks available SQLAdmin templates:

| Block Name | Description |
|------------|-------------|
| `head_meta` | Page metadata in the header |
| `head_css` | Various CSS includes in the header |
| `head` | Empty block in HTML head, in case you want to put something there |
| `head_tail` | Additional HTML elements before the closing `</head>` tag |
| `body` | The main body of the page |
| `main` | The main container for the page content |
| `content` | The main content area where page-specific content is rendered |
| `tail` | Additional HTML elements before the closing `</body>` tag |
| `tail_js ` | Various JavaScript includes before the closing `</body>` tag |
| `create_form` | The form used in the create view containing fields |
| `edit_form` | The form used in the edit view containing fields |
| `details_table` | The the div containing the records details table|
| `submit_buttons_bottom` | The submit buttons at the bottom of create/edit views |
| `action_buttons_bottom` | The action buttons at the bottom of details view |
| `model_list_table` | The table displaying records in the list view |
| `model_menu_bar` | The menu bar at the top of model list view |


You can override these blocks in your custom templates to modify the layout and appearance of the admin interface as needed.
5 changes: 4 additions & 1 deletion sqladmin/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)
from urllib.parse import parse_qsl, urljoin

from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader
from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader, PrefixLoader
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session, sessionmaker
Expand Down Expand Up @@ -114,6 +114,9 @@ def init_templating_engine(self) -> Jinja2Templates:
templates = Jinja2Templates("templates")
loaders = [
FileSystemLoader(self.templates_dir),
PrefixLoader(
{"sqladmin_original": PackageLoader("sqladmin", "templates/sqladmin")}
),
PackageLoader("sqladmin", "templates"),
]

Expand Down
12 changes: 11 additions & 1 deletion sqladmin/templates/sqladmin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@
<html lang="en">

<head>
{% block head_meta %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
{% endblock %}
{% block head_css %}
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/tabler.min.css') }}">
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/tabler-icons.min.css') }}">
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/fontawesome.min.css') }}">
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/select2.min.css') }}">
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/flatpickr.min.css') }}">
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/main.css') }}">
{% endblock %}
{% if admin.favicon_url %}
<link rel="icon" href="{{ admin.favicon_url }}">
{% endif %}
<title>{{ admin.title }}</title>
{% block head %}
{% endblock %}
<title>{{ admin.title }}</title>
{% block head_tail %}
{% endblock %}
</head>

<body>
Expand All @@ -25,15 +31,19 @@
{% endblock %}
</main>
{% endblock %}
{% block tail_js %}
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/tabler.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/popper.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/bootstrap.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/select2.full.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/flatpickr.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/main.js') }}"></script>
{% endblock %}
{% block tail %}
{% endblock %}


</body>

</html>
10 changes: 7 additions & 3 deletions sqladmin/templates/sqladmin/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<h3 class="card-title">New {{ model_view.name }}</h3>
</div>
<div class="card-body border-bottom py-3">
{% block create_form %}
<form action="{{ url_for('admin:create', identity=model_view.identity) }}" method="POST"
enctype="multipart/form-data">
<div class="row">
Expand All @@ -25,13 +26,16 @@ <h3 class="card-title">New {{ model_view.name }}</h3>
</div>
<div class="col-md-6">
<div class="btn-group flex-wrap" data-toggle="buttons">
<input type="submit" name="save" value="Save" class="btn">
<input type="submit" name="save" value="Save and continue editing" class="btn">
<input type="submit" name="save" value="Save and add another" class="btn">
{% block submit_buttons_bottom %}
<input type="submit" name="save" value="Save" class="btn">
<input type="submit" name="save" value="Save and continue editing" class="btn">
<input type="submit" name="save" value="Save and add another" class="btn">
{% endblock %}
</div>
</div>
</div>
</form>
{% endblock %}
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion sqladmin/templates/sqladmin/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ <h3 class="card-title">
{% endfor %}: {{ get_object_identifier(model) }}</h3>
</div>
<div class="card-body border-bottom py-3">
{% block details_table %}
<div class="table-responsive">
<table class="table card-table table-vcenter text-nowrap table-hover table-bordered">
<thead>
Expand Down Expand Up @@ -47,6 +48,7 @@ <h3 class="card-title">
</tbody>
</table>
</div>
{% endblock %}
<div class="card-footer container">
<div class="row row-gap-2">
<div class="col-auto">
Expand Down Expand Up @@ -90,6 +92,7 @@ <h3 class="card-title">
</div>
</div>
</div>
{% block action_buttons_bottom %}
{% if model_view.can_delete %}
{% include 'sqladmin/modals/delete.html' %}
{% endif %}
Expand All @@ -102,5 +105,5 @@ <h3 class="card-title">
{% endwith %}
{% endif %}
{% endfor %}

{% endblock %}
{% endblock %}
22 changes: 13 additions & 9 deletions sqladmin/templates/sqladmin/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<h3 class="card-title">Edit {{ model_view.name }}</h3>
</div>
<div class="card-body border-bottom py-3">
{% block edit_form %}
<form action="{{ model_view._build_url_for('admin:edit', request, obj) }}" method="POST"
enctype="multipart/form-data">
<div class="row">
Expand All @@ -25,19 +26,22 @@ <h3 class="card-title">Edit {{ model_view.name }}</h3>
</div>
<div class="col-md-6">
<div class="btn-group flex-wrap" data-toggle="buttons">
<input type="submit" name="save" value="Save" class="btn">
<input type="submit" name="save" value="Save and continue editing" class="btn">
{% if model_view.can_create %}
{% if model_view.save_as %}
<input type="submit" name="save" value="Save as new" class="btn">
{% else %}
<input type="submit" name="save" value="Save and add another" class="btn">
{% endif %}
{% endif %}
{% block submit_buttons_bottom %}
<input type="submit" name="save" value="Save" class="btn">
<input type="submit" name="save" value="Save and continue editing" class="btn">
{% if model_view.can_create %}
{% if model_view.save_as %}
<input type="submit" name="save" value="Save as new" class="btn">
{% else %}
<input type="submit" name="save" value="Save and add another" class="btn">
{% endif %}
{% endif %}
{% endblock %}
</div>
</div>
</div>
</form>
{% endblock %}
</div>
</div>
</div>
Expand Down
66 changes: 35 additions & 31 deletions sqladmin/templates/sqladmin/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,39 @@
<div class="card-header">
<h3 class="card-title">{{ model_view.name_plural }}</h3>
<div class="ms-auto">
{% if model_view.can_export %}
{% if model_view.export_types | length > 1 %}
<div class="ms-3 d-inline-block dropdown">
<a href="#" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton1" data-bs-toggle="dropdown"
aria-expanded="false">
Export
</a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
{% for export_type in model_view.export_types %}
<li><a class="dropdown-item"
href="{{ url_for('admin:export', identity=model_view.identity, export_type=export_type) }}">{{
export_type | upper }}</a></li>
{% endfor %}
</ul>
</div>
{% elif model_view.export_types | length == 1 %}
<div class="ms-3 d-inline-block">
<a href="{{ url_for('admin:export', identity=model_view.identity, export_type=model_view.export_types[0]) }}"
class="btn btn-secondary">
Export
</a>
</div>
{% endif %}
{% endif %}
{% if model_view.can_create %}
<div class="ms-3 d-inline-block">
<a href="{{ url_for('admin:create', identity=model_view.identity) }}" class="btn btn-primary">
+ New {{ model_view.name }}
</a>
</div>
{% endif %}
{% block model_menu_bar %}
{% if model_view.can_export %}
{% if model_view.export_types | length > 1 %}
<div class="ms-3 d-inline-block dropdown">
<a href="#" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton1" data-bs-toggle="dropdown"
aria-expanded="false">
Export
</a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
{% for export_type in model_view.export_types %}
<li><a class="dropdown-item"
href="{{ url_for('admin:export', identity=model_view.identity, export_type=export_type) }}">{{
export_type | upper }}</a></li>
{% endfor %}
</ul>
</div>
{% elif model_view.export_types | length == 1 %}
<div class="ms-3 d-inline-block">
<a href="{{ url_for('admin:export', identity=model_view.identity, export_type=model_view.export_types[0]) }}"
class="btn btn-secondary">
Export
</a>
</div>
{% endif %}
{% endif %}
{% if model_view.can_create %}
<div class="ms-3 d-inline-block">
<a href="{{ url_for('admin:create', identity=model_view.identity) }}" class="btn btn-primary">
+ New {{ model_view.name }}
</a>
</div>
{% endif %}
{% endblock %}
</div>
</div>
<div class="card-body border-bottom py-3">
Expand Down Expand Up @@ -88,6 +90,7 @@ <h3 class="card-title">{{ model_view.name_plural }}</h3>
{% endif %}
</div>
</div>
{% block model_list_table %}
<div class="table-responsive">
<table class="table card-table table-vcenter text-nowrap">
<thead>
Expand Down Expand Up @@ -170,6 +173,7 @@ <h3 class="card-title">{{ model_view.name_plural }}</h3>
</tbody>
</table>
</div>
{% endblock %}
<div class="card-footer d-flex justify-content-between align-items-center gap-2">
<p class="m-0 text-muted">Showing <span>{{ ((pagination.page - 1) * pagination.page_size) + 1 }}</span> to
<span>{{ min(pagination.page * pagination.page_size, pagination.count) }}</span> of <span>{{ pagination.count
Expand Down
Loading
Loading