Skip to content

Commit 1033256

Browse files
committed
feat(legal): easier customization
- customization of the legal menu - clarified when LEGAL_URL / PRIVACY_URL are applied - support for customizing legal CSS - the legal app can now utilize external documents - clarified the customization documentation
1 parent 7262be6 commit 1033256

20 files changed

Lines changed: 455 additions & 68 deletions

docs/admin/config.rst

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,64 @@ Defaults to -1.
13151315
* :setting:`IP_BEHIND_REVERSE_PROXY`
13161316
* :setting:`IP_PROXY_HEADER`
13171317

1318+
.. setting:: LEGAL_DOCUMENT_CSS_CLASS
1319+
1320+
LEGAL_DOCUMENT_CSS_CLASS
1321+
------------------------
1322+
1323+
.. versionadded:: 2026.7
1324+
1325+
CSS class added to the wrappers around legal document templates.
1326+
1327+
Defaults to ``"tos"``, which enables the built-in legal document numbering and
1328+
spacing rules. Set this to an empty string to render legal documents without
1329+
the built-in numbering.
1330+
1331+
.. code-block:: python
1332+
1333+
LEGAL_DOCUMENT_CSS_CLASS = ""
1334+
1335+
.. seealso::
1336+
1337+
:ref:`legal`
1338+
1339+
.. setting:: LEGAL_HIDDEN_DOCUMENTS
1340+
1341+
LEGAL_HIDDEN_DOCUMENTS
1342+
----------------------
1343+
1344+
.. versionadded:: 2026.7
1345+
1346+
List of legal document page identifiers to hide from the legal module.
1347+
1348+
The ``index`` page is always visible. Supported document identifiers are
1349+
``terms``, ``cookies``, ``privacy``, and ``contracts``.
1350+
1351+
Hidden pages are removed from the legal menu and return a 404 response when
1352+
requested directly. Hiding ``terms`` or ``privacy`` is not recommended when
1353+
terms of service confirmation is enabled.
1354+
1355+
When ``terms`` or ``privacy`` is hidden, links exposed through the
1356+
``terms_url`` and ``privacy_url`` template variables use :setting:`LEGAL_URL`
1357+
and :setting:`PRIVACY_URL` as fallbacks when configured. If no fallback URL is
1358+
configured, the related link is omitted.
1359+
1360+
With terms of service confirmation enabled, hiding ``terms`` and setting
1361+
:setting:`LEGAL_URL` makes the confirmation page link to the external terms
1362+
document instead of embedding :file:`legal/documents/tos.html`.
1363+
1364+
In non-Docker deployments, define :setting:`LEGAL_HIDDEN_DOCUMENTS` and
1365+
:setting:`LEGAL_URL` before ``SPECTACULAR_SETTINGS`` is created so the API
1366+
schema terms link uses the same fallback.
1367+
1368+
.. code-block:: python
1369+
1370+
LEGAL_HIDDEN_DOCUMENTS = ("contracts",)
1371+
1372+
.. seealso::
1373+
1374+
:ref:`legal`
1375+
13181376
.. setting:: LEGAL_TOS_DATE
13191377

13201378
LEGAL_TOS_DATE
@@ -1344,8 +1402,9 @@ URL where your Weblate instance shows its legal documents.
13441402

13451403
.. hint::
13461404

1347-
Useful if you host your legal documents outside Weblate for embedding them inside Weblate.
1348-
Please check :ref:`legal` for details.
1405+
Useful if you host your legal documents outside Weblate instead of using
1406+
the :ref:`legal` module. When the legal module is enabled, Weblate links to
1407+
the internal legal pages by default.
13491408

13501409
Example:
13511410

@@ -1586,8 +1645,9 @@ URL where your Weblate instance shows its privacy policy.
15861645

15871646
.. hint::
15881647

1589-
Useful if you host your legal documents outside Weblate for embedding them inside Weblate,
1590-
please check :ref:`legal` for details.
1648+
Useful if you host your privacy policy outside Weblate instead of using
1649+
the :ref:`legal` module. When the legal module is enabled, Weblate links to
1650+
the internal legal pages by default.
15911651

