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

OCHA-DAP / hdx-ckan / #5847

09 Nov 2024 05:10PM UTC coverage: 74.501% (+0.4%) from 74.062%
#5847

Pull #6470

coveralls-python

danmihaila
HDX-10191 replace xls with xlsx label
Pull Request #6470: HDX-10191 org stats download as xlsx

75 of 115 new or added lines in 5 files covered. (65.22%)

1 existing line in 1 file now uncovered.

12403 of 16648 relevant lines covered (74.5%)

0.75 hits per line

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

73.25
/ckanext-hdx_org_group/ckanext/hdx_org_group/helpers/organization_helper.py
1
"""
2
Created on Jan 14, 2015
3

4
@author: alexandru-m-g
5
"""
6

7
import json
1✔
8
import logging
1✔
9
import os
1✔
10
import six
1✔
11
import openpyxl
1✔
12
import ckanext.hdx_search.cli.click_feature_search_command as lunr
1✔
13
import ckanext.hdx_users.helpers.mailer as hdx_mailer
1✔
14
from sqlalchemy import func
1✔
15
import ckanext.hdx_org_group.helpers.static_lists as static_lists
1✔
16
from flask import make_response
1✔
17
from tempfile import NamedTemporaryFile
1✔
18
import ckan.lib.dictization as dictization
1✔
19
import ckan.lib.dictization.model_dictize as model_dictize
1✔
20
import ckan.lib.dictization.model_save as model_save
1✔
21
import ckan.lib.helpers as helpers
1✔
22
import ckan.lib.navl.dictization_functions
1✔
23
import ckan.lib.plugins as lib_plugins
1✔
24
import ckan.lib.uploader as uploader
1✔
25
import ckan.logic as logic
1✔
26
import ckan.logic.action as core
1✔
27
import ckan.model as model
1✔
28
import ckan.plugins as plugins
1✔
29
from collections import OrderedDict
1✔
30
from ckan.common import _, c, config
1✔
31
import ckan.plugins.toolkit as toolkit
1✔
32
import ckan.lib.base as base
1✔
33
import ckanext.hdx_theme.util.jql as jql
1✔
34
from openpyxl.styles import Alignment, Font
1✔
35

36
BUCKET = str(uploader.get_storage_path()) + '/storage/uploads/group/'
1✔
37
abort = base.abort
1✔
38

39
log = logging.getLogger(__name__)
1✔
40
chained_action = toolkit.chained_action
1✔
41
get_action = logic.get_action
1✔
42
check_access = logic.check_access
1✔
43
_get_or_bust = logic.get_or_bust
1✔
44
_validate = ckan.lib.navl.dictization_functions.validate
1✔
45

46
NotFound = logic.NotFound
1✔
47
ValidationError = logic.ValidationError
1✔
48

49

50
def filter_and_sort_results_case_insensitive(results, sort_by, q=None, has_datasets=False):
1✔
51
    """
52
    :param results: list of organizations to filter/sort
53
    :type results: list[dict]
54
    :param sort_by:
55
    :type sort_by: str
56
    :param q:
57
    :type q: str
58
    :param has_datasets: True if it should filter out orgs without at least one datasets
59
    :type has_datasets: bool
60
    :return: sorted/filtered list
61
    :rtype: list[dict]
62
    """
63

64
    filtered_results = results
1✔
65
    if q:
1✔
66
        q = q.lower()
×
67
        filtered_results = [org for org in filtered_results
×
68
                            if q in org.get('title', '').lower() or q in org.get('name', '')
69
                            or q in org.get('description', '').lower()]
70
    if has_datasets:
1✔
71
        filtered_results = [org for org in filtered_results if 'regular_package_count' in org and org['regular_package_count']]
1✔
72

73
    if filtered_results:
1✔
74
        if sort_by == 'title asc':
×
75
            return sorted(filtered_results, key=lambda x: x.get('title', '').lower())
×
76
        elif sort_by == 'title desc':
×
77
            return sorted(filtered_results, key=lambda x: x.get('title', '').lower(), reverse=True)
×
78
        elif sort_by == 'datasets desc':
×
79
            return sorted(filtered_results, key=lambda x: x.get('package_count', 0), reverse=True)
×
80
        elif sort_by == 'datasets asc':
×
81
            return sorted(filtered_results, key=lambda x: x.get('package_count', 0))
×
82
        elif sort_by == 'popularity':
×
83
            pass
×
84
    return filtered_results
1✔
85

86

