Skip to content

Commit 981c00c

Browse files
committed
Add support for djangocms-link 5
1 parent 1c0173e commit 981c00c

File tree

18 files changed

+47
-249
lines changed

18 files changed

+47
-249
lines changed

djangocms_frontend/common/bootstrap5/background.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from djangocms_frontend import settings
66
from djangocms_frontend.fields import ButtonGroup, ColoredButtonGroup
7-
from djangocms_frontend.helpers import first_choice, insert_fields
7+
from djangocms_frontend.helpers import insert_fields
88

99

1010
class BackgroundMixin:
@@ -23,9 +23,8 @@ def get_fieldsets(self, request, obj=None):
2323
def render(self, context, instance, placeholder):
2424
if getattr(instance, "background_context", ""):
2525
instance.add_classes(f"bg-{instance.background_context}")
26-
if getattr(instance, "background_opacity", "100") != "100":
27-
if instance.background_opacity:
28-
instance.add_classes(f"bg-opacity-{instance.background_opacity}")
26+
if getattr(instance, "background_opacity", ""):
27+
instance.add_classes(f"bg-opacity-{instance.background_opacity}")
2928
if getattr(instance, "background_shadow", ""):
3029
if instance.background_shadow == "reg":
3130
instance.add_classes("shadow")
@@ -54,8 +53,8 @@ class Meta:
5453
background_opacity = forms.ChoiceField(
5554
label=_("Background opacity"),
5655
required=False,
57-
choices=settings.framework_settings.OPACITY_CHOICES,
58-
initial=first_choice(settings.framework_settings.OPACITY_CHOICES),
56+
choices=settings.EMPTY_CHOICE + settings.framework_settings.OPACITY_CHOICES,
57+
initial=settings.EMPTY_CHOICE[0][0],
5958
widget=ButtonGroup(attrs=dict(property="opacity")),
6059
help_text=_("Opacity of card background color (only if no outline selected)"),
6160
)

djangocms_frontend/common/bootstrap5/responsive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_fieldsets(self, request, obj=None):
3636
)
3737