15921652
Example:
15931653

docs/admin/install/docker.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,10 @@ Generic settings
908908
:file:`/app/data/python/customize/templates/legal/documents`, see
909909
:ref:`docker-static-override`.
910910

911+
Recreate the Docker container after changing this environment variable,
912+
for example using :program:`docker compose up -d`. Restarting an existing
913+
container does not apply changed environment values.
914+
911915
**Example:**
912916

913917
.. code-block:: yaml
@@ -920,6 +924,35 @@ Generic settings
920924
* :ref:`legal`
921925
* :ref:`docker-static-override`
922926

927+
.. envvar:: WEBLATE_LEGAL_DOCUMENT_CSS_CLASS
928+
929+
Configures :setting:`LEGAL_DOCUMENT_CSS_CLASS` in Docker deployments with
930+
:envvar:`WEBLATE_LEGAL_INTEGRATION` enabled.
931+
932+
Set this to an empty string to disable the built-in legal document
933+
numbering.
934+
935+
**Example:**
936+
937+
.. code-block:: yaml
938+
939+
environment:
940+
WEBLATE_LEGAL_DOCUMENT_CSS_CLASS: ""
941+
942+
.. envvar:: WEBLATE_LEGAL_HIDDEN_DOCUMENTS
943+
944+
Configures :setting:`LEGAL_HIDDEN_DOCUMENTS` in Docker deployments with
945+
:envvar:`WEBLATE_LEGAL_INTEGRATION` enabled.
946+
947+
Provide a comma-separated list of legal document page identifiers.
948+
949+
**Example:**
950+
951+
.. code-block:: yaml
952+
953+
environment:
954+
WEBLATE_LEGAL_HIDDEN_DOCUMENTS: contracts
955+
923956
.. envvar:: WEBLATE_PUBLIC_ENGAGE
924957

925958
Enables :setting:`PUBLIC_ENGAGE`.

docs/admin/optionals.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ following templates in the documents:
138138
Privacy policy document
139139
:file:`legal/documents/summary.html`
140140
Short overview of the terms of service and privacy policy
141+
:file:`legal/documents/contracts.html`
142+
Subcontractor information
143+
144+
The legal module embeds these templates inside Weblate and uses
145+
:file:`legal/documents/tos.html` for terms of service confirmation. This is
146+
separate from :setting:`LEGAL_URL` and :setting:`PRIVACY_URL`, which are meant
147+
for linking to externally hosted legal documents from the footer when the
148+
legal module is not enabled. When the legal module is enabled, Weblate links to
149+
the internal legal pages by default.
141150

142151
On changing the terms of service documents, please adjust
143152
:setting:`LEGAL_TOS_DATE` so that users are forced to agree with the updated
@@ -193,11 +202,41 @@ Installation
193202
:file:`/app/data/python/customize/templates/legal/documents`, see
194203
:ref:`docker-static-override`.
195204

205+
Recreate the Docker container after changing environment variables, for
206+
example using :program:`docker compose up -d`. Restarting an existing
207+
container does not apply changed environment values.
208+
196209
Usage
197210
+++++
198211

199212
After installation and editing, the legal documents are shown in the Weblate UI.
200213

