• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Wirecloud / wirecloud / 9847109294

08 Jul 2024 09:20PM UTC coverage: 88.223% (+0.01%) from 88.21%
9847109294

Pull #545

github

web-flow
Merge 4c8983fc4 into 2a2576628
Pull Request #545: Add ability to define different widget layouts for different screen sizes

8208 of 9832 branches covered (83.48%)

Branch coverage included in aggregate %.

459 of 511 new or added lines in 18 files covered. (89.82%)

31 existing lines in 8 files now uncovered.

21696 of 24064 relevant lines covered (90.16%)

31.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.14
src/wirecloud/platform/iwidget/utils.py
1
# -*- coding: utf-8 -*-
2

3
# Copyright (c) 2012-2016 CoNWeT Lab., Universidad Politécnica de Madrid
4
# Copyright (c) 2020 Future Internet Consulting and Development Solutions S.L.
5

6
# This file is part of Wirecloud.
7

8
# Wirecloud is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12

13
# Wirecloud is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU Affero General Public License for more details.
17

18
# You should have received a copy of the GNU Affero General Public License
19
# along with Wirecloud.  If not, see <http://www.gnu.org/licenses/>.
20

21
from django.utils.translation import ugettext as _
13✔
22

23
from wirecloud.catalogue.models import CatalogueResource
13✔
24
from wirecloud.platform.models import IWidget, Tab
13✔
25

26

27
def parse_value_from_text(info, value):
13✔
28
    if info['type'] == 'boolean':
13✔
29
        return value.strip().lower() in ('true', '1', 'on')
13✔
30
    elif info['type'] == 'number':
13✔
31
        try:
13✔
32
            return float(value)
13✔
33
        except ValueError:
12✔
34
            try:
12✔
35
                return float(info['default'])
12✔
36
            except (KeyError, ValueError):
12✔
37
                return 0
12✔
38
    else:  # info['type'] in ('list', 'text', 'password'):
39
        return str(value)
13✔
40

41

42
def process_initial_value(vardef, initial_value=None):
13✔
43

44
    # Sets the default value of variable
45
    if vardef.get('readonly', False) is False and initial_value is not None:
13✔
46
        value = initial_value
13✔
47
    elif vardef.get('value', None) is not None:
13!
48
        value = vardef['value']
×
49
    elif vardef['default']:
13✔
50
        value = parse_value_from_text(vardef, vardef['default'])
13✔
51
    else:
52
        value = ''
12✔
53
    return value
13✔
54

55

56
def update_title_value(iwidget, data):
13✔
57

58
    if 'title' in data:
13✔
59
        if data['title'] is None or data['title'].strip() == '':
13✔
60
            iwidget_info = iwidget.widget.resource.get_processed_info()
12✔
61
            iwidget.name = iwidget_info['title']
12✔
62
        else:
63
            iwidget.name = data['title']
13✔
64

65

66
def update_boolean_value(model, data, field):
13✔
67
    if field in data:
13✔
68
        value = data[field]
13✔
69

70
        if type(value) is not bool:
13✔
71
            raise TypeError(_('Field %(field)s must contain a boolean value') % {"field": field})
12✔
72

73
        model[field] = value
13✔
74

75
def update_screen_size_value(model, data, field):
13✔
76
    if field in data:
13✔
77
        value = data[field]
13✔
78

79
        if type(value) not in (int,):
13✔
80
            raise TypeError(_('Field %(field)s must contain a number value') % {"field": field})
12✔
81

82
        if value < -1:
13✔
83
            raise ValueError(_('Invalid value for %(field)s') % {"field": field})
12✔
84

85
        model[field] = value
13✔
86

87
def update_position_value(model, data, field, data_field=None):
13✔
88
    data_field = data_field if data_field is not None else field
13✔
89
    if data_field in data:
13✔
90
        size = data[data_field]
13✔
91

92
        if type(size) not in (int, float):
13✔
93
            raise TypeError(_('Field %(field)s must contain a number value') % {"field": data_field})
12✔
94

95
        if size < 0:
13✔
96
            raise ValueError(_('Invalid value for %(field)s') % {"field": data_field})
12✔
97

98
        model[field] = size
13✔
99

100

101
def update_size_value(model, data, field):
13✔
102
    if field in data:
13✔
103
        size = data[field]
13✔
104

105
        if type(size) not in (int, float):
13✔
106
            raise TypeError(_('Field %(field)s must contain a number value') % {"field": field})