87
# def hdx_get_featured_orgs(context, data_dict):
88
#     orgs = list()
89
#     # Pull resource with data on our featured orgs
90
#     resource_id = config.get('hdx.featured_org_config')  # Move this to common_config once data team has set things up
91
#     user = context.get('user')
92
#     userobj = context.get('auth_user_obj')
93
#     reset_thumbnails = data_dict.get('reset_thumbnails', 'false')
94
#     featured_config = get_featured_orgs_config(user, userobj, resource_id)
95
#
96
#     for cfg in featured_config:
97
#         # getting the first 3 rows/organizations
98
#         if len(orgs) < 3:
99
#             # Check for screencap
100
#             file_path = BUCKET + cfg.get('org_name') + '_thumbnail.png'
101
#             exists = os.path.isfile(file_path)
102
#             expired = False
103
#             if exists:
104
#                 timestamp = datetime.fromtimestamp(os.path.getmtime(file_path))
105
#                 expire = timestamp + timedelta(days=7)
106
#                 expired = datetime.utcnow() > expire
107
#             reset = not exists or expired
108
#             if reset or reset_thumbnails == 'true':
109
#                 # Build new screencap
110
#                 context['reset'] = reset
111
#                 context['file_path'] = file_path
112
#                 context['cfg'] = cfg
113
#                 log.info("Triggering screenshot for " + cfg.get('org_name'))
114
#                 get_action('hdx_trigger_screencap')(context, data_dict)
115
#
116
#             org_dict = get_action('organization_show')(context, {'id': cfg.get('org_name')})
117
#
118
#             # Build highlight data
119
#             org_dict['highlight'] = get_featured_org_highlight(context, org_dict, cfg)
120
#
121
#             # checking again here if the file was generated
122
#             exists = os.path.isfile(file_path)
123
#             if exists:
124
#                 org_dict['featured_org_thumbnail'] = "/image/" + cfg['org_name'] + '_thumbnail.png'
125
#             else:
126
#                 org_dict['featured_org_thumbnail'] = "/images/featured_orgs_placeholder" + str(len(orgs)) + ".png"
127
#             orgs.append(org_dict)
128
#     return orgs
129

130

131
def get_viz_title_from_extras(org_dict):
1✔
132
    try:
×
133
        for item in org_dict.get('extras'):
×
134
            if item.get('key') == 'visualization_config':
×
135
                result = json.loads(item.get('value')).get('vis-title') or json.loads(item.get('value')).get(
×
136
                    'viz-title')
137
                return result
×
138
    except:
×
139
        return None
×
140
    return None
×
141

142

143
def get_value_dict_from_extras(org_dict, key='visualization_config'):
1✔
144
    try:
×
145
        for item in org_dict.get('extras'):
×
146
            if item.get('key') == key:
×
147
                return json.loads(item.get('value'))
×
148
    except:
×
149
        return None
×
150
    return None
×
151

152

153
def get_featured_org_highlight(context, org_dict, config):
1✔
154
    link = helpers.url_for('organization_read', id=org_dict.get('name'))
1✔
155
    title = ''
1✔
156
    description = ''
1✔
157
    if config.get('highlight_asset_type').strip().lower() == 'key figures':
1✔
158
        description = 'Key Figures'
1✔
159
        link += '#key-figures'
1✔
160
    if config.get('highlight_asset_type').strip().lower() == 'interactive data':
1✔
161
        description = 'Interactive Data: '
×
162
        link += '#interactive-data'
×
163
        title = get_viz_title_from_extras(org_dict)
×
164
    return {'link': link, 'description': description, 'type': config.get('highlight_asset_type'), 'title': title}
1✔
165

166

167
# def get_featured_orgs_config(user, userobj, resource_id):
168
#     context = {'model': model, 'session': model.Session,
169
#                'user': user, 'for_view': True,
170
#                'auth_user_obj': userobj}
171
#     featured_org_dict = {
172
#         'datastore_config': {
173
#             'resource_id': resource_id
174
#         }
175
#     }
176
#     datastore_access = data_access.DataAccess(featured_org_dict)
177
#     datastore_access.fetch_data_generic(context)
178
#     org_items = datastore_access.get_top_line_items()
179
#
180
#     return org_items
181

182

183
def hdx_get_group_activity_list(context, data_dict):
1✔
184
    from ckanext.hdx_package.helpers import helpers as hdx_package_helpers
×
185

186
    group_uuid = data_dict.get('group_uuid', None)
×
187
    if group_uuid:
×
188
        check_access('group_show', context, data_dict)
×
189

190
        model = context['model']
×
191
        offset = data_dict.get('offset', 0)
×
192
        limit = int(
×
193
            data_dict.get('limit', config.get('ckan.activity_list_limit', 31)))
194

195
        activity_objects = model.activity.group_activity_list(group_uuid,
×
196
                                                              limit=limit, offset=offset)
197
        activity_stream = model_dictize.activity_list_dictize(
×
198
            activity_objects, context)
199
    else:
200
        if 'group_type' in data_dict and data_dict['group_type'] == 'organization':