214+
The legal document templates are regular Django templates. Text is translated
215+
only when you use Django translation tags such as ``{% translate %}`` or
216+
``{% blocktranslate %}``; plain HTML text is shown as written.
217+
218+
Legal pages and the sign-in and registration overview provide ``terms_url`` and
219+
``privacy_url`` variables for linking to the terms of service and privacy
220+
policy documents.
221+
222+
By default, legal document wrappers use the ``tos`` CSS class. This class
223+
automatically numbers ``h2`` headings, paragraphs with ``item``, ``subitem``,
224+
or ``subsubitem`` classes, and top-level ordered list items. If your legal
225+
text already contains numbering, set :setting:`LEGAL_DOCUMENT_CSS_CLASS` to an
226+
empty string to disable this styling.
227+
228+
Use :setting:`LEGAL_HIDDEN_DOCUMENTS` to hide optional legal pages such as
229+
subcontractors from the legal menu. Hidden pages return a 404 response when
230+
requested directly. If ``terms`` or ``privacy`` is hidden, links using
231+
``terms_url`` or ``privacy_url`` fall back to :setting:`LEGAL_URL` or
232+
:setting:`PRIVACY_URL` when configured, otherwise the link is omitted.
233+
234+
To use externally hosted legal documents with terms confirmation, configure
235+
:setting:`LEGAL_HIDDEN_DOCUMENTS` to hide ``terms`` and ``privacy`` and set
236+
:setting:`LEGAL_URL` and :setting:`PRIVACY_URL`. The confirmation page then
237+
links to those external documents without requiring a
238+
:file:`legal/documents/tos.html` template override.
239+
201240
.. _avatars:
202241

203242
Avatars

docs/changes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Weblate 2026.7
77

88
.. rubric:: Improvements
99

10+
* Documented :ref:`legal` customizations and added options to hide legal pages or disable document numbering.
11+
1012
.. rubric:: Bug fixes
1113

1214
.. rubric:: Compatibility

weblate/api/spectacular.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55

66
from __future__ import annotations
77

8-
from typing import Any
8+
from typing import TYPE_CHECKING, Any
99

1010
from django.utils.functional import lazy
1111
from django.utils.translation import gettext_lazy
1212

13+
if TYPE_CHECKING:
14+
from collections.abc import Sequence
15+
1316