12✔
107

108
        if size <= 0:
13✔
109
            raise ValueError(_('Invalid value for %(field)s') % {"field": field})
12✔
110

111
        model[field] = size
13✔
112

113

114
def update_anchor_value(model, data):
13✔
115
    if "anchor" in data:
13✔
116
        anchor = data["anchor"]
13✔
117

118
        if type(anchor) != str:
13✔
119
            raise TypeError(_('anchor field must contain a string value'))
12✔
120

121
        if anchor not in ("top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right"):
13✔
122
            raise ValueError(_('Invalid value for anchor field'))
12✔
123

124
        model["anchor"] = anchor
13✔
125

126
def check_intervals(data):
13✔
127
    # The screen size intervals should cover the interval [0, +inf) and should not overlap nor have gaps,
128
    # each interval is defined by the properties 'moreOrEqual' and 'lessOrEqual'
129

130
    data.sort(key=lambda x: x.get('moreOrEqual', float('-inf')))
13✔
131

132
    if data[0].get('moreOrEqual') != 0:
13✔
133
        raise ValueError('The first interval must start from 0')
12✔
134

135
    for i in range(len(data) - 1):
13✔
136
        if data[i]['lessOrEqual'] + 1 != data[i + 1].get('moreOrEqual'):
13!
UNCOV
137
            raise ValueError('Intervals should not overlap nor have gaps')
×
138

139
    if data[-1]['lessOrEqual'] != -1:
13✔
140
        raise ValueError('The last interval must extend to infinity')
12✔
141

142
def update_position(iwidget, key, data):
13✔
143
    # Check if we have duplicate ids in the layoutConfigurations
144
    ids = set()
13✔
145
    for layoutConfig in data["layoutConfigurations"]:
13✔
146
        if 'id' not in layoutConfig:
13✔
147
            raise ValueError('Missing id field')
12✔
148
        if layoutConfig['id'] in ids:
13✔
149
            raise ValueError('Duplicate id field')
12✔
150
        ids.add(layoutConfig['id'])
13✔
151

152
    intervals = {}
13✔
153
    for conf in iwidget.positions["configurations"]:
13✔
154
        intervals[conf['id']] = conf
13✔
155

156
    for layoutConfig in data["layoutConfigurations"]:
13✔
157
        if not 'action' in layoutConfig:
13✔
158
            raise ValueError('Missing action field')
12✔
159
        if layoutConfig['action'] not in ('update', 'delete'):
13✔
160
            raise ValueError('Invalid value for action field: ' + layoutConfig['action'])
12✔
161

162
        if layoutConfig['action'] == 'delete':
13✔
163
            del intervals[layoutConfig['id']]
12✔
164
        else:
165
            if not layoutConfig['id'] in intervals:
13✔
166
                intervals[layoutConfig['id']] = {
13✔
167
                    'id': layoutConfig['id'],
168
                    'moreOrEqual': 0,
169
                    'lessOrEqual': -1,
170
                }
171

172
                intervals[layoutConfig['id']][key] = {
13✔
173
                    'top': 0,
174
                    'left': 0,
175
                    'zIndex': 0,
176
                    'height': 0,
177
                    'width': 0,
178
                    'minimized': False,
179
                    'titlevisible': True,
180
                    'fulldragboard': False
181
                }
182

183
            update_screen_size_value(intervals[layoutConfig['id']], layoutConfig, 'moreOrEqual')
13✔
184
            update_screen_size_value(intervals[layoutConfig['id']], layoutConfig, 'lessOrEqual')
13✔
185
            update_position_value(intervals[layoutConfig['id']][key], layoutConfig, 'top')
13✔
186
            update_position_value(intervals[layoutConfig['id']][key], layoutConfig, 'left')
13✔
187
            update_position_value(intervals[layoutConfig['id']][key], layoutConfig, 'zIndex')
13✔
188
            update_size_value(intervals[layoutConfig['id']][key], layoutConfig, 'height')
13✔
189
            update_size_value(intervals[layoutConfig['id']][key], layoutConfig, 'width')
13✔
190
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'minimized')
13✔
191
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'titlevisible')
13✔
192
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'fulldragboard')
13✔
193
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'relwidth')
13✔
194
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'relheight')
13✔
195
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'relx')
13✔
196
            update_boolean_value(intervals[layoutConfig['id']][key], layoutConfig, 'rely')
13✔
197
            update_anchor_value(intervals[layoutConfig['id']][key], layoutConfig)
