Skip to content

docx_plus.protection.document

Document-level protection — the thing that turns a document-with-SDTs into an actual fillable form. mode="forms" locks every range outside of a content control; "readOnly" / "comments" / "trackedChanges" cover the other Word edit-restriction modes.

Unpassworded (SPEC §1 non-goal). Password-protected forms (legacy hash algorithm) remain deferred — neither v0.1 nor the v0.2 cycle added them.

w:documentProtection placement follows CT_Settings schema order (before w:defaultTabStop) — see ARCHITECTURE.md §7.

docx_plus.protection.document

Document-level protection — the thing that enforces form-fill mode.

Adding content controls makes a document fillable, but it does not stop a reader from editing surrounding paragraphs. w:documentProtection in settings.xml flips that switch: with mode="forms" Word locks every range outside of an SDT, so the only thing the reader can edit is the content-control fields.

v0.1 protection is unpassworded: it prevents accidental editing, not a determined user. Password-protected forms (legacy hash algorithm; SPEC §1 non-goal) are deferred to v0.2.

This module imports only from docx_plus.core (SPEC §9.1).

ProtectionMode module-attribute

ProtectionMode = Literal['forms', 'readOnly', 'comments', 'trackedChanges']

protect_document

protect_document(doc: Document, *, mode: ProtectionMode = 'forms') -> None

Enforce document protection.

Writes (or replaces) <w:documentProtection w:edit="MODE" w:enforcement="1"/> in settings.xml. Idempotent: a second call with a different mode replaces the previous protection rather than stacking.

Parameters:

Name Type Description Default
doc Document

The python-docx :class:~docx.document.Document to protect.

required
mode ProtectionMode

What kind of editing to enforce.

  • "forms" (default) — only content controls are editable. Pair with :class:docx_plus.controls.FormBuilder to produce a fillable form.
  • "readOnly" — entire document is read-only.
  • "comments" — readers may only add comments.
  • "trackedChanges" — readers may edit with revisions on.
'forms'
Example

from docx import Document from docx_plus.protection import protect_document doc = Document() protect_document(doc, mode="forms")

Source code in docx_plus/protection/document.py
def protect_document(
    doc: DocxDocument,
    *,
    mode: ProtectionMode = "forms",
) -> None:
    """Enforce document protection.

    Writes (or replaces) ``<w:documentProtection w:edit="MODE"
    w:enforcement="1"/>`` in ``settings.xml``. Idempotent: a second call with
    a different ``mode`` replaces the previous protection rather than
    stacking.

    Args:
        doc: The python-docx :class:`~docx.document.Document` to protect.
        mode: What kind of editing to enforce.

            * ``"forms"`` (default) — only content controls are editable.
              Pair with :class:`docx_plus.controls.FormBuilder` to produce a
              fillable form.
            * ``"readOnly"`` — entire document is read-only.
            * ``"comments"`` — readers may only add comments.
            * ``"trackedChanges"`` — readers may edit with revisions on.

    Example:
        >>> from docx import Document
        >>> from docx_plus.protection import protect_document
        >>> doc = Document()
        >>> protect_document(doc, mode="forms")
    """
    settings = doc.settings.element
    existing = settings.find(qn("w:documentProtection"))
    if existing is not None:
        existing.set(qn("w:edit"), mode)
        existing.set(qn("w:enforcement"), "1")
        return
    new = el("w:documentProtection", **{"w:edit": mode, "w:enforcement": "1"})
    insert_before_first_anchor(settings, new, _DOC_PROTECTION_LATER_SIBLINGS)

unprotect_document

unprotect_document(doc: Document) -> None

Remove any document protection.

Idempotent: a no-op if the document is already unprotected.

Parameters:

Name Type Description Default
doc Document

The python-docx :class:~docx.document.Document to unlock.

required
Source code in docx_plus/protection/document.py
def unprotect_document(doc: DocxDocument) -> None:
    """Remove any document protection.

    Idempotent: a no-op if the document is already unprotected.

    Args:
        doc: The python-docx :class:`~docx.document.Document` to unlock.
    """
    settings = doc.settings.element
    existing = settings.find(qn("w:documentProtection"))
    if existing is not None:
        settings.remove(existing)

is_protected

is_protected(doc: Document) -> bool

Return True if any protection is currently enforced.

Parameters:

Name Type Description Default
doc Document

The python-docx :class:~docx.document.Document to inspect.

required

Returns:

Type Description
bool

True if a w:documentProtection element is present in

bool

settings.xml, False otherwise. Does not distinguish modes —

bool

read the element's w:edit attribute for that.

Source code in docx_plus/protection/document.py
def is_protected(doc: DocxDocument) -> bool:
    """Return ``True`` if any protection is currently enforced.

    Args:
        doc: The python-docx :class:`~docx.document.Document` to inspect.

    Returns:
        ``True`` if a ``w:documentProtection`` element is present in
        ``settings.xml``, ``False`` otherwise. Does not distinguish modes —
        read the element's ``w:edit`` attribute for that.
    """
    settings = doc.settings.element
    return settings.find(qn("w:documentProtection")) is not None