1417
def get_doc_url_wrapper(page: str, anchor: str = "") -> str:
1518
"""
@@ -23,8 +26,27 @@ def get_doc_url_wrapper(page: str, anchor: str = "") -> str:
2326
return get_doc_url(page, anchor, doc_version="latest")
2427

2528

29+
def get_legal_terms_url(
30+
legal_hidden_documents: Sequence[str] | str = (), legal_url: str | None = None
31+
) -> str | None:
32+
if isinstance(legal_hidden_documents, str):
33+
hidden_documents = legal_hidden_documents.split(",")
34+
else:
35+
hidden_documents = legal_hidden_documents
36+
37+
for document in hidden_documents:
38+
if document.strip() == "terms":
39+
return legal_url
40+
return "/legal/terms/"
41+
42+
2643
def get_spectacular_settings(
27-
installed_apps: list[str], site_url: str, site_title: str
44+
installed_apps: list[str],
45+
site_url: str,
46+
site_title: str,
47+
*,
48+
legal_hidden_documents: Sequence[str] | str = (),
49+
legal_url: str | None = None,
2850
) -> dict[str, Any]:
2951
settings = {
3052
# Use redoc from sidecar
@@ -202,7 +224,9 @@ def get_spectacular_settings(
202224
"WEBHOOKS": ["weblate.addons.webhooks.change_event_webhook"],
203225
}
204226
if "weblate.legal" in installed_apps:
205-
settings["TOS"] = "/legal/terms/"
227+
terms_url = get_legal_terms_url(legal_hidden_documents, legal_url)
228+
if terms_url:
229+
settings["TOS"] = terms_url
206230

207231
return settings
208232

weblate/legal/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
class WeblateLegalConf(AppConf):
2929
# Current TOS date
3030
LEGAL_TOS_DATE = DEFAULT_TOS_DATE
31+
# CSS class for styling legal document templates
32+
LEGAL_DOCUMENT_CSS_CLASS = "tos"
33+
# Legal documents to hide from the UI
34+
LEGAL_HIDDEN_DOCUMENTS = ()
3135

3236
class Meta:
3337
prefix = ""

weblate/legal/templates/legal/confirm.html

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,24 @@
1616
<h4 class="card-title">{% translate "You have to agree to the General Terms and Conditions" %}</h4>
1717
</div>
1818
<div class="card-body">
19-
<p>{% translate "Please read following General Terms and Conditions document:" %}</p>
20-
<div class="list-group-item pre-scrollable tos">{% include "legal/documents/tos.html" %}</div>
19+
{% if terms_document_hidden and terms_url %}
20+
<p>{% translate "Please read the following legal documents:" %}</p>
21+
<ul>
22+
<li>
23+
<a href="{{ terms_url }}">{% translate "General Terms and Conditions" %}</a>
24+
</li>
25+
{% if privacy_url %}
26+
<li>
27+
<a href="{{ privacy_url }}">{% translate "Privacy Policy" %}</a>
28+
</li>
29+
{% endif %}
30+
</ul>
31+
{% else %}
32+
<p>{% translate "Please read the following General Terms and Conditions document:" %}</p>
33+
<div class="list-group-item pre-scrollable{% if legal_document_css_class %} {{ legal_document_css_class }}{% endif %}">
34+
{% include "legal/documents/tos.html" %}
35+
</div>
36+
{% endif %}
2137
{{ form|crispy }}
2238
{% csrf_token %}
2339
</div>

weblate/legal/templates/legal/contracts.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
<div class="card-header">
2020
<h4 class="card-title">{% translate "Subcontractors" %}</h4>
2121
</div>
22-
<div class="card-body tos">{% include "legal/documents/contracts.html" %}</div>
22+
<div class="card-body{% if legal_document_css_class %} {{ legal_document_css_class }}{% endif %}">
23+
{% include "legal/documents/contracts.html" %}
24+
</div>
2325
</div>
2426

2527
{% endblock content %}

weblate/legal/templates/legal/cookies.html

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,24 @@ <h4 class="card-title">{% translate "Cookies Policy" %}</h4>
2121
</div>
2222
<div class="card-body">
2323

24-
<p>
25-
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
26-
</p>
27-
28-
<ul>
29-
<li>
30-
<a href="{% url 'legal:terms' %}">{% translate "General Terms and Conditions" %}</a>
31-
</li>
32-
<li>
33-
<a href="{% url 'legal:privacy' %}">{% translate "Privacy Policy" %}</a>
34-
</li>
35-
</ul>
24+
{% if terms_url or privacy_url %}
25+
<p>
26+
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
27+
</p>
28+
29+
<ul>
30+
{% if terms_url %}
31+
<li>
32+
<a href="{{ terms_url }}">{% translate "General Terms and Conditions" %}</a>
33+
</li>
34+
{% endif %}
35+
{% if privacy_url %}
36+
<li>
37+
<a href="{{ privacy_url }}">{% translate "Privacy Policy" %}</a>
38+
</li>
39+
{% endif %}
40+
</ul>
41+
{% endif %}
3642

3743
<p>
3844
{% blocktranslate %}Cookies are used on this site for the following:{% endblocktranslate %}

weblate/legal/templates/legal/index.html

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,24 @@ <h4 class="card-title">{% translate "Legal Terms Overview" %}</h4>
2020

2121
{% include 'legal/documents/summary.html' %}
2222

23-
<p>
24-
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
25-
</p>
26-
27-
<ul>
28-
<li>
29-
<a href="{% url 'legal:terms' %}">{% translate "General Terms and Conditions" %}</a>
30-
</li>
31-
<li>
32-
<a href="{% url 'legal:privacy' %}">{% translate "Privacy Policy" %}</a>
33-
</li>
34-
</ul>
23+
{% if terms_url or privacy_url %}
24+
<p>
25+
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
26+
</p>
27+
28+
<ul>
29+
{% if terms_url %}
30+
<li>
31+
<a href="{{ terms_url }}">{% translate "General Terms and Conditions" %}</a>
32+
</li>
33+
{% endif %}
34+
{% if privacy_url %}
35+
<li>
36+
<a href="{{ privacy_url }}">{% translate "Privacy Policy" %}</a>
37+
</li>
38+
{% endif %}
39+
</ul>
40+
{% endif %}
3541

3642
</div>
3743
</div>

0 commit comments

Comments
 (0)