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

bloomberg / pybossa / 21182355076

20 Jan 2026 06:10PM UTC coverage: 94.07% (-0.003%) from 94.073%
21182355076

Pull #1084

github

web-flow
Merge f5a22ee8d into 94413386c
Pull Request #1084: RDISCROWD-8411: Filter task data by fields under files

47 of 51 new or added lines in 2 files covered. (92.16%)

9 existing lines in 2 files now uncovered.

17911 of 19040 relevant lines covered (94.07%)

0.94 hits per line

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

94.5
/pybossa/api/task.py
1
# -*- coding: utf8 -*-
2
# This file is part of PYBOSSA.
3
#
4
# Copyright (C) 2015 Scifabric LTD.
5
#
6
# PYBOSSA is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU Affero General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# PYBOSSA is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU Affero General Public License for more details.
15
#
16
# You should have received a copy of the GNU Affero General Public License
17
# along with PYBOSSA.  If not, see <http://www.gnu.org/licenses/>.
18
"""
1✔
19
PYBOSSA api module for exposing domain object Task via an API.
20

21
This package adds GET, POST, PUT and DELETE methods for:
22
    * tasks
23

24
"""
25
from flask import abort, current_app
1✔
26
from flask_login import current_user
1✔
27
from werkzeug.exceptions import BadRequest, Conflict, NotFound
1✔
28
from pybossa.model.task import Task
1✔
29
from pybossa.model.project import Project
1✔
30
from pybossa.core import result_repo
1✔
31
from pybossa.util import sign_task
1✔
32
from .api_base import APIBase
1✔
33
from pybossa.api.pwd_manager import get_pwd_manager
1✔
34
from pybossa.util import get_user_id_or_ip, validate_required_fields
1✔
35
from pybossa.core import task_repo, project_repo
1✔
36
from pybossa.cache.projects import get_project_data
1✔
37
from pybossa.data_access import when_data_access
1✔
38
import hashlib
1✔
39
from flask import url_for
1✔
40
from pybossa.cloud_store_api.s3 import upload_json_data
1✔
41
from pybossa.auth.task import TaskAuth
1✔
42
from pybossa.cache import delete_memoized
1✔
43
from pybossa.cache.task_browse_helpers import get_searchable_columns
1✔
44
import json
1✔
45
import copy
1✔
46
from pybossa.task_creator_helper import get_task_expiration
1✔
47
from pybossa.model import make_timestamp
1✔
48
from pybossa.task_creator_helper import generate_checksum
1✔
49
from pybossa.cache.projects import get_project_data
1✔
50

51

52
class TaskAPI(APIBase):
1✔
53

54
    """Class for domain object Task."""
55

56
    __class__ = Task
1✔
57
    reserved_keys = set(['id', 'created', 'state', 'fav_user_ids',
1✔
58
        'calibration'])
59

60
    immutable_keys = set(['project_id'])
1✔
61

62
    def _forbidden_attributes(self, data):
1✔
63
        for key in data.keys():
1✔
64
            if key in self.reserved_keys:
1✔
65
                raise BadRequest("Reserved keys in payload")
1✔
66

67
    def _update_attribute(self, new, old):
1✔
68
        for key, value in old.info.items():
1✔
69
            new.info.setdefault(key, value)
1✔
70

71
        gold_task = bool(new.gold_answers)
1✔
72
        n_taskruns = len(new.task_runs)
1✔
73
        if new.state == 'completed':
1✔
74
            if gold_task or (old.n_answers < new.n_answers and
1✔
75
                n_taskruns < new.n_answers):
76
                new.state = 'ongoing'
1✔
77
        if new.state == 'ongoing':
1✔
78
            if not gold_task and (n_taskruns >= new.n_answers):
1✔
79
                new.state = 'completed'
1✔
80
        new.calibration = int(gold_task)
1✔
81
        if new.expiration is not None:
1✔
82
            new.expiration = get_task_expiration(new.expiration, old.created)
1✔
83

84
    def _preprocess_post_data(self, data):
1✔
85
        project_id = data["project_id"]
1✔
86
        project = project_repo.get(project_id)
1✔
87
        if not project:
1✔
88
            raise NotFound(f'Non existing project id {project_id}')
1✔
89

90
        info = data["info"]
1✔
91
        if isinstance(info, dict):
1✔
92
            hdfs_task = any([val.startswith("/fileproxy/hdfs/") for val in info.values() if isinstance(val, str)])
1✔
93
            if hdfs_task:
1✔
94
                raise BadRequest("Invalid task payload. HDFS is not supported")
1✔
95
        try:
1✔
96
            dup_checksum = generate_checksum(project_id=project_id, task=data)
1✔
97
        except Exception as e:
1✔
98
            current_app.logger.info("Project %d. Error generating duplicate task checksum %s", project_id, str(e))
1✔
99
            raise BadRequest(str(e))
1✔
100
        data["dup_checksum"] = dup_checksum
1✔
101
        completed_tasks = project.info.get("duplicate_task_check", {}).get("completed_tasks", False)
1✔
102
        duplicate_task = task_repo.find_duplicate(
1✔
103
            project_id=project_id,
104
            info=info,
105
            dup_checksum=dup_checksum,
106
            completed_tasks=completed_tasks
107
        )
108
        if duplicate_task:
1✔
109
            current_app.logger.info("Project %s, task checksum %s. Duplicate task found with task id %s. Ignoring task creation",
1✔
110
                                    str(project_id), str(dup_checksum), str(duplicate_task))
111
            message = {
1✔
112
                'reason': 'DUPLICATE_TASK',
113
                'task_id': duplicate_task
114
            }
115
            raise Conflict(json.dumps(message))
1✔
116

117

118
        if 'n_answers' not in data:
1✔
119
            data['n_answers'] = project.get_default_n_answers()
1✔
120
        user_pref = data.get('user_pref', {})
1✔
121
        if user_pref.get('languages'):
1✔
UNCOV
122
            user_pref['languages'] = [s.lower() for s in user_pref.get('languages', [])]
×
123
        if user_pref.get('locations'):
1✔
UNCOV
124
            user_pref['locations'] = [s.lower() for s in user_pref.get('locations', [])]
×
125
        if user_pref.get('assign_user'):
1✔
UNCOV
126
            user_pref['assign_user'] = [s.lower() for s in user_pref.get('assign_user', [])]
×
127
        invalid_fields = validate_required_fields(info)
1✔
128
        if invalid_fields:
1✔
129
            raise BadRequest('Missing or incorrect required fields: {}'
1✔
130
                            .format(','.join(invalid_fields)))
131
        if data.get('gold_answers'):
1✔
132
            try:
1✔
133
                gold_answers = data['gold_answers']
1✔
134
                if type(gold_answers) is dict:
1✔
135
                    data['calibration'] = 1
1✔
136
                    data['exported'] = True
1✔
UNCOV
137
            except Exception as e:
×
138
                raise BadRequest('Invalid gold_answers')
×
139
        create_time = data.get("created") or make_timestamp()
1✔
140
        data["expiration"] = get_task_expiration(data.get('expiration'), create_time)
1✔
141

142
    def _verify_auth(self, item):
1✔
143
        if not current_user.is_authenticated:
1✔
UNCOV
144
            return False
×
145
        if current_user.admin or current_user.subadmin:
1✔
146
            return True
1✔
147
        project = Project(**get_project_data(item.project_id))
1✔
148
        pwd_manager = get_pwd_manager(project)
1✔
149
        return not pwd_manager.password_needed(project, get_user_id_or_ip())
1✔
150

151
    def _sign_item(self, item):
1✔
152
        project_id = item['project_id']
1✔
153
        if current_user.admin or \
1✔
154
           current_user.id in get_project_data(project_id)['owners_ids']:
155
            sign_task(item)
1✔
156

157
    def _select_attributes(self, data):
1✔
158
        return TaskAuth.apply_access_control(data, user=current_user, project_data=get_project_data(data['project_id']))
1✔
159

160
    def put(self, oid):
1✔
161
        # reset cache / memoized
162
        delete_memoized(get_searchable_columns)
1✔
163
        return super(TaskAPI, self).put(oid)
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

© 2026 Coveralls, Inc