×
201
            activity_stream = get_action(
×
202
                'organization_activity_list')(context, data_dict)
203
        else:
204
            activity_stream = get_action(
×
205
                'group_activity_list')(context, data_dict)
206
    offset = int(data_dict.get('offset', 0))
×
207
    extra_vars = {
×
208
        'controller': 'group',
209
        'action': 'activity',
210
        'id': data_dict['id'],
211
        'offset': offset,
212
    }
213
    return hdx_package_helpers._activity_list(context, activity_stream, extra_vars)
×
214

215

216
# def compile_less(result, translate_func=None):
217
#     return True
218
    # base_color = '#007CE0'  # default value
219
    # logo_use_org_color = "false"
220
    #
221
    # def get_value_from_result(key):
222
    #     value = result.get(key)
223
    #     if not value:
224
    #         extra = next((e for e in (result.get('extras') or []) if e['key'] == key), None)
225
    #         if extra:
226
    #             value = extra.get('value')
227
    #     return value
228
    #
229
    # less_code_list = get_value_from_result('less')
230
    # customization = get_value_from_result('customization')
231
    #
232
    # if customization:
233
    #     try:
234
    #         variables = json.loads(customization)
235
    #         base_color = variables.get('highlight_color', '#007CE0') or '#007CE0'
236
    #         logo_use_org_color = variables.get('use_org_color', 'false')
237
    #     except:
238
    #         base_color = '#007CE0'
239
    #
240
    # if less_code_list:
241
    #     less_code = less_code_list.strip()
242
    #     if less_code:
243
    #         less_code = _add_custom_less_code(base_color, logo_use_org_color) + less_code
244
    #         css_dest_dir = '/organization/' + result['name']
245
    #         compiler = less.LessCompiler(less_code, css_dest_dir, result['name'],
246
    #                                      h.hdx_get_extras_element(result, value_key="modified_at"),
247
    #                                      translate_func=translate_func)
248
    #         compilation_result = compiler.compile_less()
249
    #         result['less_compilation'] = compilation_result
250

251

252
# def _add_custom_less_code(base_color, logo_use_org_color):
253
#     # Add base color definition
254
#     less_code = '\n\r@wfpBlueColor: ' + base_color + ';\n\r'
255
#     if not 'true' == logo_use_org_color:
256
#         less_code += '@logoBackgroundColor: #FAFAFA; @logoBorderColor: #CCCCCC;'
257
#     return less_code
258

259

260
def hdx_organization_update(context, data_dict):
1✔
261
    test = True if config.get('ckan.site_id') == 'test.ckan.net' else False
1✔
262
    result = hdx_group_or_org_update(context, data_dict, is_org=True)
1✔
263
    if not test:
1✔
264
        lunr.build_index()
×
265

266
    # hdx_generate_embedded_preview(result)
267
    return result
1✔
268

269

270
# def hdx_generate_embedded_preview(result):
271
#     org_name = result.get('name') or result.get('id')
272
#     vis_config = get_value_dict_from_extras(result, 'visualization_config')
273
#     if vis_config and vis_config.get('visualization-select') == 'embedded-preview':
274
#         selector = vis_config.get('vis-preview-selector', None)
275
#         url = vis_config.get('vis-url')
276
#         file_path = BUCKET + org_name + '_embedded_preview.png'
277
#         hdx_capturejs(url, file_path, selector, renderdelay=15000)
278
#         return True
279
#     return False
280

281

282
def remove_image(filename):
1✔
283
    if not filename.startswith('http'):
×
284
        try:
×
285
            os.remove(uploader.get_storage_path() + '/storage/uploads/group/' + filename)
×
286
        except:
×
287
            return False
×
288
    return True
×
289

290

291
def hdx_group_create(context, data_dict):
1✔
292
    return _run_core_group_org_action(context, data_dict, core.create.group_create)
1✔
293

294

295
def hdx_group_update(context, data_dict):
1✔
296
    return _run_core_group_org_action(context, data_dict, core.update.group_update)
1✔
297

298

299
def hdx_group_delete(context, data_dict):
1✔
300
    return _run_core_group_org_action(context, data_dict, core.delete.group_delete)
1✔
301

302
def _check_user_is_maintainer(user_id, org_id):
1✔
303
    group = model.Group.get(org_id)
1✔
304
    result = logic.get_action('package_search')({}, {
1✔
305
        'q': '*:*',
306
        'fq': 'maintainer:{0}, organization:{1}'.format(user_id, group.name),
307
        'rows': 100,
308
    })
309

310
    if len(result['results']) > 0:
1✔
311
        return True
1✔
312
    return False
1✔
313

314
@chained_action
1✔
315
def organization_member_delete(original_action, context, data_dict):
1✔
316
    user_id = data_dict.get('user_id') or data_dict.get('user')
