Skip to content

Commit ce82fab

Browse files
committed
Handle double click call for action text in QGIS 4
1 parent ac86a17 commit ce82fab

File tree

9 files changed

+193
-13
lines changed

9 files changed

+193
-13
lines changed

qgisfeedproject/qgisfeed/forms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class Meta:
115115
"image",
116116
"content",
117117
"url",
118+
"action_text",
118119
"sticky",
119120
"sorting",
120121
"language_filter",
@@ -159,6 +160,12 @@ def __init__(self, *args, **kwargs):
159160
self.fields["url"].widget = forms.TextInput(
160161
attrs={"class": "input", "placeholder": "URL for more information link"}
161162
)
163+
self.fields["action_text"].widget = forms.TextInput(
164+
attrs={
165+
"class": "input",
166+
"placeholder": "e.g. Double-click here to read more (QGIS 3 only)",
167+
}
168+
)
162169
self.fields["sorting"].widget = forms.NumberInput(
163170
attrs={
164171
"class": "input",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.db import migrations, models
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
("qgisfeed", "0018_alter_feedentryreview_options"),
8+
]
9+
10+
operations = [
11+
migrations.AddField(
12+
model_name="qgisfeedentry",
13+
name="action_text",
14+
field=models.CharField(
15+
blank=True,
16+
help_text=(
17+
"Optional call-to-action shown only on QGIS 3 (e.g. 'Double-click here to read more'). "
18+
"Leave blank if not needed. QGIS 4 opens the URL via a dedicated button so this text is hidden there."
19+
),
20+
max_length=255,
21+
null=True,
22+
verbose_name="Call to action text",
23+
),
24+
),
25+
]

qgisfeedproject/qgisfeed/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ class QgisFeedEntry(models.Model):
124124
blank=True,
125125
null=True,
126126
)
127+
action_text = models.CharField(
128+
_("Call to action text"),
129+
max_length=255,
130+
blank=True,
131+
null=True,
132+
help_text=_(
133+
"Optional call-to-action shown only on QGIS 3 (e.g. 'Double-click here to read more'). "
134+
"Leave blank if not needed. QGIS 4 opens the URL via a dedicated button so this text is hidden there."
135+
),
136+
)
127137

128138
# Auto fields
129139
author = models.ForeignKey(

qgisfeedproject/qgisfeed/tests.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,63 @@ def test_group_is_created(self):
311311
set(("qgisfeed.add_qgisfeedentry", "qgisfeed.view_qgisfeedentry")),
312312
)
313313

314+
def test_action_text_shown_on_qgis3_hidden_on_qgis4(self):
315+
"""action_text must be appended to content for QGIS 3 clients and
316+
omitted for QGIS 4+ clients, regardless of the language used."""
317+
author = User.objects.get(username="admin")
318+
entry = QgisFeedEntry.objects.create(
319+
title="Action-text test entry",
320+
content="<p>Some real content here.</p>",
321+
action_text="Double-cliquez ici pour en savoir plus.",
322+
url="https://www.example.com",
323+
author=author,
324+
status=QgisFeedEntry.PUBLISHED,
325+
publish_from="2019-05-05T12:00:00Z",
326+
)
327+
328+
# --- QGIS 3 client ---
329+
c3 = Client(
330+
HTTP_USER_AGENT="Mozilla/5.0 QGIS/33600/Fedora Linux (Workstation Edition)"
331+
)
332+
response = c3.get("/")
333+
data = json.loads(response.content)
334+
335+
# action_text appended for QGIS 3
336+
match = next((d for d in data if d["title"] == "Action-text test entry"), None)
337+
self.assertIsNotNone(match)
338+
self.assertIn("Double-cliquez ici pour en savoir plus.", match["content"])
339+
self.assertIn("Some real content here", match["content"])
340+
341+
# --- QGIS 4 client ---
342+
c4 = Client(
343+
HTTP_USER_AGENT="Mozilla/5.0 QGIS/40000/Fedora Linux (Workstation Edition)"
344+
)
345+
response = c4.get("/")
346+
data = json.loads(response.content)
347+
348+
# action_text must NOT appear in content for QGIS 4
349+
match = next((d for d in data if d["title"] == "Action-text test entry"), None)
350+
self.assertIsNotNone(match)
351+
self.assertNotIn("Double-cliquez ici pour en savoir plus.", match["content"])
352+
self.assertIn("Some real content here", match["content"])
353+
self.assertNotIn("action_text", match)
354+
355+
# Entry with no action_text: content unchanged for all versions
356+
entry_no_action = QgisFeedEntry.objects.create(
357+
title="No-action-text entry",
358+
content="<p>Plain content.</p>",
359+
action_text=None,
360+
url="https://www.example.com",
361+
author=author,
362+
status=QgisFeedEntry.PUBLISHED,
363+
publish_from="2019-05-05T12:00:00Z",
364+
)
365+
response = c4.get("/")
366+
data = json.loads(response.content)
367+
match = next((d for d in data if d["title"] == "No-action-text entry"), None)
368+
self.assertIsNotNone(match)
369+
self.assertEqual(match["content"], "<p>Plain content.</p>")
370+
314371
def test_admin_publish_from(self):
315372
"""Test that published entries have publish_from set"""
316373

