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.
@@ -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