1✔
317
    if not user_id:
1✔
318
        user_id = model.User.get(data_dict.get('username')).id
1✔
319

320
    if _check_user_is_maintainer(user_id, data_dict.get('id')):
1✔
321
        abort(403, _('User is set as maintainer for datasets belonging to this org. Can\t delete, please change maintainer first'))
1✔
322

323
    return original_action(context, data_dict)
1✔
324

325
@chained_action
1✔
326
def organization_member_create(original_action, context, data_dict):
1✔
327
    user_id = data_dict.get('user') or data_dict.get('user_id')
1✔
328
    if not user_id:
1✔
329
        user_id = model.User.get(data_dict.get('username')).id
1✔
330

331
    if data_dict.get('role') == 'member':
1✔
332
        if _check_user_is_maintainer(user_id, data_dict.get('id')):
1✔
333
            abort(403, _('User is set as maintainer for datasets belonging to this org. Can\'t change role to \'member\', please change maintainer first'))
×
334

335
    return original_action(context, data_dict)
1✔
336

337
def hdx_organization_create(context, data_dict):
1✔
338
    data_dict['type'] = 'organization'
1✔
339
    test = True if config.get('ckan.site_id') == 'test.ckan.net' else False
1✔
340
    result = hdx_group_or_org_create(context, data_dict, is_org=True)
1✔
341
    if not test:
1✔
342
        lunr.build_index()
×
343

344
    # hdx_generate_embedded_preview(result)
345
    return result
1✔
346

347

348
def hdx_organization_delete(context, data_dict):
1✔
349
    return _run_core_group_org_action(context, data_dict, core.delete.organization_delete)
1✔
350

351

352
def _run_core_group_org_action(context, data_dict, core_action):
1✔
353
    """
354
    Runs core ckan action with lunr update
355
    """
356
    test = True if config.get('ckan.site_id') == 'test.ckan.net' else False
1✔
357
    result = core_action(context, data_dict)
1✔
358
    if not test:
1✔
359
        lunr.build_index()
×
360
    return result
1✔
361

362

363
def hdx_group_or_org_update(context, data_dict, is_org=False):
1✔
364
    # Overriding default so that orgs can have multiple images
365
    model = context['model']
1✔
366
    user = context['user']
1✔
367
    session = context['session']
1✔
368
    id = _get_or_bust(data_dict, 'id')
1✔
369

370
    group = model.Group.get(id)
1✔
371
    context['group'] = group
1✔
372
    if group is None:
1✔
373
        raise NotFound('Group was not found.')
×
374

375
    data_dict['type'] = group.type
1✔
376

377
    # get the schema
378
    group_plugin = lib_plugins.lookup_group_plugin(group.type)
1✔
379
    try:
1✔
380
        schema = group_plugin.form_to_db_schema_options({'type': 'update',
1✔
381
                                                         'api': 'api_version' in context,
382
                                                         'context': context})
383
    except AttributeError:
×
384
        schema = group_plugin.form_to_db_schema()
×
385

386
    if is_org:
1✔
387
        check_access('organization_update', context, data_dict)
1✔
388
    else:
389
        check_access('group_update', context, data_dict)
×
390

391

392
    try:
1✔
393
        customization = json.loads(group.extras['customization'])
1✔
394
    except:
×
395
        customization = {'image_sq': '', 'image_rect': ''}
×
396

397
    try:
1✔
398
        data_dict['customization'] = json.loads(data_dict['customization'])
1✔
399
    except:
1✔
400
        data_dict['customization'] = {}
1✔
401

402
    # If we're removing the image
403
    upload_sq = _manage_image_upload_for_org('image_sq', customization, data_dict)
1✔
404
    upload_rect = _manage_image_upload_for_org('image_rect', customization, data_dict)
1✔
405

406
    storage_path = uploader.get_storage_path()
1✔
407
    ##Rearrange things the way we need them
408
    try:
1✔
409
        if data_dict['image_sq'] != '' and data_dict['image_sq'] != None:
1✔
410
            data_dict['customization']['image_sq'] = data_dict['image_sq']
×
411
        else:
412
            data_dict['customization']['image_sq'] = customization['image_sq']
×
413
    except KeyError:
1✔
414
        data_dict['customization']['image_sq'] = ''
1✔
415

416
    try:
1✔
417
        if data_dict['image_rect'] != '' and data_dict['image_rect'] != None:
1✔
418
            data_dict['customization']['image_rect'] = data_dict['image_rect']
×
419
        else:
420
            data_dict['customization']['image_rect'] = customization['image_rect']
×
421
    except KeyError:
1✔
422
        data_dict['customization']['image_rect'] = ''
1✔
423

424
    data_dict['customization'] = json.dumps(data_dict['customization'])
1✔
425

426
    if 'api_version' not in context:
1✔
427
        # old plugins do not support passing the schema so we need
428
        # to ensure they still work
429
        try:
1✔
430
            group_plugin.check_data_dict(data_dict, schema)
1✔
431
        except TypeError:
1✔
432
            group_plugin.check_data_dict(data_dict)
1✔
433

434
    data, errors = lib_plugins.plugin_validate(
1✔
435
        group_plugin, context, data_dict, schema,
436
        'organization_update' if is_org else 'group_update')
437
    log.debug('group_update validate_errs=%r user=%s group=%s data_dict=%r',
1✔
438
              errors, context.get('user'),
439
              context.get('group').name if context.get('group') else '',
440
              data_dict)
441

442
    if errors:
1✔
443
        session.rollback()
×
444
        raise ValidationError(errors)
×
445

446
    contains_packages = 'packages' in data_dict
1✔
447

448
    group = model_save.group_dict_save(
1✔
449
        data, context,
450
        prevent_packages_update=is_org or not contains_packages
451
    )
452

453
    if is_org:
1✔
454
        plugin_type = plugins.IOrganizationController
1✔
455
    else:
456
        plugin_type = plugins.IGroupController
×
457

458
    for item in plugins.PluginImplementations(plugin_type):
1✔
459
        item.edit(group)
1✔
460

461
    if is_org:
1✔
462
        activity_type = 'changed organization'
1✔
463
    else:
464
        activity_type = 'changed group'
×
465

466
    activity_dict = {
1✔
467
            'user_id': model.User.by_name(six.ensure_text(user)).id,
468
            'object_id': group.id,
469
            'activity_type': activity_type,
470
            }
471
    # Handle 'deleted' groups.
472
    # When the user marks a group as deleted this comes through here as
473
    # a 'changed' group activity. We detect this and change it to a 'deleted'
474
    # activity.
475
    if group.state == u'deleted':
1✔
476
        if session.query(ckan.model.Activity).filter_by(
×
477
                object_id=group.id, activity_type='deleted').all():
478
            # A 'deleted group' activity for this group has already been
479
            # emitted.
480
            # FIXME: What if the group was deleted and then activated again?
481
            activity_dict = None
×
482
        else:
483
            # We will emit a 'deleted group' activity.
484
            activity_dict['activity_type'] = \
×
485
                'deleted organization' if is_org else 'deleted group'
486
    if activity_dict is not None:
1✔
487
        activity_dict['data'] = {
1✔
488
            'group': dictization.table_dictize(group, context)
489
        }
490
        activity_create_context = {
1✔
491
            'model': model,
492
            'user': user,
493
            'defer_commit': True,
494
            'ignore_auth': True,
495
            'session': session
496
        }
497
        get_action('activity_create')(activity_create_context, activity_dict)
1✔
498
        # TODO: Also create an activity detail recording what exactly changed
499
        # in the group.
500

501
    if upload_sq:
1✔
502
        upload_sq.upload(uploader.get_max_image_size())
×
503

504
    if upload_rect:
1✔
505
        upload_rect.upload(uploader.get_max_image_size())
×
506

507
    if not context.get('defer_commit'):
1✔
508
        model.repo.commit()
1✔
509

510
    return model_dictize.group_dictize(group, context)
1✔
511

512

513
def _manage_image_upload_for_org(image_field, customization, data_dict):
1✔
514
    clear_field = 'clear_{}'.format(image_field)
1✔
515
    if clear_field in data_dict and data_dict[clear_field]:
1✔
516
        remove_image(customization[image_field])
×
517
        data_dict['customization'][image_field] = ''
×
518
        # customization[image_field] = ''
519

520
    upload_field = '{}_upload'.format(image_field)
1✔
521
    if data_dict.get(upload_field):
1✔
522
        # If old image exists remove it
523
        if customization[image_field]:
×
524
            remove_image(customization[image_field])
×
525

526
        upload_obj = uploader.Upload('group', customization[image_field])
×
527
        upload_obj.update_data_dict(data_dict, image_field,
×
528
                                 upload_field, 'clear_upload')
529

530
        return upload_obj
×
531
    return None
1✔
532

533

534
def hdx_group_or_org_create(context, data_dict, is_org=False):
1✔
535
    # Overriding default so that orgs can have multiple images
536

537
    model = context['model']
1✔
538
    user = context['user']
1✔
539
    session = context['session']
1✔
540
    data_dict['is_organization'] = is_org
1✔
541

542
    if is_org:
1✔
543
        check_access('organization_create', context, data_dict)
1✔
544
    else:
545
        check_access('group_create', context, data_dict)
×
546

547
    # get the schema
548
    group_type = data_dict.get('type')
1✔
549
    group_plugin = lib_plugins.lookup_group_plugin(group_type)
1✔
550
    try:
1✔
551
        schema = group_plugin.form_to_db_schema_options({
1✔
552
            'type': 'create', 'api': 'api_version' in context,
553
            'context': context})
554
    except AttributeError:
×
555
        schema = group_plugin.form_to_db_schema()
×
556

557
    # try:
558
    #     customization = json.loads(group.extras['customization'])
559
    # except:
560
    customization = {'image_sq': '', 'image_rect': ''}
1✔
561

562
    try:
1✔
563
        data_dict['customization'] = json.loads(data_dict['customization'])
1✔
564
    except:
1✔
565
        data_dict['customization'] = {}
1✔
566

567
    if 'image_sq_upload' in data_dict and data_dict['image_sq_upload'] != '' and data_dict['image_sq_upload'] != None:
1✔
568
        # If old image was uploaded remove it
569
        if customization['image_sq']:
×
570
            remove_image(customization['image_sq'])
×
571

572
        upload1 = uploader.Upload('group', customization['image_sq'])
×
573
        upload1.update_data_dict(data_dict, 'image_sq',
×
574
                                 'image_sq_upload', 'clear_upload')
575

576
    if 'image_rect_upload' in data_dict and data_dict['image_rect_upload'] != '' and data_dict[
1✔
577
        'image_rect_upload'] != None:
578
        if customization['image_rect']:
×
579
            remove_image(customization['image_rect'])
×
580
        upload2 = uploader.Upload('group', customization['image_rect'])
×
581
        upload2.update_data_dict(data_dict, 'image_rect',
×
582
                                 'image_rect_upload', 'clear_upload')
583

584
    storage_path = uploader.get_storage_path()
1✔
585
    ##Rearrange things the way we need them
586
    try:
1✔
587
        if data_dict['image_sq'] != '' and data_dict['image_sq'] != None:
1✔
588
            data_dict['customization']['image_sq'] = data_dict['image_sq']
×
589
        else:
590
            data_dict['customization']['image_sq'] = customization['image_sq']
×
591
    except KeyError:
1✔
592
        data_dict['customization']['image_sq'] = ''
1✔
593

594
    try:
1✔
595
        if data_dict['image_rect'] != '' and data_dict['image_rect'] != None:
1✔
596
            data_dict['customization']['image_rect'] = data_dict['image_rect']
×
597
        else:
598
            data_dict['customization']['image_rect'] = customization['image_rect']
×
599
    except KeyError:
1✔
600
        data_dict['customization']['image_rect'] = ''
1✔
601

602
    data_dict['customization'] = json.dumps(data_dict['customization'])
1✔
603

604
    if 'api_version' not in context:
1✔
605
        # old plugins do not support passing the schema so we need
606
        # to ensure they still work
607
        try:
1✔
608
            group_plugin.check_data_dict(data_dict, schema)
1✔
609
        except TypeError:
1✔
610
            group_plugin.check_data_dict(data_dict)
1✔
611

612
    data, errors = lib_plugins.plugin_validate(
1✔
613
        group_plugin, context, data_dict, schema,
614
        'organization_create' if is_org else 'group_create')
615
    log.debug('group_create validate_errs=%r user=%s group=%s data_dict=%r',
1✔
616
              errors, context.get('user'), data_dict.get('name'), data_dict)
617

618
    if errors:
1✔
619
        session.rollback()
1✔
620
        raise ValidationError(errors)
1✔
621

622
    group = model_save.group_dict_save(data, context)
1✔
623

624
    # Needed to let extensions know the group id
625
    session.flush()
1✔
626

627
    if is_org:
1✔
628
        plugin_type = plugins.IOrganizationController
1✔
629
    else:
630
        plugin_type = plugins.IGroupController
×
631

632
    for item in plugins.PluginImplementations(plugin_type):
1✔
633
        item.create(group)
1✔
634

635
    if is_org:
1✔
636
        activity_type = 'new organization'
1✔
637
    else:
638
        activity_type = 'new group'
×
639

640
    user_id = model.User.by_name(six.ensure_text(user)).id
1✔
641

642
    activity_dict = {
1✔
643
        'user_id': user_id,
644
        'object_id': group.id,
645
        'activity_type': activity_type,
646
        'data': {
647
            'group': ckan.lib.dictization.table_dictize(group, context)
648
        }
649
    }
650
    activity_create_context = {
1✔
651
        'model': model,
652
        'user': user,
653
        'defer_commit': True,
654
        'ignore_auth': True,
655
        'session': session
656
    }
657
    logic.get_action('activity_create')(activity_create_context, activity_dict)
1✔
658

659
    try:
1✔
660
        upload1.upload(uploader.get_max_image_size())
1✔
661
    except:
1✔
662
        pass
1✔
663

664
    try:
1✔
665
        upload2.upload(uploader.get_max_image_size())
1✔
666
    except:
1✔
667
        pass
1✔
668

669
    if not context.get('defer_commit'):
1✔
670
        model.repo.commit()
1✔
671
    context['group'] = group
1✔
672
    context['id'] = group.id
1✔
673

674
    # creator of group/org becomes an admin
675
    # this needs to be after the repo.commit or else revisions break
676
    member_dict = {
1✔
677
        'id': group.id,
678
        'object': user_id,
679
        'object_type': 'user',
680
        'capacity': 'admin',
681
    }
682
    member_create_context = {
1✔
683
        'model': model,
684
        'user': user,
685
        'ignore_auth': True,  # we are not a member of the group at this point
686
        'session': session
687
    }
688
    logic.get_action('member_create')(member_create_context, member_dict)
1✔
689

690
    log.debug('Created object %s' % group.name)
1✔
691

692
    return_id_only = context.get('return_id_only', False)
1✔
693
    action = 'organization_show' if is_org else 'group_show'
1✔
694

695
    output = context['id'] if return_id_only \
1✔
696
        else get_action(action)(context, {'id': group.id})
697

698
    return output
1✔
699

700

701
# def recompile_everything(context):
702
#     orgs = get_action('organization_list')(context, {'all_fields': False})
703
#     if orgs:
704
#         for org_name in orgs:
705
#             org = get_action('hdx_light_group_show')(context, {'id': org_name})
706
#             compile_less(org, translate_func=lambda str: str)
707

708

709
# def hdx_capturejs(uri, output_file, selector, renderdelay=90000, waitcapturedelay=10000, viewportsize='1200x800'):
710
#     quoted_selector = '"{}"'.format(selector)
711
#     screenshot_creator = ScreenshotCreator(uri, output_file, quoted_selector,
712
#                                            renderdelay=renderdelay, waitcapturedelay=waitcapturedelay,
713
#                                            http_timeout=None,
714
#                                            viewportsize=viewportsize, mogrify=True, resize='40%')
715
#     return screenshot_creator.execute()
716

717
def notify_admins(data_dict):
1✔
718
    try:
×
719
        if data_dict.get('admins'):
×
720
            # for admin in data_dict.get('admins'):
721
            hdx_mailer.mail_recipient(data_dict.get('admins'), data_dict.get('subject'), data_dict.get('message'))
×
722
    except Exception as e:
×
NEW
723
        log.error('Email server error: can not send email to admin users' + e.message)
×
724
        return False
×
NEW
725
    log.info('admin users where notified by email')
×
726
    return True
×
727

728

729
def hdx_user_in_org_or_group(group_id, include_pending=False):
1✔
730
    '''
731
    Based on user_in_org_or_group() from ckan.lib.helpers.
732
    Added a flag that includes "pending" requests in the check.
733
    Useful for not showing the "request membership" option for a user that already has done the request.
734
    :param group_id:
735
    :type group_id: str
736
    :param include_pending: if it should include the "pending" state in the check ( not just the "active") (optional)
737
    :type include_pending: bool
738
    :return: True if the user belongs to the group or org. Otherwise False
739
    :rtype: bool
740
    '''
741

742
    # we need a user
743
    if not c.userobj:
1✔
744
        return False
1✔
745
    # sysadmins can do anything
746
    if c.userobj.sysadmin:
1✔
747
        return True
1✔
748

749
    checked_states = ['active']
1✔
750
    if include_pending:
1✔
751
        checked_states.append('pending')
1✔
752

753
    query = model.Session.query(func.count(model.Member.id)) \
1✔
754
        .filter(model.Member.state.in_(checked_states)) \
755
        .filter(model.Member.table_name == 'user') \
756
        .filter(model.Member.group_id == group_id) \
757
        .filter(model.Member.table_id == c.userobj.id)
758
    length = query.all()[0][0]
1✔
759
    return length != 0
1✔
760

761

762
def hdx_organization_type_dict(include_default_value=None):
1✔
763
    result = OrderedDict()
1✔
764

765
    if include_default_value:
1✔
766
        result['-1'] = _('-- Please select --')
×
767

768
    result.update(OrderedDict({t[1]: _(t[0]) for t in static_lists.ORGANIZATION_TYPE_LIST}))
1✔
769

770
    return result
1✔
771

772

773
def _find_last_update_for_orgs(org_names):
1✔
774
    org_to_update_time = {}
1✔
775
    if org_names:
1✔
776
        context = {
1✔
777
            'model': model,
778
            'session': model.Session
779
        }
780
        fq_filter = 'organization:({}) +dataset_type:dataset'.format(' OR '.join(org_names))
1✔
781

