-
Notifications
You must be signed in to change notification settings - Fork 73
/
models.py
263 lines (206 loc) · 7.78 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
"""
Plugins and tabs make it possible to customize the behavior and appearance of pages in this
system. Plugins are rendered as small "boxes" on the side of a page, where tabs have their own
pages which can be accessed through a tab-like user interface.
Any model can be related to a Plugin or Tab using a django.contrib.contenttypes.GenericRelation
field and naming AbstractApp fields container_pk & container_type for the link.
"""
import datetime
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.template import loader
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
import feedparser
from apps.app_renderers import ExternalIFramePluginRenderer, \
ExternalIFrameTabRenderer, TabRenderer
from inheritance.models import ModelWithInheritance
class AbstractApp(ModelWithInheritance):
# Generic foreign key implementation from Django contenttypes framework.
container_type = models.ForeignKey(ContentType,
verbose_name=_('LABEL_CONTAINER_TYPE'),
on_delete=models.CASCADE,
)
container_pk = models.TextField(
verbose_name=_('LABEL_CONTAINER_PK'),
)
container = GenericForeignKey(ct_field="container_type", fk_field="container_pk")
# Apps used to have an oembed reference which was removed in migration to Python 3
# in favor of future implementations, for example LTI.
class Meta:
verbose_name = _('MODEL_NAME_ABSTRACT_APP')
verbose_name_plural = _('MODEL_NAME_ABSTRACT_APP_PLURAL')
abstract = True
class BaseTab(AbstractApp):
label = models.CharField(
verbose_name=_('LABEL_LABEL'),
max_length=12,
help_text=_('TAB_LABEL_HELPTEXT'),
)
title = models.CharField(
verbose_name=_('LABEL_TITLE'),
max_length=64,
help_text=_('TAB_TITLE_HELPTEXT'),
)
order = models.IntegerField(
verbose_name=_('LABEL_ORDER'),
default=100,
)
# A Tab can be opened in a new window, in the same window?
opening_method = models.CharField(
verbose_name=_('LABEL_OPENING_METHOD'),
max_length=32,
blank=True,
)
def render(self):
return _('TAB_NO_CONTENT')
def get_label(self):
return self.label
def get_container(self):
if isinstance(self.container, ModelWithInheritance):
return self.container.as_leaf_class()
return self.container
def get_renderer_class(self):
raise NotImplementedError('Missing method implementation!')
def __str__(self):
return self.label
class Meta:
verbose_name = _('MODEL_NAME_BASE_TAB')
verbose_name_plural = _('MODEL_NAME_BASE_TAB_PLURAL')
ordering = ['order', 'id']
class HTMLTab(BaseTab):
content = models.TextField(
verbose_name=_('LABEL_CONTENT'),
)
def render(self):
return mark_safe(self.content)
class Meta:
verbose_name = _('MODEL_NAME_HTML_TAB')
verbose_name_plural = _('MODEL_NAME_HTML_TAB_PLURAL')
class ExternalEmbeddedTab(BaseTab):
content_url = models.URLField(
verbose_name=_('LABEL_CONTENT_URL'),
max_length=128,
)
element_id = models.CharField(
verbose_name=_('LABEL_ELEMENT_ID'),
max_length=32,
blank=True,
)
def get_renderer_class(self):
return TabRenderer
class Meta:
verbose_name = _('MODEL_NAME_EXTERNAL_EMBEDDED_TAB')
verbose_name_plural = _('MODEL_NAME_EXTERNAL_EMBEDDED_TAB_PLURAL')
class ExternalIFrameTab(BaseTab):
"""
An ExternalIFrameTab gets its content from an external url resource through
an iframe which has the content_url as its src, possibly with additional
url parameters.
ExternalIFrameTab uses ExternalIFrameTabRenderer for rendering. Refer to
its documentation for more information about the available url parameters.
Iframes' width and height are fixed in the html document flow and thus they
should be given explicitly and they should be the size of the expected
content html.
"""
content_url = models.URLField(
verbose_name=_('LABEL_CONTENT_URL'),
max_length=255,
)
# Desired width and height
width = models.IntegerField(
verbose_name=_('LABEL_WIDTH'),
)
height = models.IntegerField(
verbose_name=_('LABEL_HEIGHT'),
)
def get_renderer_class(self):
return ExternalIFrameTabRenderer
class Meta:
verbose_name = _('MODEL_NAME_EXTERNAL_IFRAME_TAB')
verbose_name_plural = _('MODEL_NAME_EXTERNAL_IFRAME_TAB_PLURAL')
class BasePlugin(AbstractApp):
title = models.CharField(
verbose_name=_('LABEL_TITLE'),
max_length=64
)
views = models.CharField(
verbose_name=_('LABEL_VIEWS'),
max_length=255,
blank=True,
)
def render(self):
leaf = self.as_leaf_class()
if leaf != self:
return leaf.render()
return _('BASE_PLUGIN_MISSING_RENDER-METHOD')
class Meta:
verbose_name = _('MODEL_NAME_BASE_PLUGIN')
verbose_name_plural = _('MODEL_NAME_BASE_PLUGIN_PLURAL')
class RSSPlugin(BasePlugin):
feed_url = models.URLField(
verbose_name=_('LABEL_FEED_URL'),
max_length=256,
blank=False,
)
def render(self):
doc = feedparser.parse(self.feed_url)
feed = doc.feed
sorted_entries = sorted(doc["entries"], key=lambda entry: entry.published_parsed)
sorted_entries.reverse()
sorted_entries = sorted_entries[:5]
# Set timestamps in a format that Django knows how to handle in templates
for entry in sorted_entries:
entry.django_timestamp = datetime.datetime(*entry.published_parsed[:7])
out = loader.render_to_string("plugins/rss.html", {
"entries": sorted_entries,
"title": self.title,
"feed": feed,
"plugin": self
})
return out
class Meta:
verbose_name = _('MODEL_NAME_RSS_PLUGIN')
verbose_name_plural = _('MODEL_NAME_RSS_PLUGIN_PLURAL')
class HTMLPlugin(BasePlugin):
content = models.TextField(
verbose_name=_('LABEL_CONTENT'),
blank=False,
)
def render(self):
return mark_safe(self.content)
class Meta:
verbose_name = _('MODEL_NAME_HTML_PLUGIN')
verbose_name_plural = _('MODEL_NAME_HTML_PLUGIN_PLURAL')
class ExternalIFramePlugin(BasePlugin):
"""
An ExternalIFramePlugin gets its content from an external url resource
through an iframe which has the content_url as its src, possibly with
additional url parameters.
ExternalIFramePlugin uses ExternalIFramePluginRenderer for rendering. Refer
to its documentation for more information about the available url
parameters and its view behaviour.
Iframes' width and height are fixed in the html document flow and thus they
should be given explicitly and they should be at least the size of the
expected content html but at maximum the size available for the plugin in
each view which varies among the views. The size of the rendered iframe
will thus be the given width and height but at maximum the width and height
available in the view.
"""
service_url = models.URLField(
verbose_name=_('LABEL_SERVICE_URL'),
max_length=255,
)
# Desired width and height
width = models.IntegerField(
verbose_name=_('LABEL_WIDTH'),
)
height = models.IntegerField(
verbose_name=_('LABEL_HEIGHT'),
)
def get_renderer_class(self):
return ExternalIFramePluginRenderer
class Meta:
verbose_name = _('MODEL_NAME_EXTERNAL_IFRAME_PLUGIN')
verbose_name_plural = _('MODEL_NAME_EXTERNAL_IFRAME_PLUGIN_PLURAL')