3838
def render(self, context, instance, placeholder):
39-
if instance.config.get("responsive_visibility", None) is not None:
39+
if instance.config.get("responsive_visibility", None):
4040
instance.add_classes(
4141
get_display_classes(
4242
instance.responsive_visibility,

djangocms_frontend/common/title.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class Meta:
6161
plugin_title = TitleField(
6262
label=_("Title"),
6363
required=False,
64+
initial={"show": False, "title": ""},
6465
help_text=_(
6566
"Optional title of the plugin for easier identification. "
6667
"Its <code>title</code> attribute "

djangocms_frontend/contrib/link/cms_plugins.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
from .. import link
1313
from . import forms, models, views
1414
from .constants import USE_LINK_ICONS
15+
from .helpers import GetLinkMixin
1516

1617
mixin_factory = settings.get_renderer(link)
1718

1819

1920
UILINK_FIELDS = (
2021
("name", "link_type"),
21-
("site", "url_grouper") if apps.is_installed("djangocms_url_manager") else ("external_link", "internal_link"),
22+
("site", "url_grouper") if apps.is_installed("djangocms_url_manager") else "link",
2223
("link_context", "link_size"),
2324
("link_outline", "link_block"),
2425
"link_stretched",
@@ -33,20 +34,6 @@
3334
},
3435
),
3536
]
36-
if not apps.is_installed("djangocms_url_manager"):
37-
UILINK_FIELDSET += [
38-
(
39-
_("Link settings"),
40-
{
41-
"classes": ("collapse",),
42-
"fields": (
43-
("mailto", "phone"),
44-
("anchor", "target"),
45-
("file_link",),
46-
),
47-
},
48-
),
49-
]
5037

5138

5239
class LinkPluginMixin:
@@ -65,6 +52,7 @@ class LinkPluginMixin:
6552
def render(self, context, instance, placeholder):
6653
if "request" in context:
6754
instance._cms_page = getattr(context["request"], "current_page", None)
55+
context["link"] = instance.get_link()
6856
return super().render(context, instance, placeholder)
6957

7058
def get_form(self, request, obj=None, change=False, **kwargs):
@@ -85,7 +73,7 @@ def get_fieldsets(self, request, obj=None):
8573
return fieldsets
8674

8775

88-
class TextLinkPlugin(mixin_factory("Link"), AttributesMixin, SpacingMixin, LinkPluginMixin, CMSUIPlugin):
76+
class TextLinkPlugin(mixin_factory("Link"), AttributesMixin, SpacingMixin, LinkPluginMixin, GetLinkMixin, CMSUIPlugin):
8977
"""
9078
Components > "Button" Plugin
9179
https://getbootstrap.com/docs/5.0/components/buttons/

djangocms_frontend/contrib/link/forms.py

Lines changed: 9 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,14 @@
77
from django.contrib.admin.widgets import SELECT2_TRANSLATIONS, AutocompleteMixin
88
from django.contrib.contenttypes.models import ContentType
99
from django.contrib.sites.models import Site
10-
from django.core.exceptions import ObjectDoesNotExist, ValidationError
10+
from django.core.exceptions import ObjectDoesNotExist
1111
from django.db import models
12-
from django.db.models.fields.related import ManyToOneRel
13-
from django.utils.encoding import force_str
1412
from django.utils.translation import get_language
1513
from django.utils.translation import gettext as _
14+
from djangocms_link.fields import LinkFormField
1615

1716
# from djangocms_link.validators import IntranetURLValidator
1817
from entangled.forms import EntangledModelForm
19-
from filer.fields.image import AdminFileFormField, FilerFileField
20-
from filer.models import File
2118

2219
from ... import settings
2320
from ...common import SpacingFormMixin
@@ -28,11 +25,11 @@
2825
TagTypeFormField,
2926
TemplateChoiceMixin,
3027
)
31-
from ...helpers import first_choice, get_related_object
28+
from ...helpers import first_choice
3229
from ...models import FrontendUIItem
3330
from .. import link
3431
from .constants import LINK_CHOICES, LINK_SIZE_CHOICES, TARGET_CHOICES
35-
from .helpers import get_choices, get_object_for_value
32+
from .helpers import get_object_for_value
3633

3734
mixin_factory = settings.get_forms(link)
3835

@@ -174,59 +171,18 @@ class AbstractLinkForm(EntangledModelForm):
174171
class Meta:
175172
entangled_fields = {
176173
"config": [
177-
"external_link",
178-
"internal_link",
179-
"file_link",
180-
"anchor",
181-
"mailto",
182-
"phone",
174+
"link",
183175
"target",
184176
]
185177
}
186178

187179
link_is_optional = False
188180

189-
# url_validators = [
190-
# IntranetURLValidator(intranet_host_re=HOSTNAME),
191-
# ]
192-
193-
external_link = forms.URLField(
194-
label=_("External link"),
195-
required=False,
196-
# validators=url_validators,
197-
help_text=_("Provide a link to an external source."),
198-
)
199-
internal_link = SmartLinkField(
200-
label=_("Internal link"),
201-
required=False,
202-
help_text=_("If provided, overrides the external link."),
203-
)
204-
file_link = AdminFileFormField(
205-
rel=ManyToOneRel(FilerFileField, File, "id"),
206-
queryset=File.objects.all(),
207-
to_field_name="id",
208-
label=_("File link"),
181+
link = LinkFormField(
182+
label=_("Link"),
183+
initial={},
209184
required=False,
210-
help_text=_("If provided links a file from the filer app."),
211185
)
212-
# other link types
213-
anchor = forms.CharField(
214-
label=_("Anchor"),
215-
required=False,
216-
help_text=_(
217-
"Appends the value only after the internal or external link. "
218-
'Do <em>not</em> include a preceding "&#35;" symbol.'
219-
),
220-
)
221-
mailto = forms.EmailField(
222-
label=_("Email address"),
223-
required=False,
224-
)
225-
phone = forms.CharField(
226-
label=_("Phone"),
227-
required=False,
228-
)
229-
# advanced options
230186
target = forms.ChoiceField(
231187
label=_("Target"),
232188
choices=settings.EMPTY_CHOICE + TARGET_CHOICES,
@@ -235,71 +191,7 @@ class Meta:
235191

236192
def __init__(self, *args, **kwargs):
237193
super().__init__(*args, **kwargs)
238-
self.fields["internal_link"].choices = self.get_choices
239-
240-
def get_choices(self):
241-
if MINIMUM_INPUT_LENGTH == 0:
242-
return get_choices(self.request)
243-
if not self.is_bound: # find initial value
244-
int_link_field = self.fields["internal_link"]
245-
initial = self.get_initial_for_field(int_link_field, "internal_link")
246-
if initial: # Initial set?
247-
obj = get_related_object(dict(obj=initial), "obj") # get it!
248-
if obj is not None:
249-
value = int_link_field.prepare_value(initial)
250-
return ((value, str(obj)),)
251-
return () # nothing found
252-
253-
def clean(self):
254-
super().clean()
255-
link_field_names = (
256-
"external_link",
257-
"internal_link",
258-
"mailto",
259-
"phone",
260-
"file_link",
261-
)
262-
anchor_field_name = "anchor"
263-
field_names_allowed_with_anchor = (
264-
"external_link",
265-
"internal_link",
266-
)
267-
anchor_field_verbose_name = force_str(self.fields[anchor_field_name].label)
268-
anchor_field_value = self.cleaned_data.get(anchor_field_name, None)
269-
link_fields = {key: self.cleaned_data.get(key, None) for key in link_field_names}
270-
link_field_verbose_names = {key: force_str(self.fields[key].label) for key in link_fields.keys()}
271-
provided_link_fields = {key: value for key, value in link_fields.items() if value}
272-
273-
if len(provided_link_fields) > 1:
274-
# Too many fields have a value.
275-
verbose_names = sorted(link_field_verbose_names.values())
276-
error_msg = _("Only one of {0} or {1} may be given.").format(
277-
", ".join(verbose_names[:-1]),
278-
verbose_names[-1],
279-
)
280-
errors = {}.fromkeys(provided_link_fields.keys(), error_msg)
281-
raise ValidationError(errors)
282-
283-
if (
284-
len(provided_link_fields) == 0
285-
and not self.cleaned_data.get(anchor_field_name, None)
286-
and not self.link_is_optional
287-
):
288-
raise ValidationError(_("Please provide a link."))
289-
290-
if anchor_field_value:
291-
for field_name in provided_link_fields.keys():
292-
if field_name not in field_names_allowed_with_anchor:
293-
error_msg = _("%(anchor_field_verbose_name)s is not allowed together with %(field_name)s") % {
294-
"anchor_field_verbose_name": anchor_field_verbose_name,
295-
"field_name": link_field_verbose_names.get(field_name),
296-
}
297-
raise ValidationError(
298-
{
299-
anchor_field_name: error_msg,
300-
field_name: error_msg,
301-
}
302-
)
194+
self.fields["link"].required = not self.link_is_optional
303195

304196

305197
class LinkForm(mixin_factory("Link"), SpacingFormMixin, TemplateChoiceMixin, AbstractLinkForm):

djangocms_frontend/contrib/link/frameworks/bootstrap5.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ def render(self, context, instance, placeholder):
2828
link_classes.append("d-block")
2929
if instance.config.get("link_stretched", False):
3030
link_classes.append("stretched-link")
31-
context["link"] = instance.get_link()
3231
instance.add_classes(link_classes)
3332
return super().render(context, instance, placeholder)

djangocms_frontend/contrib/link/helpers.py

Lines changed: 7 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from cms.forms.utils import get_page_choices
44
from cms.models import Page
5+
from django.apps import apps
56
from django.conf import settings as django_settings
67
from django.contrib.admin import site
78
from django.contrib.contenttypes.models import ContentType
@@ -138,99 +139,16 @@ def to_choices(json):
138139

139140

140141
class GetLinkMixin:
141-
def __init__(self, *args, **kwargs):
142-
self._cms_page = None
143-
super().__init__(*args, **kwargs)
144-
145-
def get_link(self):
146-
if getattr(self, "url_grouper", None):
142+
def get_link(self) -> str:
143+
if "url_grouper" in self.config and self.config["url_grouper"] and apps.is_installed("djangocms_url_manager"):
147144
url_grouper = get_related_object(self.config, "url_grouper")
148145
if not url_grouper:
149146
return ""
150-
# The next line is a workaround, since djangocms-url-manager does not provide a way of
151-
# getting the current URL object.
152147
from djangocms_url_manager.models import Url
153-
url = Url._base_manager.filter(url_grouper=url_grouper).order_by("pk").last()
148+
url = Url.objects.filter(url_grouper=url_grouper).order_by("pk").last()
154149
if not url: # pragma: no cover
155150
return ""
156-
# simulate the call to the unauthorized CMSPlugin.page property
157-
cms_page = self.placeholder.page if self.placeholder_id else None
158-
159-
# first, we check if the placeholder the plugin is attached to
160-
# has a page. Thus, the check "is not None":
161-
if cms_page is not None:
162-
if getattr(cms_page, "node", None):
163-
cms_page_site_id = getattr(cms_page.node, "site_id", None)
164-
else:
165-
cms_page_site_id = getattr(cms_page, "site_id", None)
166-
# a plugin might not be attached to a page and thus has no site
167-
# associated with it. This also applies to plugins inside
168-
# static placeholders
169-
else:
170-
cms_page_site_id = None
171-
return url.get_url(cms_page_site_id) or ""
172-
173-
if getattr(self, "internal_link", None):
174-
try:
175-
ref_page = get_related_object(self.config, "internal_link")
176-
link = ref_page.get_absolute_url()
177-
except (
178-
KeyError,
179-
TypeError,
180-
ValueError,
181-
AttributeError,
182-
ObjectDoesNotExist,
183-
):
184-
self.internal_link = None
185-
return ""
186-
187-
# simulate the call to the unauthorized CMSPlugin.page property
188-
cms_page = self._cms_page or self.placeholder.page if self.placeholder_id else None
189-
190-
# first, we check if the placeholder the plugin is attached to
191-
# has a page. Thus, the check "is not None":
192-
if cms_page is not None:
193-
if getattr(cms_page, "node", None):
194-
cms_page_site_id = getattr(cms_page.node, "site_id", None)
195-
else:
196-
cms_page_site_id = getattr(cms_page, "site_id", None)
197-
# a plugin might not be attached to a page and thus has no site
198-
# associated with it. This also applies to plugins inside
199-
# static placeholders
200-
else:
201-
cms_page_site_id = None
202-
203-
# now we do the same for the reference page the plugin links to
204-
# in order to compare them later
205-
if getattr(ref_page, "node", None):
206-
ref_page_site_id = ref_page.node.site_id
207-
elif getattr(ref_page, "site_id", None):
208-
ref_page_site_id = ref_page.site_id
209-
# if no external reference is found the plugin links to the
210-
# current page
211-
else:
212-
ref_page_site_id = Site.objects.get_current().pk
213-
214-
if ref_page_site_id != cms_page_site_id:
215-
ref_site = Site.objects._get_site_by_id(ref_page_site_id).domain
216-
link = f"//{ref_site}{link}"
217-
218-
elif getattr(self, "file_link", None):
219-
link = getattr(get_related_object(self.config, "file_link"), "url", "")
220-
221-
elif getattr(self, "external_link", None):
222-
link = self.external_link
223-
224-
elif getattr(self, "phone", None):
225-
link = "tel:{}".format(self.phone.replace(" ", ""))
226-
227-
elif getattr(self, "mailto", None):
228-
link = f"mailto:{self.mailto}"
229-
230-
else:
231-
link = ""
232-
233-
if (not getattr(self, "phone", None) and not getattr(self, "mailto", None)) and getattr(self, "anchor", None):
234-
link += f"#{self.anchor}"
151+
return url.get_absolute_url() or ""
235152

236-
return link
153+
from djangocms_link.helpers import get_link as djangocms_link_get_link
154+
return djangocms_link_get_link(self.config.get("link", {}), Site.objects.get_current().pk) or ""

0 commit comments

Comments
 (0)