wagtail-link-field 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wagtail_link_field-0.1.0/.github/workflows/python-publish.yml +70 -0
- wagtail_link_field-0.1.0/.gitignore +145 -0
- wagtail_link_field-0.1.0/LICENSE +28 -0
- wagtail_link_field-0.1.0/PKG-INFO +202 -0
- wagtail_link_field-0.1.0/README.md +168 -0
- wagtail_link_field-0.1.0/pyproject.toml +64 -0
- wagtail_link_field-0.1.0/ruff.toml +35 -0
- wagtail_link_field-0.1.0/testmanage.py +61 -0
- wagtail_link_field-0.1.0/tox.ini +69 -0
- wagtail_link_field-0.1.0/wagtail_link_field/__init__.py +39 -0
- wagtail_link_field-0.1.0/wagtail_link_field/apps.py +10 -0
- wagtail_link_field-0.1.0/wagtail_link_field/blocks.py +180 -0
- wagtail_link_field-0.1.0/wagtail_link_field/enums.py +44 -0
- wagtail_link_field-0.1.0/wagtail_link_field/fields.py +120 -0
- wagtail_link_field-0.1.0/wagtail_link_field/panels.py +153 -0
- wagtail_link_field-0.1.0/wagtail_link_field/static/wagtail_link_field/js/link-block.js +95 -0
- wagtail_link_field-0.1.0/wagtail_link_field/templates/wagtail_link_field/link_tag.html +5 -0
- wagtail_link_field-0.1.0/wagtail_link_field/templatetags/__init__.py +1 -0
- wagtail_link_field-0.1.0/wagtail_link_field/templatetags/wagtail_link_field_tags.py +51 -0
- wagtail_link_field-0.1.0/wagtail_link_field/utils.py +61 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# This workflow will upload a Python Package to PyPI when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
|
3
|
+
|
|
4
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
5
|
+
# They are provided by a third-party and are governed by
|
|
6
|
+
# separate terms of service, privacy policy, and support
|
|
7
|
+
# documentation.
|
|
8
|
+
|
|
9
|
+
name: Upload Python Package
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release-build:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.x"
|
|
28
|
+
|
|
29
|
+
- name: Build release distributions
|
|
30
|
+
run: |
|
|
31
|
+
# NOTE: put your own distribution build steps here.
|
|
32
|
+
python -m pip install build
|
|
33
|
+
python -m build
|
|
34
|
+
|
|
35
|
+
- name: Upload distributions
|
|
36
|
+
uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: release-dists
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
pypi-publish:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
needs:
|
|
44
|
+
- release-build
|
|
45
|
+
permissions:
|
|
46
|
+
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
47
|
+
id-token: write
|
|
48
|
+
|
|
49
|
+
# Dedicated environments with protections for publishing are strongly recommended.
|
|
50
|
+
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
|
|
51
|
+
environment:
|
|
52
|
+
name: pypi
|
|
53
|
+
# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
|
|
54
|
+
# url: https://pypi.org/p/YOURPROJECT
|
|
55
|
+
#
|
|
56
|
+
# ALTERNATIVE: if your GitHub Release name is the PyPI project version string
|
|
57
|
+
# ALTERNATIVE: exactly, uncomment the following line instead:
|
|
58
|
+
# url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Retrieve release distributions
|
|
62
|
+
uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: release-dists
|
|
65
|
+
path: dist/
|
|
66
|
+
|
|
67
|
+
- name: Publish release distributions to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
69
|
+
with:
|
|
70
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Django stuff:
|
|
54
|
+
*.log
|
|
55
|
+
local_settings.py
|
|
56
|
+
db.sqlite3
|
|
57
|
+
db.sqlite3-journal
|
|
58
|
+
*.db
|
|
59
|
+
|
|
60
|
+
# Flask stuff:
|
|
61
|
+
instance/
|
|
62
|
+
.webassets-cache
|
|
63
|
+
|
|
64
|
+
# Scrapy stuff:
|
|
65
|
+
.scrapy
|
|
66
|
+
|
|
67
|
+
# Sphinx documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# PyBuilder
|
|
71
|
+
target/
|
|
72
|
+
|
|
73
|
+
# Jupyter Notebook
|
|
74
|
+
.ipynb_checkpoints
|
|
75
|
+
|
|
76
|
+
# IPython
|
|
77
|
+
profile_default/
|
|
78
|
+
ipython_config.py
|
|
79
|
+
|
|
80
|
+
# pyenv
|
|
81
|
+
.python-version
|
|
82
|
+
|
|
83
|
+
# pipenv
|
|
84
|
+
Pipfile.lock
|
|
85
|
+
|
|
86
|
+
# poetry
|
|
87
|
+
poetry.lock
|
|
88
|
+
|
|
89
|
+
# pdm
|
|
90
|
+
.pdm.toml
|
|
91
|
+
|
|
92
|
+
# PEP 582
|
|
93
|
+
__pypackages__/
|
|
94
|
+
|
|
95
|
+
# Celery stuff
|
|
96
|
+
celerybeat-schedule
|
|
97
|
+
celerybeat.pid
|
|
98
|
+
|
|
99
|
+
# SageMath parsed files
|
|
100
|
+
*.sage.py
|
|
101
|
+
|
|
102
|
+
# Environments
|
|
103
|
+
.env
|
|
104
|
+
.venv
|
|
105
|
+
env/
|
|
106
|
+
venv/
|
|
107
|
+
ENV/
|
|
108
|
+
env.bak/
|
|
109
|
+
venv.bak/
|
|
110
|
+
|
|
111
|
+
# Spyder project settings
|
|
112
|
+
.spyderproject
|
|
113
|
+
.spyproject
|
|
114
|
+
|
|
115
|
+
# Rope project settings
|
|
116
|
+
.ropeproject
|
|
117
|
+
|
|
118
|
+
# mkdocs documentation
|
|
119
|
+
/site
|
|
120
|
+
|
|
121
|
+
# mypy
|
|
122
|
+
.mypy_cache/
|
|
123
|
+
.dmypy.json
|
|
124
|
+
dmypy.json
|
|
125
|
+
|
|
126
|
+
# Pyre type checker
|
|
127
|
+
.pyre/
|
|
128
|
+
|
|
129
|
+
# pytype static type analyzer
|
|
130
|
+
.pytype/
|
|
131
|
+
|
|
132
|
+
# Cython debug symbols
|
|
133
|
+
cython_debug/
|
|
134
|
+
|
|
135
|
+
# uv
|
|
136
|
+
uv.lock
|
|
137
|
+
|
|
138
|
+
# Test media
|
|
139
|
+
test-media/
|
|
140
|
+
|
|
141
|
+
# IDE
|
|
142
|
+
.idea/
|
|
143
|
+
.vscode/
|
|
144
|
+
*.swp
|
|
145
|
+
*.swo
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, infoFactory
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wagtail-link-field
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Flexible link block and field for Wagtail CMS
|
|
5
|
+
Project-URL: Homepage, https://github.com/infofactory/wagtail-link-field
|
|
6
|
+
Project-URL: Source, https://github.com/infofactory/wagtail-link-field
|
|
7
|
+
Project-URL: Issues, https://github.com/infofactory/wagtail-link-field/issues
|
|
8
|
+
Author: infoFactory
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: block,cms,django,field,link,wagtail
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: Django
|
|
14
|
+
Classifier: Framework :: Django :: 4.2
|
|
15
|
+
Classifier: Framework :: Django :: 5.1
|
|
16
|
+
Classifier: Framework :: Django :: 5.2
|
|
17
|
+
Classifier: Framework :: Wagtail
|
|
18
|
+
Classifier: Framework :: Wagtail :: 6
|
|
19
|
+
Classifier: Framework :: Wagtail :: 7
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Requires-Dist: django>=4.2
|
|
30
|
+
Requires-Dist: wagtail>=6.3
|
|
31
|
+
Provides-Extra: testing
|
|
32
|
+
Requires-Dist: coverage<8.0,>=7.0; extra == 'testing'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# wagtail-link-field
|
|
36
|
+
|
|
37
|
+
A flexible link block and field for Wagtail CMS that supports internal pages, external URLs, documents, anchors, email, phone, and custom links.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **LinkBlock**: A StreamField block for flexible link selection
|
|
42
|
+
- **LinkField**: A model field that stores link data as JSON
|
|
43
|
+
- **LinkPanel**: A panel for editing LinkField in Wagtail admin
|
|
44
|
+
- **Template tags**: For rendering links in templates
|
|
45
|
+
- **Dynamic field visibility**: JavaScript shows/hides relevant fields based on selected link type
|
|
46
|
+
- **Clean JSON storage**: Only relevant fields are stored, irrelevant ones are stripped
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install wagtail-link-field
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Add to your `INSTALLED_APPS`:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
INSTALLED_APPS = [
|
|
58
|
+
# ...
|
|
59
|
+
"wagtail_link_field",
|
|
60
|
+
# ...
|
|
61
|
+
]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### LinkBlock in StreamField
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from wagtail.fields import StreamField
|
|
70
|
+
from wagtail import blocks
|
|
71
|
+
from wagtail_link_field import LinkBlock
|
|
72
|
+
|
|
73
|
+
class MyPage(Page):
|
|
74
|
+
body = StreamField([
|
|
75
|
+
("link", LinkBlock()),
|
|
76
|
+
], blank=True, use_json_field=True)
|
|
77
|
+
|
|
78
|
+
# Restrict to specific link types
|
|
79
|
+
body_restricted = StreamField([
|
|
80
|
+
("link", LinkBlock(link_types=["internal-link", "external-link"])),
|
|
81
|
+
], blank=True, use_json_field=True)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### LinkField with LinkPanel
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from django.db import models
|
|
88
|
+
from wagtail_link_field import LinkField, LinkPanel
|
|
89
|
+
|
|
90
|
+
class MyPage(Page):
|
|
91
|
+
cta = LinkField(blank=True, null=True)
|
|
92
|
+
|
|
93
|
+
content_panels = Page.content_panels + [
|
|
94
|
+
LinkPanel("cta"),
|
|
95
|
+
# Or restrict link types:
|
|
96
|
+
LinkPanel("cta_restricted", link_types=["internal-link", "external-link"]),
|
|
97
|
+
]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Accessing link data
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
page = MyPage.objects.first()
|
|
104
|
+
|
|
105
|
+
# Get the URL
|
|
106
|
+
url = page.cta.url() # Returns the computed URL
|
|
107
|
+
|
|
108
|
+
# Check if external
|
|
109
|
+
is_external = page.cta.is_external() # True for external, email, phone, custom links
|
|
110
|
+
|
|
111
|
+
# Get the action type
|
|
112
|
+
action = page.cta.action # e.g., "internal-link", "external-link"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Template tags
|
|
116
|
+
|
|
117
|
+
```django
|
|
118
|
+
{% load wagtail_link_field_tags %}
|
|
119
|
+
|
|
120
|
+
{# Render a full anchor tag #}
|
|
121
|
+
{% render_link page.cta css_class="btn btn-primary" %}
|
|
122
|
+
|
|
123
|
+
{# Or use filters for custom markup #}
|
|
124
|
+
<a href="{{ page.cta|link_url }}" class="my-class">{{ page.cta|link_title }}</a>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Available link types
|
|
128
|
+
|
|
129
|
+
| Type | Description | Fields |
|
|
130
|
+
|------|-------------|--------|
|
|
131
|
+
| `internal-link` | Link to a Wagtail page | `internal_link`, `page_query_string` |
|
|
132
|
+
| `document-link` | Link to a Wagtail document | `document_link` |
|
|
133
|
+
| `external-link` | External URL | `external_link` |
|
|
134
|
+
| `anchor-link` | Same-page anchor | `anchor_link` |
|
|
135
|
+
| `email-link` | Email address | `url_link` (auto-prefixed with `mailto:`) |
|
|
136
|
+
| `phone-link` | Phone number | `url_link` (auto-prefixed with `tel:`) |
|
|
137
|
+
| `custom-link` | Custom URL scheme | `url_link` |
|
|
138
|
+
|
|
139
|
+
## Restricting link types
|
|
140
|
+
|
|
141
|
+
Pass `link_types` to `LinkBlock` or `LinkPanel`:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# Only internal and external links
|
|
145
|
+
LinkBlock(link_types=["internal-link", "external-link"])
|
|
146
|
+
|
|
147
|
+
# Only documents
|
|
148
|
+
LinkPanel("download", link_types=["document-link"])
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Optional links
|
|
152
|
+
|
|
153
|
+
Set `required=False` for optional links:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# LinkBlock (default is required=True)
|
|
157
|
+
LinkBlock(required=False)
|
|
158
|
+
|
|
159
|
+
# LinkField (use blank=True, null=True)
|
|
160
|
+
cta = LinkField(blank=True, null=True)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
### Setup
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Clone the repo
|
|
169
|
+
git clone https://github.com/infofactory/wagtail-link-field.git
|
|
170
|
+
cd wagtail-link-field
|
|
171
|
+
|
|
172
|
+
# Install with uv
|
|
173
|
+
uv sync --extra testing
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Running tests
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Run tests
|
|
180
|
+
python testmanage.py test
|
|
181
|
+
|
|
182
|
+
# Run with coverage
|
|
183
|
+
python -m coverage run testmanage.py test
|
|
184
|
+
python -m coverage report -m
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Running linting
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
ruff check .
|
|
191
|
+
ruff format --check .
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Running tox (matrix testing)
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
tox
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
BSD-3-Clause. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# wagtail-link-field
|
|
2
|
+
|
|
3
|
+
A flexible link block and field for Wagtail CMS that supports internal pages, external URLs, documents, anchors, email, phone, and custom links.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **LinkBlock**: A StreamField block for flexible link selection
|
|
8
|
+
- **LinkField**: A model field that stores link data as JSON
|
|
9
|
+
- **LinkPanel**: A panel for editing LinkField in Wagtail admin
|
|
10
|
+
- **Template tags**: For rendering links in templates
|
|
11
|
+
- **Dynamic field visibility**: JavaScript shows/hides relevant fields based on selected link type
|
|
12
|
+
- **Clean JSON storage**: Only relevant fields are stored, irrelevant ones are stripped
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install wagtail-link-field
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Add to your `INSTALLED_APPS`:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
INSTALLED_APPS = [
|
|
24
|
+
# ...
|
|
25
|
+
"wagtail_link_field",
|
|
26
|
+
# ...
|
|
27
|
+
]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### LinkBlock in StreamField
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from wagtail.fields import StreamField
|
|
36
|
+
from wagtail import blocks
|
|
37
|
+
from wagtail_link_field import LinkBlock
|
|
38
|
+
|
|
39
|
+
class MyPage(Page):
|
|
40
|
+
body = StreamField([
|
|
41
|
+
("link", LinkBlock()),
|
|
42
|
+
], blank=True, use_json_field=True)
|
|
43
|
+
|
|
44
|
+
# Restrict to specific link types
|
|
45
|
+
body_restricted = StreamField([
|
|
46
|
+
("link", LinkBlock(link_types=["internal-link", "external-link"])),
|
|
47
|
+
], blank=True, use_json_field=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### LinkField with LinkPanel
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from django.db import models
|
|
54
|
+
from wagtail_link_field import LinkField, LinkPanel
|
|
55
|
+
|
|
56
|
+
class MyPage(Page):
|
|
57
|
+
cta = LinkField(blank=True, null=True)
|
|
58
|
+
|
|
59
|
+
content_panels = Page.content_panels + [
|
|
60
|
+
LinkPanel("cta"),
|
|
61
|
+
# Or restrict link types:
|
|
62
|
+
LinkPanel("cta_restricted", link_types=["internal-link", "external-link"]),
|
|
63
|
+
]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Accessing link data
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
page = MyPage.objects.first()
|
|
70
|
+
|
|
71
|
+
# Get the URL
|
|
72
|
+
url = page.cta.url() # Returns the computed URL
|
|
73
|
+
|
|
74
|
+
# Check if external
|
|
75
|
+
is_external = page.cta.is_external() # True for external, email, phone, custom links
|
|
76
|
+
|
|
77
|
+
# Get the action type
|
|
78
|
+
action = page.cta.action # e.g., "internal-link", "external-link"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Template tags
|
|
82
|
+
|
|
83
|
+
```django
|
|
84
|
+
{% load wagtail_link_field_tags %}
|
|
85
|
+
|
|
86
|
+
{# Render a full anchor tag #}
|
|
87
|
+
{% render_link page.cta css_class="btn btn-primary" %}
|
|
88
|
+
|
|
89
|
+
{# Or use filters for custom markup #}
|
|
90
|
+
<a href="{{ page.cta|link_url }}" class="my-class">{{ page.cta|link_title }}</a>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Available link types
|
|
94
|
+
|
|
95
|
+
| Type | Description | Fields |
|
|
96
|
+
|------|-------------|--------|
|
|
97
|
+
| `internal-link` | Link to a Wagtail page | `internal_link`, `page_query_string` |
|
|
98
|
+
| `document-link` | Link to a Wagtail document | `document_link` |
|
|
99
|
+
| `external-link` | External URL | `external_link` |
|
|
100
|
+
| `anchor-link` | Same-page anchor | `anchor_link` |
|
|
101
|
+
| `email-link` | Email address | `url_link` (auto-prefixed with `mailto:`) |
|
|
102
|
+
| `phone-link` | Phone number | `url_link` (auto-prefixed with `tel:`) |
|
|
103
|
+
| `custom-link` | Custom URL scheme | `url_link` |
|
|
104
|
+
|
|
105
|
+
## Restricting link types
|
|
106
|
+
|
|
107
|
+
Pass `link_types` to `LinkBlock` or `LinkPanel`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
# Only internal and external links
|
|
111
|
+
LinkBlock(link_types=["internal-link", "external-link"])
|
|
112
|
+
|
|
113
|
+
# Only documents
|
|
114
|
+
LinkPanel("download", link_types=["document-link"])
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Optional links
|
|
118
|
+
|
|
119
|
+
Set `required=False` for optional links:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# LinkBlock (default is required=True)
|
|
123
|
+
LinkBlock(required=False)
|
|
124
|
+
|
|
125
|
+
# LinkField (use blank=True, null=True)
|
|
126
|
+
cta = LinkField(blank=True, null=True)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
### Setup
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Clone the repo
|
|
135
|
+
git clone https://github.com/infofactory/wagtail-link-field.git
|
|
136
|
+
cd wagtail-link-field
|
|
137
|
+
|
|
138
|
+
# Install with uv
|
|
139
|
+
uv sync --extra testing
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Running tests
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Run tests
|
|
146
|
+
python testmanage.py test
|
|
147
|
+
|
|
148
|
+
# Run with coverage
|
|
149
|
+
python -m coverage run testmanage.py test
|
|
150
|
+
python -m coverage report -m
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Running linting
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
ruff check .
|
|
157
|
+
ruff format --check .
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Running tox (matrix testing)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
tox
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
BSD-3-Clause. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wagtail-link-field"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Flexible link block and field for Wagtail CMS"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "BSD-3-Clause"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "infoFactory" }]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Framework :: Django",
|
|
25
|
+
"Framework :: Django :: 4.2",
|
|
26
|
+
"Framework :: Django :: 5.1",
|
|
27
|
+
"Framework :: Django :: 5.2",
|
|
28
|
+
"Framework :: Wagtail",
|
|
29
|
+
"Framework :: Wagtail :: 6",
|
|
30
|
+
"Framework :: Wagtail :: 7",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"Django>=4.2",
|
|
34
|
+
"Wagtail>=6.3",
|
|
35
|
+
]
|
|
36
|
+
keywords = ["wagtail", "django", "cms", "link", "field", "block"]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
testing = [
|
|
40
|
+
"coverage>=7.0,<8.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/infofactory/wagtail-link-field"
|
|
45
|
+
Source = "https://github.com/infofactory/wagtail-link-field"
|
|
46
|
+
Issues = "https://github.com/infofactory/wagtail-link-field/issues"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.version]
|
|
49
|
+
path = "wagtail_link_field/__init__.py"
|
|
50
|
+
pattern = "__version__ = ['\"](?P<version>[^'\"]+)['\"]"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["wagtail_link_field"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
exclude = [
|
|
57
|
+
"wagtail_link_field/test/",
|
|
58
|
+
"wagtail_link_field/tests/",
|
|
59
|
+
".tox",
|
|
60
|
+
".venv",
|
|
61
|
+
"*.db",
|
|
62
|
+
"*.sqlite3",
|
|
63
|
+
"__pycache__",
|
|
64
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[lint]
|
|
2
|
+
extend-select = [
|
|
3
|
+
"B", # flake8-bugbear
|
|
4
|
+
"C4", # flake8-comprehensions
|
|
5
|
+
"DJ", # flake8-django
|
|
6
|
+
"E", # pycodestyle errors
|
|
7
|
+
"F", # pyflakes
|
|
8
|
+
"I", # isort
|
|
9
|
+
"PIE", # flake8-pie
|
|
10
|
+
"PGH", # pygrep-hooks
|
|
11
|
+
"SIM", # flake8-simplify
|
|
12
|
+
"W", # pycodestyle warnings
|
|
13
|
+
"UP", # pyupgrade
|
|
14
|
+
"RUF100", # unused noqa
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
extend-ignore = [
|
|
18
|
+
"E501", # no line length errors
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
fixable = ["C4", "E", "F", "I", "UP"]
|
|
22
|
+
|
|
23
|
+
exclude = ["dist", "build", "venv", ".venv", ".tox", ".git", "__pycache__", "node_modules"]
|
|
24
|
+
|
|
25
|
+
[lint.per-file-ignores]
|
|
26
|
+
"tests/**.py" = ["DJ008"]
|
|
27
|
+
"wagtail_link_field/test/**.py" = ["DJ008"]
|
|
28
|
+
|
|
29
|
+
[lint.isort]
|
|
30
|
+
known-first-party = ["wagtail_link_field"]
|
|
31
|
+
lines-between-types = 1
|
|
32
|
+
lines-after-imports = 2
|
|
33
|
+
|
|
34
|
+
[format]
|
|
35
|
+
docstring-code-format = true
|