13✔
198

199
    newPositions = list(intervals.values())
13✔
200
    check_intervals(newPositions)
13✔
201

202
    iwidget.positions["configurations"] = newPositions
13✔
203

204

205
def update_permissions(iwidget, data):
13✔
206
    permissions = iwidget.permissions.setdefault('viewer', {})
13✔
207
    update_boolean_value(permissions, data, 'move')
13✔
208

209

210
def update_widget_value(iwidget, data, user, required=False):
13✔
211

212
    if 'widget' in data:
13✔
213
        (widget_vendor, widget_name, widget_version) = data['widget'].split('/')
13✔
214
        resource = CatalogueResource.objects.select_related('widget').get(vendor=widget_vendor, short_name=widget_name, version=widget_version)
13✔
215
        if not resource.is_available_for(user):
13✔
216
            raise CatalogueResource.DoesNotExist
12✔
217

218
        if resource.resource_type() != 'widget':
13✔
219
            raise ValueError(_('%(uri)s is not a widget') % {"uri": data['widget']})
12✔
220

221
        iwidget.widget = resource.widget
13✔
222
        return resource
13✔
223
    elif required:
13✔
224
        raise ValueError('Missing widget info')
12✔
225

226

227
def set_initial_values(iwidget, initial_values, iwidget_info, user):
13✔
228

229
    for vardef in (iwidget_info['preferences'] + iwidget_info['properties']):
13✔
230
        if vardef['name'] in initial_values:
13✔
231
            initial_value = initial_values[vardef['name']]
13✔
232
        else:
233
            initial_value = None
13✔
234
        iwidget.set_variable_value(vardef['name'], process_initial_value(vardef, initial_value), user)
13✔
235

236

237
def SaveIWidget(iwidget, user, tab, initial_variable_values=None, commit=True):
13✔
238

239
    new_iwidget = IWidget(tab=tab)
13✔
240

241
    resource = update_widget_value(new_iwidget, iwidget, user, required=True)
13✔
242
    iwidget_info = resource.get_processed_info()
13✔
243
    new_iwidget.name = iwidget_info['title']
13✔
244
    new_iwidget.layout = iwidget.get('layout', 0)
13✔
245

246
    new_iwidget.positions = {
13✔
247
        'configurations': []
248
    }
249

250

251
    if initial_variable_values is not None:
13✔
252
        set_initial_values(new_iwidget, initial_variable_values, iwidget_info, user)
12✔
253

254
    update_title_value(new_iwidget, iwidget)
13✔
255
    if "layoutConfigurations" in iwidget:
13✔
256
        update_position(new_iwidget, 'widget', iwidget)
13✔
257
    else:
258
        # set default positions
259
        new_iwidget.positions['configurations'] = [{
12✔
260
            'moreOrEqual': 0,
261
            'lessOrEqual': -1,
262
            'id': 0,
263
            'widget': {
264
                'top': 0,
265
                'left': 0,
266
                'zIndex': 0,
267
                'height': 1,
268
                'width': 1,
269
                'minimized': False,
270
                'titlevisible': True,
271
                'fulldragboard': False,
272
            },
273
        }]
274

275
    if commit:
13✔
276
        new_iwidget.save()
13✔
277

278
    return new_iwidget
13✔
279

280

281
def UpdateIWidget(data, user, tab, updatecache=True):
13✔
282

283
    iwidget = IWidget.objects.get(tab=tab, pk=data.get('id'))
13✔
284

285
    update_widget_value(iwidget, data, user)
13✔
286
    update_title_value(iwidget, data)
13✔
287

288
    if 'tab' in data:
13✔
289
        if data['tab'] != tab.id:
13!
290
            newtab = Tab.objects.get(workspace__pk=tab.workspace_id, pk=data['tab'])
13✔
291
            iwidget.tab = newtab
13✔
292

293
    if 'layout' in data:
13✔
294
        if data['layout'] < 0:
13✔
295
            raise ValueError('Invalid value for layout field')
12✔
296
        layout = data['layout']
13✔
297
        iwidget.layout = layout
13✔
298

299
    update_permissions(iwidget, data.get('permissions', {}).get('viewer', {}))
13✔
300

301
    # update positions
302
    if "layoutConfigurations" in data:
13✔
303
        update_position(iwidget, 'widget', data)
13✔
304

305
    # save the changes
306
    iwidget.save(updatecache=updatecache)
13✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc