Skip to content

Commit 7266a09

Browse files
authored
Merge pull request #1621 from alexandermendes/fix-1592
Fix 1592
2 parents 149f542 + becb21f commit 7266a09

15 files changed

+210
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Add info to category
2+
3+
Revision ID: 4e795a38cd4b
4+
Revises: 0a6628a97161
5+
Create Date: 2017-08-15 12:21:47.441561
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = '4e795a38cd4b'
11+
down_revision = '0a6628a97161'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
from sqlalchemy.dialects.postgresql import JSON
16+
17+
def upgrade():
18+
op.add_column('category', sa.Column('info', JSON))
19+
20+
21+
def downgrade():
22+
op.drop_column('category', 'info')

doc/api.rst

+11-11
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ It is possible to limit the number of returned objects::
274274
GET http://{pybossa-site-url}/api/{domain-object}[?field1=value&limit=20]
275275

276276

277-
It is possible to access first level JSON keys within the **info** field of Projects,
278-
Tasks, Task Runs and Results::
277+
It is possible to access first level JSON keys within the **info** field of Categories,
278+
Projects, Tasks, Task Runs and Results::
279279

280280
GET http://{pybossa-site-url}/api/{domain-object}[?field1=value&info=foo::bar&limit=20]
281281

@@ -313,9 +313,9 @@ When you use the fulltextsearch argument, the API will return the objects enrich
313313
* **headline**: The matched words of the key1::value1 found, with <b></b> items to highlight them.
314314
* **rank**: The ranking returned by the database. Ranking attempts to measure how relevant documents are to a particular query, so that when there are many matches the most relevant ones can be shown first.
315315

316-
Here you have an example of the expected output for an api call like this::
316+
Here you have an example of the expected output for an api call like this::
317317

318-
/api/task?project_id=1&info=name::ipsum%26bravo&fulltextsearch=1
318+
/api/task?project_id=1&info=name::ipsum%26bravo&fulltextsearch=1
319319

320320
.. code-block:: python
321321
@@ -349,7 +349,7 @@ Here you have an example of the expected output for an api call like this::
349349
We use PostgreSQL ts_rank_cd with the following configuration: ts_rank_cd(textsearch, query, 4). For more details check the official documentation of PostgreSQL.
350350

351351
.. note::
352-
By default PYBOSSA uses English for the searches. You can customize this behavior using any of the supported languages by PostgreSQL changing the settings_local.py config variable: *FULLTEXTSEARCH_LANGUAGE* = 'spanish'.
352+
By default PYBOSSA uses English for the searches. You can customize this behavior using any of the supported languages by PostgreSQL changing the settings_local.py config variable: *FULLTEXTSEARCH_LANGUAGE* = 'spanish'.
353353

354354
.. note::
355355
By default all GET queries return a maximum of 20 objects unless the
@@ -459,14 +459,14 @@ Favorites
459459
---------
460460

461461
Authenticated users can mark a task as a favorite. This is useful for users when they
462-
want to see all the tasks they have done to remember them. For example, a user can mark
462+
want to see all the tasks they have done to remember them. For example, a user can mark
463463
as a favorite a picture that's beautiful and that he/she has marked as favorited.
464464

465465
For serving this purpose PYBOSSA provides the following api endpoint::
466466

467467
GET /api/favorites
468468

469-
If the user is authenticated it will return all the tasks the user has marked as favorited.
469+
If the user is authenticated it will return all the tasks the user has marked as favorited.
470470

471471
To add a task as a favorite, a POST should be done with a payload of {'task_id': Task.id}::
472472

@@ -493,7 +493,7 @@ You can also use **limit** to get more than 1 task for the user like this::
493493

494494
GET http://{pybossa-site-url}/api/{project.id}/newtask?limit=100
495495

496-
That query will return 100 tasks for the user.
496+
That query will return 100 tasks for the user.
497497

498498
.. note::
499499
That's the maximum of tasks that a user can get at once. If you pass an argument of 200,
@@ -610,7 +610,7 @@ User api endpoint
610610
----------------
611611

612612
While all the other endpoints behave the same, this one is a bit special as we deal with private information
613-
like emails.
613+
like emails.
614614

615615
Anonymous users
616616
~~~~~~~~~~~~~~~
@@ -3731,7 +3731,7 @@ argument: **?template=basic** for the basic or **?template=iamge** for the image
37313731
"title": "Project: asdf1324 &middot; Task Presenter Editor"
37323732
}
37333733
3734-
Then, you can use that template, or if you prefer you can do a POST directly without that information. As in
3734+
Then, you can use that template, or if you prefer you can do a POST directly without that information. As in
37353735
any other request involving a POST you will need the CSRFToken to validate it.
37363736
37373737
**POST**
@@ -4225,7 +4225,7 @@ Therefore, if you want to import tasks from a CSV link, you will have to do the
42254225
42264226
GET server/project/<short_name>/tasks/import?type=csv
42274227
4228-
That query will return the same output as before, but instead of the available_importers, you will get the the form fields and CSRF token for that importer.
4228+
That query will return the same output as before, but instead of the available_importers, you will get the the form fields and CSRF token for that importer.
42294229
42304230
**POST**
42314231

doc/conf.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272
# built documents.
7373
#
7474
# The short X.Y version.
75-
version = 'v2.6.0'
75+
version = 'v2.6.1'
7676
# The full version, including alpha/beta/rc tags.
77-
release = 'v2.6.0'
77+
release = 'v2.6.1'
7878

7979
# The language for content autogenerated by Sphinx. Refer to documentation
8080
# for a list of supported languages.

doc/customizing.rst

+13-12
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ Multiple languages
139139
==================
140140

141141
By default PYBOSSA only speaks English, however the default theme comes with a few
142-
translations (Spanish, French, Italian, Japanese, Greek and German).
142+
translations (Spanish, French, Italian, Japanese, Greek and German).
143143

144144
You can enable those translations (mostly user interface strings and actions) by doing
145145
the following: creating a symlink to the translations folders:
@@ -150,14 +150,14 @@ the following: creating a symlink to the translations folders:
150150
151151
This will use the default translations of PYBOSSA for your server. We recommend to use
152152
these translations with the default theme. If you use your own theme, the best thing is
153-
to do your own translation, (see :ref:`translating`), as you might want to name things
153+
to do your own translation, (see :ref:`translating`), as you might want to name things
154154
differently on the templates.
155155

156156
You can disable/enable different languages in your config file
157157
**settings_local.py**. For example, to remove French you can add
158158
this configuration to the settings file:
159159

160-
.. code-block:: python
160+
.. code-block:: python
161161
162162
LOCALES = [('en', 'English'), ('es', u'Español'),
163163
('it', 'Italiano'), ('ja', u'日本語')]
@@ -167,23 +167,23 @@ Also, you can always specify a different default locale using the following
167167
snippet in the same settings file
168168

169169

170-
.. code-block:: python
170+
.. code-block:: python
171171
172172
DEFAULT_LOCALE = 'es'
173173
174174
175175
.. note::
176176
PYBOSSA tries to first match the user preferred language from their
177177
browser. This will work for anonymous users, while registered ones can
178-
specify the language they want using their user preferences.
178+
specify the language they want using their user preferences.
179179

180180

181181
.. note::
182182
As an alternative way to allow anonymous users to *force* a different
183183
language, PYBOSSA looks for a cookie named **language** where it expects
184184
the key of any of the supported langes in the LOCALES list. You can use
185185
JavaScript to set it up.
186-
186+
187187

188188
Creating your own theme
189189
=======================
@@ -699,7 +699,7 @@ Making extra key/value pairs in info field public
699699
=================================================
700700

701701
By default PYBOSSA protects all the information the info field except for those
702-
values that are public like the url of the image of the project, the container
702+
values that are public like the url of the image of the project, the container
703703
where that picture is stored and a few extra. While this will be more than enough
704704
for most projects, sometimes, a server will need to expose more information publicly
705705
via the info field for the User and Project Domain Objects.
@@ -712,7 +712,7 @@ to show user's badges to anonymous people.
712712
With projects it could be the same. You want to highlight some info to anyone, but hide everything else.
713713

714714
As PYBOSSA hides everything by default, you can always turn on which other fields from the
715-
info field can be shown to anonymous users, making them public.
715+
info field can be shown to anonymous users, making them public.
716716

717717
.. note::
718718

@@ -725,6 +725,7 @@ the following config variables::
725725

726726
PROJECT_INFO_PUBLIC_FIELDS = ['key1', 'key2']
727727
USER_INFO_PUBLIC_FIELDS = ['badges', 'key2', ...]
728+
CATEGORY_INFO_PUBLIC_FIELDS = ['key1', 'key2']
728729

729730
Add as many as you want, need. But please, be careful about which information you disclose.
730731

@@ -903,7 +904,7 @@ If you want to enable it, you will have to add to your settings_local.py::
903904

904905
SSE = True
905906

906-
Also, you will need to configure uwsgi and nginx to support SSE events. This is not
907+
Also, you will need to configure uwsgi and nginx to support SSE events. This is not
907908
trivial, as there are several different scenarios, libraries and options, so instead of
908909
recommending one solution, we invite you to read the `uwsgi documentation about it <http://uwsgi-docs.readthedocs.org/en/latest/Async.html>`_, so you can take a decission based on your
909910
own infrastructure and preferences.
@@ -996,7 +997,7 @@ Web Push notifications
996997
onesignal.com in order to support this feature. If you cannot use HTTPS we recommend to not enable
997998
it.
998999

999-
PYBOSSA can send web push notifications to Google Chrome, Mozilla Firefox and Safari browsers.
1000+
PYBOSSA can send web push notifications to Google Chrome, Mozilla Firefox and Safari browsers.
10001001

10011002
For supporting this feature, PYBOSSA uses the Onesignal.com service. You will need an account and create
10021003
an app for your PYBOSSA server. Then follow their documentation to download the WebPush SDK and configure
@@ -1016,13 +1017,13 @@ settings_local.py file::
10161017
ONESIGNAL_APP_ID = 'app-id'
10171018
ONESIGNAL_API_KEY = 'app-key'
10181019

1019-
Restart the server, and add one background worker for the *webpush* queue. This queue will handle the
1020+
Restart the server, and add one background worker for the *webpush* queue. This queue will handle the
10201021
creation of the apps, as well as sending the push notifications.
10211022

10221023
Then you will need to update your PYBOSSA theme in order to allow your users to subscribe. As this could
10231024
vary a lot from one project to another, we do not provide a template but some guidelines:
10241025

1025-
* Use the JS SDK to subscribe a user to a given project using the *tags* option of Onesignal.
1026+
* Use the JS SDK to subscribe a user to a given project using the *tags* option of Onesignal.
10261027
* PYBOSSA sends notifications using those tags thanks to the *filters* option that allows us to
10271028
segment traffic. PYBOSSA is especting the project.id as the tag key for segmenting.
10281029
* The JS SDK allows you to subscribe/unsubscribe a user to a give project (not only the whole server) with

pybossa/api/category.py

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* categories
2323
2424
"""
25+
from werkzeug.exceptions import BadRequest
2526
from api_base import APIBase
2627
from pybossa.model.category import Category
2728

@@ -30,4 +31,12 @@ class CategoryAPI(APIBase):
3031

3132
"""Class API for domain object Category."""
3233

34+
reserved_keys = set(['id', 'created'])
35+
3336
__class__ = Category
37+
38+
def _forbidden_attributes(self, data):
39+
for key in data.keys():
40+
if key in self.reserved_keys:
41+
msg = "Reserved keys in payload: %s" % key
42+
raise BadRequest(msg)

pybossa/model/category.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
from sqlalchemy import Integer, Text
2020
from sqlalchemy.schema import Column, ForeignKey
21+
from sqlalchemy.dialects.postgresql import JSON
22+
from sqlalchemy.ext.mutable import MutableDict
23+
from flask import current_app
2124

2225
from pybossa.core import db
2326
from pybossa.model import DomainObject, make_timestamp
@@ -38,12 +41,20 @@ class Category(db.Model, DomainObject):
3841
description = Column(Text, nullable=False)
3942
#: UTC timestamp when the Category was created
4043
created = Column(Text, default=make_timestamp)
44+
#: Info field formatted as JSON for storing additional data
45+
info = Column(MutableDict.as_mutable(JSON), default=dict())
4146

4247
@classmethod
4348
def public_attributes(self):
4449
"""Return a list of public attributes."""
45-
return ['description', 'short_name', 'created', 'id', 'name']
50+
return ['description', 'short_name', 'created', 'id', 'name', 'info']
4651

52+
@classmethod
4753
def public_info_keys(self):
4854
"""Return a list of public info keys."""
49-
return []
55+
default = []
56+
extra = current_app.config.get('CATEGORY_INFO_PUBLIC_FIELDS')
57+
if extra:
58+
return list(set(default).union(set(extra)))
59+
else:
60+
return default

pybossa/repositories/helping_repository.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,35 @@ def get_by(self, **attributes):
3333

3434
def filter_by(self, limit=None, offset=0, yielded=False,
3535
last_id=None, fulltextsearch=None, desc=False, **filters):
36-
return self._filter_by(HelpingMaterial, limit, offset, yielded,
36+
return self._filter_by(HelpingMaterial, limit, offset, yielded,
3737
last_id, fulltextsearch, desc, **filters)
3838

39-
def save(self, blogpost):
40-
self._validate_can_be('saved', blogpost)
39+
def save(self, hm):
40+
self._validate_can_be('saved', hm)
4141
try:
42-
self.db.session.add(blogpost)
42+
self.db.session.add(hm)
4343
self.db.session.commit()
4444
except IntegrityError as e:
4545
self.db.session.rollback()
4646
raise DBIntegrityError(e)
4747

48-
def update(self, blogpost):
49-
self._validate_can_be('updated', blogpost)
48+
def update(self, hm):
49+
self._validate_can_be('updated', hm)
5050
try:
51-
self.db.session.merge(blogpost)
51+
self.db.session.merge(hm)
5252
self.db.session.commit()
5353
except IntegrityError as e:
5454
self.db.session.rollback()
5555
raise DBIntegrityError(e)
5656

57-
def delete(self, blogpost):
58-
self._validate_can_be('deleted', blogpost)
59-
blog = self.db.session.query(HelpingMaterial).filter(HelpingMaterial.id==blogpost.id).first()
57+
def delete(self, hm):
58+
self._validate_can_be('deleted', hm)
59+
blog = self.db.session.query(HelpingMaterial).filter(HelpingMaterial.id==hm.id).first()
6060
self.db.session.delete(blog)
6161
self.db.session.commit()
6262

63-
def _validate_can_be(self, action, blogpost):
64-
if not isinstance(blogpost, HelpingMaterial):
65-
name = blogpost.__class__.__name__
63+
def _validate_can_be(self, action, hm):
64+
if not isinstance(hm, HelpingMaterial):
65+
name = hm.__class__.__name__
6666
msg = '%s cannot be %s by %s' % (name, action, self.__class__.__name__)
6767
raise WrongObjectError(msg)

pybossa/repositories/project_repository.py

-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@
2929

3030
class ProjectRepository(Repository):
3131

32-
def __init__(self, db):
33-
self.db = db
34-
3532
# Methods for Project objects
3633
def get(self, id):
3734
return self.db.session.query(Project).get(id)

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565

6666
setup(
6767
name = 'pybossa',
68-
version = '2.6.0',
68+
version = '2.6.1',
6969
packages = find_packages(),
7070
install_requires = requirements,
7171
# only needed when installing directly from setup.py (PyPi, eggs?) and pointing to e.g. a git repo.

test/factories/category_factory.py

+1
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ def _create(cls, model_class, *args, **kwargs):
3434
name = factory.Sequence(lambda n: 'category_name_%d' % n)
3535
short_name = factory.Sequence(lambda n: 'category_short_name_%d' % n)
3636
description = 'Category description for testing purposes'
37+
info = {'file_name': 'test.jpg', 'container': 'user_1'}

test/test_admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,7 @@ def test_24_admin_update_category(self):
936936
obj = db.session.query(Category).get(1)
937937
_name = obj.name
938938
category = obj.dictize()
939+
del category['info']
939940

940941
# Anonymous user GET
941942
url = '/admin/categories/update/%s' % obj.id
@@ -1058,6 +1059,7 @@ def test_25_admin_delete_category(self):
10581059
self.create()
10591060
obj = db.session.query(Category).get(2)
10601061
category = obj.dictize()
1062+
del category['info']
10611063

10621064
# Anonymous user GET
10631065
url = '/admin/categories/del/%s' % obj.id
@@ -1104,6 +1106,7 @@ def test_25_admin_delete_category(self):
11041106
obj = db.session.query(Category).first()
11051107
url = '/admin/categories/del/%s' % obj.id
11061108
category = obj.dictize()
1109+
del category['info']
11071110
res = self.app.post(url, data=category, follow_redirects=True)
11081111
print res.data
11091112
err_msg = "Category should not be deleted"

0 commit comments

Comments
 (0)