-
Notifications
You must be signed in to change notification settings - Fork 60
/
models.py
271 lines (232 loc) · 8.29 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""
Enables the user to add a "Link" plugin that displays a link
using the HTML <a> tag.
"""
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import force_text
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from cms.models import CMSPlugin, Page
from djangocms_attributes_field.fields import AttributesField
from filer.fields.file import FilerFileField
from .validators import IntranetURLValidator
# Add additional choices through the ``settings.py``.
def get_templates():
choices = [
('default', _('Default')),
]
choices += getattr(
settings,
'DJANGOCMS_LINK_TEMPLATES',
[],
)
return choices
HOSTNAME = getattr(
settings,
'DJANGOCMS_LINK_INTRANET_HOSTNAME_PATTERN',
None
)
TARGET_CHOICES = (
('_blank', _('Open in new window')),
('_self', _('Open in same window')),
('_parent', _('Delegate to parent')),
('_top', _('Delegate to top')),
)
class AbstractLink(CMSPlugin):
# used by django CMS search
search_fields = ('name', )
# allows link requirement to be changed when another
# CMS plugin inherits from AbstractLink
link_is_optional = False
url_validators = [
IntranetURLValidator(intranet_host_re=HOSTNAME),
]
template = models.CharField(
verbose_name=_('Template'),
choices=get_templates(),
default=get_templates()[0][0],
max_length=255,
)
name = models.CharField(
verbose_name=_('Display name'),
blank=True,
max_length=255,
)
# re: max_length, see: http://stackoverflow.com/questions/417142/
external_link = models.CharField(
verbose_name=_('External link'),
blank=True,
max_length=2040,
validators=url_validators,
help_text=_('Provide a link to an external source.'),
)
internal_link = models.ForeignKey(
Page,
verbose_name=_('Internal link'),
blank=True,
null=True,
on_delete=models.SET_NULL,
help_text=_('If provided, overrides the external link.'),
)
file_link = FilerFileField(
verbose_name=_('File link'),
blank=True,
null=True,
on_delete=models.SET_NULL,
help_text=_('If provided links a file from the filer app.'),
)
# other link types
anchor = models.CharField(
verbose_name=_('Anchor'),
blank=True,
max_length=255,
help_text=_('Appends the value only after the internal or external link. '
'Do <em>not</em> include a preceding "#" symbol.'),
)
mailto = models.EmailField(
verbose_name=_('Email address'),
blank=True,
max_length=255,
)
phone = models.CharField(
verbose_name=_('Phone'),
blank=True,
max_length=255,
)
# advanced options
target = models.CharField(
verbose_name=_('Target'),
choices=TARGET_CHOICES,
blank=True,
max_length=255,
)
attributes = AttributesField(
verbose_name=_('Attributes'),
blank=True,
excluded_keys=['href', 'target'],
)
# Add an app namespace to related_name to avoid field name clashes
# with any other plugins that have a field with the same name as the
# lowercase of the class name of this model.
# https://github.com/divio/django-cms/issues/5030
cmsplugin_ptr = models.OneToOneField(
CMSPlugin,
related_name='%(app_label)s_%(class)s',
parent_link=True,
on_delete=models.CASCADE,
)
class Meta:
abstract = True
def __str__(self):
return self.name or str(self.pk)
def get_short_description(self):
if self.name and self.get_link():
return '{} ({})'.format(self.name, self.get_link())
return self.name or self.get_link() or gettext('<link is missing>')
def get_link(self):
if self.internal_link:
ref_page = self.internal_link
link = ref_page.get_absolute_url()
# simulate the call to the unauthorized CMSPlugin.page property
cms_page = self.placeholder.page if self.placeholder_id else None
# first, we check if the placeholder the plugin is attached to
# has a page. Thus the check "is not None":
if cms_page is not None:
if getattr(cms_page, 'node', None):
cms_page_site_id = getattr(cms_page.node, 'site_id', None)
else:
cms_page_site_id = getattr(cms_page, 'site_id', None)
# a plugin might not be attached to a page and thus has no site
# associated with it. This also applies to plugins inside
# static placeholders
else:
cms_page_site_id = None
# now we do the same for the reference page the plugin links to
# in order to compare them later
if cms_page is not None:
if getattr(cms_page, 'node', None):
ref_page_site_id = ref_page.node.site_id
else:
ref_page_site_id = ref_page.site_id
# if no external reference is found the plugin links to the
# current page
else:
ref_page_site_id = Site.objects.get_current().pk
if ref_page_site_id != cms_page_site_id:
ref_site = Site.objects._get_site_by_id(ref_page_site_id).domain
link = '//{}{}'.format(ref_site, link)
elif self.file_link:
link = self.file_link.url
elif self.external_link:
link = self.external_link
elif self.phone:
link = 'tel:{}'.format(self.phone.replace(' ', ''))
elif self.mailto:
link = 'mailto:{}'.format(self.mailto)
else:
link = ''
if (not self.phone and not self.mailto) and self.anchor:
link += '#{}'.format(self.anchor)
return link
def clean(self):
super().clean()
field_names = (
'external_link',
'internal_link',
'mailto',
'phone',
'file_link',
)
anchor_field_name = 'anchor'
field_names_allowed_with_anchor = (
'external_link',
'internal_link',
)
anchor_field_verbose_name = force_text(
self._meta.get_field(anchor_field_name).verbose_name)
anchor_field_value = getattr(self, anchor_field_name)
link_fields = {
key: getattr(self, key)
for key in field_names
}
link_field_verbose_names = {
key: force_text(self._meta.get_field(key).verbose_name)
for key in link_fields.keys()
}
provided_link_fields = {
key: value
for key, value in link_fields.items()
if value
}
if len(provided_link_fields) > 1:
# Too many fields have a value.
verbose_names = sorted(link_field_verbose_names.values())
error_msg = _('Only one of {0} or {1} may be given.').format(
', '.join(verbose_names[:-1]),
verbose_names[-1],
)
errors = {}.fromkeys(provided_link_fields.keys(), error_msg)
raise ValidationError(errors)
if (len(provided_link_fields) == 0
and not self.anchor
and not self.link_is_optional):
raise ValidationError(
_('Please provide a link.')
)
if anchor_field_value:
for field_name in provided_link_fields.keys():
if field_name not in field_names_allowed_with_anchor:
error_msg = _('%(anchor_field_verbose_name)s is not allowed together with %(field_name)s') % {
'anchor_field_verbose_name': anchor_field_verbose_name,
'field_name': link_field_verbose_names.get(field_name)
}
raise ValidationError({
anchor_field_name: error_msg,
field_name: error_msg,
})
class Link(AbstractLink):
class Meta:
abstract = False