qgisfeedproject/qgisfeed/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ def create_revision_snapshot(original_entry, new_instance, user):
373373
TRACKED_FIELDS = [
374374
("title", "Title", "text"),
375375
("url", "URL", "text"),
376+
("action_text", "Call to Action Text", "text"),
376377
("content", "Content", "content"),
377378
("image", "Image", "file"),
378379
("sticky", "Sticky", "bool"),

qgisfeedproject/qgisfeed/views.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def get(self, request):
226226
"image",
227227
"content",
228228
"url",
229+
"action_text",
229230
"sticky",
230231
)[:QGISFEED_MAX_RECORDS]:
231232
if record["publish_from"]:
@@ -236,6 +237,16 @@ def get(self, request):
236237
record["image"] = request.build_absolute_uri(
237238
settings.MEDIA_URL + record["image"]
238239
)
240+
# action_text is a QGIS-3-only call-to-action (e.g. "Double-click
241+
# here to read more"). For QGIS 4+ the UI provides its own
242+
# interaction affordance so we omit it; for older clients we
243+
# append it to the content so they still see it.
244+
action_text = record.pop("action_text", None)
245+
if action_text and (qgis_version is None or qgis_version < 40000):
246+
record["content"] = (
247+
record["content"] or ""
248+
) + f"<p><strong>{action_text}</strong></p>"
249+
239250
data.append(record)
240251

241252
data_json = json.dumps(data, indent=(2 if settings.DEBUG else 0))

qgisfeedproject/templates/feeds/feed_item_form.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ <h4 class="title is-5 mb-3">
522522

523523
// Tab switching functionality using Bulma tabs
524524
document.addEventListener('DOMContentLoaded', () => {
525-
const tabItems = document.querySelectorAll('.tabs li');
525+
const tabItems = document.querySelectorAll('.tabs li[data-tab]');
526526
const tabContents = document.querySelectorAll('.tab-content');
527527

528528
tabItems.forEach(item => {

qgisfeedproject/templates/feeds/feed_item_form_widgets.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@
8585
<p id="urlError" class="help form-error"></p>
8686
</div>
8787
</div>
88+
<div class="columns">
89+
<div class="column is-12 field">
90+
<label class="label">
91+
{{ form.action_text.label_tag }}
92+
</label>
93+
<div class="control has-icons-left">
94+
{{form.action_text}}
95+
<span class="icon is-small is-left">
96+
<i class="fas fa-hand-pointer"></i>
97+
</span>
98+
</div>
99+
<p class="help">{{form.action_text.help_text}}</p>
100+
{% for error in form.action_text.errors %}
101+
<p class="help form-error">{{ error }}</p>
102+
{% endfor %}
103+
</div>
104+
</div>
88105
<div class="columns">
89106
<div class="column is-6 field">
90107
<label class="label">

qgisfeedproject/templates/feeds/feed_item_preview.html

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,71 @@
1-
<div class="form-preview columns has-background-light box-container m-0">
2-
<div class="column is-4" name="imagePreview">
3-
{% if form.image.value %}
4-
<img src="{{ form.image.value.url }}" style="border-radius:20px;">
5-
{% else %}
6-
{% endif %}
1+
<div class="preview-version-container">
2+
<div class="tabs is-toggle is-small mb-3">
3+
<ul class="preview-version-tabs is-justify-content-center">
4+
<li class="is-active" data-preview="qgis3">
5+
<a><span>QGIS 3 preview</span></a>
6+
</li>
7+
<li data-preview="qgis4">
8+
<a><span>QGIS 4 preview</span></a>
9+
</li>
10+
</ul>
11+
</div>
12+
13+
<div class="preview-pane preview-qgis3">
14+
<div class="form-preview columns has-background-light box-container m-0">
15+
<div class="column is-4" name="imagePreview">
16+
{% if form.image.value %}
17+
<img src="{{ form.image.value.url }}" style="border-radius:20px;">
18+
{% endif %}
19+
</div>
20+
<div class="column is-8">
21+
<h5 name="titlePreview" class="title is-5">
22+
{{form.title.value | default:""}}
23+
</h5>
24+
<div name="contentPreview">
25+
{{form.content.value | default:"" | safe }}
26+
</div>
27+
{% if form.action_text.value %}
28+
<p name="actionTextPreview" class="mt-2"><strong>{{ form.action_text.value }}</strong></p>
29+
{% endif %}
30+
</div>
731
</div>
8-
<div class="column is-8">
9-
<h5 name="titlePreview" class="title is-5">
10-
{{form.title.value | default:""}}
11-
</h5>
12-
<div name="contentPreview">
13-
{{form.content.value | default:"" | safe }}
32+
</div>
33+
34+
<div class="preview-pane preview-qgis4" style="display:none;">
35+
<div class="form-preview columns has-background-light box-container m-0">
36+
<div class="column is-4" name="imagePreview">
37+
{% if form.image.value %}
38+
<img src="{{ form.image.value.url }}" style="border-radius:20px;">
39+
{% endif %}
40+
</div>
41+
<div class="column is-8">
42+
<h5 class="title is-5">
43+
{{form.title.value | default:""}}
44+
</h5>
45+
<div>
46+
{{form.content.value | default:"" | safe }}
47+
</div>
1448
</div>
1549
</div>
1650
</div>
1751

52+
<script>
53+
(function () {
54+
var container = document.currentScript.closest('.preview-version-container');
55+
var tabs = container.querySelectorAll('.preview-version-tabs li');
56+
tabs.forEach(function (tab) {
57+
tab.addEventListener('click', function () {
58+
tabs.forEach(function (t) { t.classList.remove('is-active'); });
59+
tab.classList.add('is-active');
60+
container.querySelectorAll('.preview-pane').forEach(function (p) {
61+
p.style.display = 'none';
62+
});
63+
container.querySelector('.preview-' + tab.dataset.preview).style.display = '';
64+
});
65+
});
66+
}());
67+
</script>
68+
1869
<div class="form-preview columns box-container m-0">
1970
<table class="table is-fullwidth">
2071
<tbody>
@@ -73,3 +124,4 @@ <h5 name="titlePreview" class="title is-5">
73124
</tbody>
74125
</table>
75126
</div>
127+
</div><!-- /.preview-version-container -->

0 commit comments

Comments
 (0)