782
        data_dict = {
1✔
783
            'q': '',
784
            'fq': fq_filter,
785
            'fq_list': ['{!collapse field=organization nullPolicy=expand sort="metadata_modified desc"} '],
786
            'rows': len(org_names),
787
            'start': 0,
788
            'sort': 'metadata_modified desc'
789
        }
790
        query = get_action('package_search')(context, data_dict)
1✔
791
        org_to_update_time = {d['organization']['name']: d.get('metadata_modified') for d in query['results']}
1✔
792
    return org_to_update_time
1✔
793

794

795
def org_add_last_updated_field(displayed_orgs):
1✔
796
    org_to_last_update = _find_last_update_for_orgs([o.get('name') for o in displayed_orgs])
1✔
797
    for o in displayed_orgs:
1✔
798
        o['dataset_last_updated'] = org_to_last_update.get(o['name'], o.get('created'))
1✔
799

800

801
def hdx_organization_type_get_value(org_type_key):
1✔
802
    return next((org_type[0] for org_type in static_lists.ORGANIZATION_TYPE_LIST if org_type[1] == org_type_key),
×
803
                org_type_key)
804

805
def _get_mixpanel_data(org_id):
1✔
NEW
806
    result = jql.pageviews_downloads_per_organization_last_5_years(org_id)
×
NEW
807
    return result
×
808

809
def hdx_generate_organization_stats(org_dict):
1✔
810

811
    # Define variable to load the dataframe
812
    wb = openpyxl.Workbook()
1✔
813

814
    # Bold font style
815
    bold_font = Font(bold=True)
1✔
816

817
    # Create SheetOne with Data
818
    sheet_one = wb.active
1✔
819
    sheet_one.title = 'Page views and Downloads'
1✔
820

821
    result = _get_mixpanel_data(org_dict.get('id'))
1✔
822
    data = [('Date', 'Page Views - Unique', 'Page Views - Total', 'Downloads - Unique', 'Downloads - Total')]
1✔
823
    for key, value in result.items():
1✔
NEW
824
        data.append((key, value.get('pageviews_unique'), value.get('pageviews_total'), value.get('downloads_unique'), value.get('downloads_total')))
×
825

826
    for row_num, row_data in enumerate(data, start=1):
1✔
827
        for col_num, cell_value in enumerate(row_data, start=1):
1✔
828
            cell = sheet_one.cell(row=row_num, column=col_num, value=cell_value)
1✔
829
            if row_num == 1:
1✔
830
                cell.font = bold_font  # Apply bold to header row
1✔
831
            cell.alignment = Alignment(horizontal='center', vertical='center')
1✔
832

833
    # Set the width of the columns for the second sheet
834
    for col_letter in ['A', 'B', 'C', 'D', 'E']:
1✔
835
        sheet_one.column_dimensions[col_letter].width = 25
1✔
836

837
    # Create SheetTwo with Data
838
    sheet_two = wb.create_sheet(title='READ ME')
1✔
839

840
    data = [('Overview', 'This spreadsheet contains the total number of downloads and page views of your data on HDX, tracked monthly over the past 4 years.'),
1✔
841
            ('Data Source', 'The data has been sourced from the analytics platform Mixpanel [https://mixpanel.com/].'),
842
            ('Contents', 'The spreadsheet includes the following information: \n1. Page Views: Total page views by month. \n2. Downloads: Total number of downloads by month.'),
843
            ('Caveats', 'To ensure accurate data representation, we have excluded as much bot traffic as possible.'),
844
            ('Update Frequency', 'The spreadsheet is refreshed automatically on the first day of each month.'),
845
            ('Contact', 'For additional inquiries, please contact us at hdx@un.org')]
846

847
    # Add data to the worksheet
848
    for row_num, (header, text) in enumerate(data, start=1):
1✔
849
        sheet_two[f'A{row_num}'] = header
1✔
850
        sheet_two[f'A{row_num}'].font = bold_font  # Apply bold to the first column
1✔
851
        sheet_two[f'B{row_num}'] = text
1✔
852
        sheet_two[f'A{row_num}'].alignment = Alignment(horizontal='left', vertical='top')
1✔
853
        sheet_two[f'B{row_num}'].alignment = Alignment(horizontal='left', vertical='top', wrap_text=True)
1✔
854

855
    # Set the width of the columns
856
    sheet_two.column_dimensions['A'].width = 20
1✔
857
    sheet_two.column_dimensions['B'].width = 100
1✔
858

859
    # Iterate the loop to read the cell values
860
    with NamedTemporaryFile() as tmp:
1✔
861
        wb.save(tmp)
1✔
862
        tmp.seek(0)
1✔
863
        output = make_response(tmp.read())
1✔
864
        output.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1✔
865
        output.headers['Content-Disposition'] = f'attachment; filename="{org_dict.get("name")}_stats.xlsx"'
1✔
866

867
    return output
1✔
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

© 2025 Coveralls, Inc