udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30382__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- tasks/__init__.py +109 -107
- tasks/helpers.py +18 -18
- udata/__init__.py +4 -4
- udata/admin/views.py +5 -5
- udata/api/__init__.py +135 -124
- udata/api/commands.py +45 -37
- udata/api/errors.py +5 -4
- udata/api/fields.py +23 -21
- udata/api/oauth2.py +55 -74
- udata/api/parsers.py +15 -15
- udata/api/signals.py +1 -1
- udata/api_fields.py +137 -89
- udata/app.py +56 -54
- udata/assets.py +5 -5
- udata/auth/__init__.py +37 -26
- udata/auth/forms.py +23 -15
- udata/auth/helpers.py +1 -1
- udata/auth/mails.py +3 -3
- udata/auth/password_validation.py +19 -15
- udata/auth/views.py +94 -68
- udata/commands/__init__.py +71 -69
- udata/commands/cache.py +7 -7
- udata/commands/db.py +201 -140
- udata/commands/dcat.py +36 -30
- udata/commands/fixtures.py +100 -84
- udata/commands/images.py +21 -20
- udata/commands/info.py +17 -20
- udata/commands/init.py +10 -10
- udata/commands/purge.py +12 -13
- udata/commands/serve.py +41 -29
- udata/commands/static.py +16 -18
- udata/commands/test.py +20 -20
- udata/commands/tests/fixtures.py +26 -24
- udata/commands/worker.py +31 -33
- udata/core/__init__.py +12 -12
- udata/core/activity/__init__.py +0 -1
- udata/core/activity/api.py +59 -49
- udata/core/activity/models.py +28 -26
- udata/core/activity/signals.py +1 -1
- udata/core/activity/tasks.py +16 -10
- udata/core/badges/api.py +6 -6
- udata/core/badges/commands.py +14 -13
- udata/core/badges/fields.py +8 -5
- udata/core/badges/forms.py +7 -4
- udata/core/badges/models.py +16 -31
- udata/core/badges/permissions.py +1 -3
- udata/core/badges/signals.py +2 -2
- udata/core/badges/tasks.py +3 -2
- udata/core/badges/tests/test_commands.py +10 -10
- udata/core/badges/tests/test_model.py +24 -31
- udata/core/contact_point/api.py +19 -18
- udata/core/contact_point/api_fields.py +21 -14
- udata/core/contact_point/factories.py +2 -2
- udata/core/contact_point/forms.py +7 -6
- udata/core/contact_point/models.py +3 -5
- udata/core/dataservices/api.py +26 -21
- udata/core/dataservices/factories.py +13 -11
- udata/core/dataservices/models.py +35 -40
- udata/core/dataservices/permissions.py +4 -4
- udata/core/dataservices/rdf.py +40 -17
- udata/core/dataservices/tasks.py +4 -3
- udata/core/dataset/actions.py +10 -10
- udata/core/dataset/activities.py +21 -23
- udata/core/dataset/api.py +321 -298
- udata/core/dataset/api_fields.py +443 -271
- udata/core/dataset/apiv2.py +305 -229
- udata/core/dataset/commands.py +38 -36
- udata/core/dataset/constants.py +61 -54
- udata/core/dataset/csv.py +70 -74
- udata/core/dataset/events.py +39 -32
- udata/core/dataset/exceptions.py +8 -4
- udata/core/dataset/factories.py +57 -65
- udata/core/dataset/forms.py +87 -63
- udata/core/dataset/models.py +336 -280
- udata/core/dataset/permissions.py +9 -6
- udata/core/dataset/preview.py +15 -17
- udata/core/dataset/rdf.py +156 -122
- udata/core/dataset/search.py +92 -77
- udata/core/dataset/signals.py +1 -1
- udata/core/dataset/tasks.py +63 -54
- udata/core/discussions/actions.py +5 -5
- udata/core/discussions/api.py +124 -120
- udata/core/discussions/factories.py +2 -2
- udata/core/discussions/forms.py +9 -7
- udata/core/discussions/metrics.py +1 -3
- udata/core/discussions/models.py +25 -24
- udata/core/discussions/notifications.py +18 -14
- udata/core/discussions/permissions.py +3 -3
- udata/core/discussions/signals.py +4 -4
- udata/core/discussions/tasks.py +24 -28
- udata/core/followers/api.py +32 -33
- udata/core/followers/models.py +9 -9
- udata/core/followers/signals.py +3 -3
- udata/core/jobs/actions.py +7 -7
- udata/core/jobs/api.py +99 -92
- udata/core/jobs/commands.py +48 -49
- udata/core/jobs/forms.py +11 -11
- udata/core/jobs/models.py +6 -6
- udata/core/metrics/__init__.py +2 -2
- udata/core/metrics/commands.py +34 -30
- udata/core/metrics/models.py +2 -4
- udata/core/metrics/signals.py +1 -1
- udata/core/metrics/tasks.py +3 -3
- udata/core/organization/activities.py +12 -15
- udata/core/organization/api.py +167 -174
- udata/core/organization/api_fields.py +183 -124
- udata/core/organization/apiv2.py +32 -32
- udata/core/organization/commands.py +20 -22
- udata/core/organization/constants.py +11 -11
- udata/core/organization/csv.py +17 -15
- udata/core/organization/factories.py +8 -11
- udata/core/organization/forms.py +32 -26
- udata/core/organization/metrics.py +2 -1
- udata/core/organization/models.py +87 -67
- udata/core/organization/notifications.py +18 -14
- udata/core/organization/permissions.py +10 -11
- udata/core/organization/rdf.py +14 -14
- udata/core/organization/search.py +30 -28
- udata/core/organization/signals.py +7 -7
- udata/core/organization/tasks.py +42 -61
- udata/core/owned.py +38 -27
- udata/core/post/api.py +82 -81
- udata/core/post/constants.py +8 -5
- udata/core/post/factories.py +4 -4
- udata/core/post/forms.py +13 -14
- udata/core/post/models.py +20 -22
- udata/core/post/tests/test_api.py +30 -32
- udata/core/reports/api.py +8 -7
- udata/core/reports/constants.py +1 -3
- udata/core/reports/models.py +10 -10
- udata/core/reuse/activities.py +15 -19
- udata/core/reuse/api.py +123 -126
- udata/core/reuse/api_fields.py +120 -85
- udata/core/reuse/apiv2.py +11 -10
- udata/core/reuse/constants.py +23 -23
- udata/core/reuse/csv.py +18 -18
- udata/core/reuse/factories.py +5 -9
- udata/core/reuse/forms.py +24 -21
- udata/core/reuse/models.py +55 -51
- udata/core/reuse/permissions.py +2 -2
- udata/core/reuse/search.py +49 -46
- udata/core/reuse/signals.py +1 -1
- udata/core/reuse/tasks.py +4 -5
- udata/core/site/api.py +47 -50
- udata/core/site/factories.py +2 -2
- udata/core/site/forms.py +4 -5
- udata/core/site/models.py +94 -63
- udata/core/site/rdf.py +14 -14
- udata/core/spam/api.py +16 -9
- udata/core/spam/constants.py +4 -4
- udata/core/spam/fields.py +13 -7
- udata/core/spam/models.py +27 -20
- udata/core/spam/signals.py +1 -1
- udata/core/spam/tests/test_spam.py +6 -5
- udata/core/spatial/api.py +72 -80
- udata/core/spatial/api_fields.py +73 -58
- udata/core/spatial/commands.py +67 -64
- udata/core/spatial/constants.py +3 -3
- udata/core/spatial/factories.py +37 -54
- udata/core/spatial/forms.py +27 -26
- udata/core/spatial/geoids.py +17 -17
- udata/core/spatial/models.py +43 -47
- udata/core/spatial/tasks.py +2 -1
- udata/core/spatial/tests/test_api.py +115 -130
- udata/core/spatial/tests/test_fields.py +74 -77
- udata/core/spatial/tests/test_geoid.py +22 -22
- udata/core/spatial/tests/test_models.py +5 -7
- udata/core/spatial/translations.py +16 -16
- udata/core/storages/__init__.py +16 -18
- udata/core/storages/api.py +66 -64
- udata/core/storages/tasks.py +7 -7
- udata/core/storages/utils.py +15 -15
- udata/core/storages/views.py +5 -6
- udata/core/tags/api.py +17 -14
- udata/core/tags/csv.py +4 -4
- udata/core/tags/models.py +8 -5
- udata/core/tags/tasks.py +11 -13
- udata/core/tags/views.py +4 -4
- udata/core/topic/api.py +84 -73
- udata/core/topic/apiv2.py +157 -127
- udata/core/topic/factories.py +3 -4
- udata/core/topic/forms.py +12 -14
- udata/core/topic/models.py +14 -19
- udata/core/topic/parsers.py +26 -26
- udata/core/user/activities.py +30 -29
- udata/core/user/api.py +151 -152
- udata/core/user/api_fields.py +132 -100
- udata/core/user/apiv2.py +7 -7
- udata/core/user/commands.py +38 -38
- udata/core/user/factories.py +8 -9
- udata/core/user/forms.py +14 -11
- udata/core/user/metrics.py +2 -2
- udata/core/user/models.py +68 -69
- udata/core/user/permissions.py +4 -5
- udata/core/user/rdf.py +7 -8
- udata/core/user/tasks.py +2 -2
- udata/core/user/tests/test_user_model.py +24 -16
- udata/db/tasks.py +2 -1
- udata/entrypoints.py +35 -31
- udata/errors.py +2 -1
- udata/event/values.py +6 -6
- udata/factories.py +2 -2
- udata/features/identicon/api.py +5 -6
- udata/features/identicon/backends.py +48 -55
- udata/features/identicon/tests/test_backends.py +4 -5
- udata/features/notifications/__init__.py +0 -1
- udata/features/notifications/actions.py +9 -9
- udata/features/notifications/api.py +17 -13
- udata/features/territories/__init__.py +12 -10
- udata/features/territories/api.py +14 -15
- udata/features/territories/models.py +23 -28
- udata/features/transfer/actions.py +8 -11
- udata/features/transfer/api.py +84 -77
- udata/features/transfer/factories.py +2 -1
- udata/features/transfer/models.py +11 -12
- udata/features/transfer/notifications.py +19 -15
- udata/features/transfer/permissions.py +5 -5
- udata/forms/__init__.py +5 -2
- udata/forms/fields.py +164 -172
- udata/forms/validators.py +19 -22
- udata/forms/widgets.py +9 -13
- udata/frontend/__init__.py +31 -26
- udata/frontend/csv.py +68 -58
- udata/frontend/markdown.py +40 -44
- udata/harvest/actions.py +89 -77
- udata/harvest/api.py +294 -238
- udata/harvest/backends/__init__.py +4 -4
- udata/harvest/backends/base.py +128 -111
- udata/harvest/backends/dcat.py +80 -66
- udata/harvest/commands.py +56 -60
- udata/harvest/csv.py +8 -8
- udata/harvest/exceptions.py +6 -3
- udata/harvest/filters.py +24 -23
- udata/harvest/forms.py +27 -28
- udata/harvest/models.py +88 -80
- udata/harvest/notifications.py +15 -10
- udata/harvest/signals.py +13 -13
- udata/harvest/tasks.py +11 -10
- udata/harvest/tests/factories.py +23 -24
- udata/harvest/tests/test_actions.py +136 -166
- udata/harvest/tests/test_api.py +220 -214
- udata/harvest/tests/test_base_backend.py +117 -112
- udata/harvest/tests/test_dcat_backend.py +380 -308
- udata/harvest/tests/test_filters.py +33 -22
- udata/harvest/tests/test_models.py +11 -14
- udata/harvest/tests/test_notifications.py +6 -7
- udata/harvest/tests/test_tasks.py +7 -6
- udata/i18n.py +237 -78
- udata/linkchecker/backends.py +5 -11
- udata/linkchecker/checker.py +23 -22
- udata/linkchecker/commands.py +4 -6
- udata/linkchecker/models.py +6 -6
- udata/linkchecker/tasks.py +18 -20
- udata/mail.py +21 -21
- udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
- udata/migrations/2020-08-24-add-fs-filename.py +9 -8
- udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
- udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
- udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
- udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
- udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
- udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
- udata/migrations/2021-08-17-follow-integrity.py +5 -4
- udata/migrations/2021-08-17-harvest-integrity.py +13 -12
- udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
- udata/migrations/2021-08-17-transfer-integrity.py +5 -4
- udata/migrations/2021-08-17-users-integrity.py +9 -8
- udata/migrations/2021-12-14-reuse-topics.py +7 -6
- udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
- udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
- udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
- udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
- udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
- udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
- udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
- udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
- udata/migrations/__init__.py +123 -105
- udata/models/__init__.py +4 -4
- udata/mongo/__init__.py +13 -11
- udata/mongo/badges_field.py +3 -2
- udata/mongo/datetime_fields.py +13 -12
- udata/mongo/document.py +17 -16
- udata/mongo/engine.py +15 -16
- udata/mongo/errors.py +2 -1
- udata/mongo/extras_fields.py +30 -20
- udata/mongo/queryset.py +12 -12
- udata/mongo/slug_fields.py +38 -28
- udata/mongo/taglist_field.py +1 -2
- udata/mongo/url_field.py +5 -5
- udata/mongo/uuid_fields.py +4 -3
- udata/notifications/__init__.py +1 -1
- udata/notifications/mattermost.py +10 -9
- udata/rdf.py +167 -188
- udata/routing.py +40 -45
- udata/search/__init__.py +18 -19
- udata/search/adapter.py +17 -16
- udata/search/commands.py +44 -51
- udata/search/fields.py +13 -20
- udata/search/query.py +23 -18
- udata/search/result.py +9 -10
- udata/sentry.py +21 -19
- udata/settings.py +262 -198
- udata/sitemap.py +8 -6
- udata/static/chunks/{11.e9b9ca1f3e03d4020377.js → 11.52e531c19f8de80c00cf.js} +3 -3
- udata/static/chunks/{11.e9b9ca1f3e03d4020377.js.map → 11.52e531c19f8de80c00cf.js.map} +1 -1
- udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js → 13.c3343a7f1070061c0e10.js} +2 -2
- udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js.map → 13.c3343a7f1070061c0e10.js.map} +1 -1
- udata/static/chunks/{16.0baa2b64a74a2dcde25c.js → 16.8fa42440ad75ca172e6d.js} +2 -2
- udata/static/chunks/{16.0baa2b64a74a2dcde25c.js.map → 16.8fa42440ad75ca172e6d.js.map} +1 -1
- udata/static/chunks/{19.350a9f150b074b4ecefa.js → 19.9c6c8412729cd6d59cfa.js} +3 -3
- udata/static/chunks/{19.350a9f150b074b4ecefa.js.map → 19.9c6c8412729cd6d59cfa.js.map} +1 -1
- udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js → 5.71d15c2e4f21feee2a9a.js} +3 -3
- udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js.map → 5.71d15c2e4f21feee2a9a.js.map} +1 -1
- udata/static/chunks/{6.d8a5f7b017bcbd083641.js → 6.9139dc098b8ea640b890.js} +3 -3
- udata/static/chunks/{6.d8a5f7b017bcbd083641.js.map → 6.9139dc098b8ea640b890.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/storage/s3.py +20 -13
- udata/tags.py +4 -5
- udata/tasks.py +43 -42
- udata/tests/__init__.py +9 -6
- udata/tests/api/__init__.py +5 -6
- udata/tests/api/test_auth_api.py +395 -321
- udata/tests/api/test_base_api.py +31 -33
- udata/tests/api/test_contact_points.py +7 -9
- udata/tests/api/test_dataservices_api.py +211 -158
- udata/tests/api/test_datasets_api.py +823 -812
- udata/tests/api/test_follow_api.py +13 -15
- udata/tests/api/test_me_api.py +95 -112
- udata/tests/api/test_organizations_api.py +301 -339
- udata/tests/api/test_reports_api.py +35 -25
- udata/tests/api/test_reuses_api.py +134 -139
- udata/tests/api/test_swagger.py +5 -5
- udata/tests/api/test_tags_api.py +18 -25
- udata/tests/api/test_topics_api.py +94 -94
- udata/tests/api/test_transfer_api.py +53 -48
- udata/tests/api/test_user_api.py +128 -141
- udata/tests/apiv2/test_datasets.py +290 -198
- udata/tests/apiv2/test_me_api.py +10 -11
- udata/tests/apiv2/test_organizations.py +56 -74
- udata/tests/apiv2/test_swagger.py +5 -5
- udata/tests/apiv2/test_topics.py +69 -87
- udata/tests/cli/test_cli_base.py +8 -8
- udata/tests/cli/test_db_cli.py +21 -19
- udata/tests/dataservice/test_dataservice_tasks.py +8 -12
- udata/tests/dataset/test_csv_adapter.py +44 -35
- udata/tests/dataset/test_dataset_actions.py +2 -3
- udata/tests/dataset/test_dataset_commands.py +7 -8
- udata/tests/dataset/test_dataset_events.py +36 -29
- udata/tests/dataset/test_dataset_model.py +224 -217
- udata/tests/dataset/test_dataset_rdf.py +142 -131
- udata/tests/dataset/test_dataset_tasks.py +15 -15
- udata/tests/dataset/test_resource_preview.py +10 -13
- udata/tests/features/territories/__init__.py +9 -13
- udata/tests/features/territories/test_territories_api.py +71 -91
- udata/tests/forms/test_basic_fields.py +7 -7
- udata/tests/forms/test_current_user_field.py +39 -66
- udata/tests/forms/test_daterange_field.py +31 -39
- udata/tests/forms/test_dict_field.py +28 -26
- udata/tests/forms/test_extras_fields.py +102 -76
- udata/tests/forms/test_form_field.py +8 -8
- udata/tests/forms/test_image_field.py +33 -26
- udata/tests/forms/test_model_field.py +134 -123
- udata/tests/forms/test_model_list_field.py +7 -7
- udata/tests/forms/test_nested_model_list_field.py +117 -79
- udata/tests/forms/test_publish_as_field.py +36 -65
- udata/tests/forms/test_reference_field.py +34 -53
- udata/tests/forms/test_user_forms.py +23 -21
- udata/tests/forms/test_uuid_field.py +6 -10
- udata/tests/frontend/__init__.py +9 -6
- udata/tests/frontend/test_auth.py +7 -6
- udata/tests/frontend/test_csv.py +81 -96
- udata/tests/frontend/test_hooks.py +43 -43
- udata/tests/frontend/test_markdown.py +211 -191
- udata/tests/helpers.py +32 -37
- udata/tests/models.py +2 -2
- udata/tests/organization/test_csv_adapter.py +21 -16
- udata/tests/organization/test_notifications.py +11 -18
- udata/tests/organization/test_organization_model.py +13 -13
- udata/tests/organization/test_organization_rdf.py +29 -22
- udata/tests/organization/test_organization_tasks.py +16 -17
- udata/tests/plugin.py +76 -73
- udata/tests/reuse/test_reuse_model.py +21 -21
- udata/tests/reuse/test_reuse_task.py +11 -13
- udata/tests/search/__init__.py +11 -12
- udata/tests/search/test_adapter.py +60 -70
- udata/tests/search/test_query.py +16 -16
- udata/tests/search/test_results.py +10 -7
- udata/tests/site/test_site_api.py +11 -16
- udata/tests/site/test_site_metrics.py +20 -30
- udata/tests/site/test_site_model.py +4 -5
- udata/tests/site/test_site_rdf.py +94 -78
- udata/tests/test_activity.py +17 -17
- udata/tests/test_discussions.py +292 -299
- udata/tests/test_i18n.py +37 -40
- udata/tests/test_linkchecker.py +91 -85
- udata/tests/test_mail.py +13 -17
- udata/tests/test_migrations.py +219 -180
- udata/tests/test_model.py +164 -157
- udata/tests/test_notifications.py +17 -17
- udata/tests/test_owned.py +14 -14
- udata/tests/test_rdf.py +25 -23
- udata/tests/test_routing.py +89 -93
- udata/tests/test_storages.py +137 -128
- udata/tests/test_tags.py +44 -46
- udata/tests/test_topics.py +7 -7
- udata/tests/test_transfer.py +42 -49
- udata/tests/test_uris.py +160 -161
- udata/tests/test_utils.py +79 -71
- udata/tests/user/test_user_rdf.py +5 -9
- udata/tests/workers/test_jobs_commands.py +57 -58
- udata/tests/workers/test_tasks_routing.py +23 -29
- udata/tests/workers/test_workers_api.py +125 -131
- udata/tests/workers/test_workers_helpers.py +6 -6
- udata/tracking.py +4 -6
- udata/uris.py +45 -46
- udata/utils.py +68 -66
- udata/wsgi.py +1 -1
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/METADATA +3 -2
- udata-9.1.2.dev30382.dist-info/RECORD +704 -0
- udata-9.1.2.dev30355.dist-info/RECORD +0 -704
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/LICENSE +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/WHEEL +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/entry_points.txt +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/top_level.txt +0 -0
udata/core/discussions/api.py
CHANGED
|
@@ -1,127 +1,130 @@
|
|
|
1
|
-
from bson import ObjectId
|
|
2
1
|
from datetime import datetime
|
|
3
2
|
|
|
4
|
-
from
|
|
3
|
+
from bson import ObjectId
|
|
5
4
|
from flask_restx.inputs import boolean
|
|
5
|
+
from flask_security import current_user
|
|
6
6
|
|
|
7
|
+
from udata.api import API, api, fields
|
|
7
8
|
from udata.auth import admin_permission
|
|
8
|
-
from udata.api import api, API, fields
|
|
9
9
|
from udata.core.spam.api import SpamAPIMixin
|
|
10
10
|
from udata.core.spam.fields import spam_fields
|
|
11
|
-
from udata.utils import id_or_404
|
|
12
11
|
from udata.core.user.api_fields import user_ref_fields
|
|
12
|
+
from udata.utils import id_or_404
|
|
13
13
|
|
|
14
|
-
from .forms import
|
|
15
|
-
from .models import
|
|
14
|
+
from .forms import DiscussionCommentForm, DiscussionCreateForm
|
|
15
|
+
from .models import Discussion, Message
|
|
16
16
|
from .permissions import CloseDiscussionPermission
|
|
17
17
|
from .signals import on_discussion_deleted
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
user_ref_fields, description=
|
|
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
|
-
|
|
19
|
+
ns = api.namespace("discussions", "Discussion related operations")
|
|
20
|
+
|
|
21
|
+
message_fields = api.model(
|
|
22
|
+
"DiscussionMessage",
|
|
23
|
+
{
|
|
24
|
+
"content": fields.String(description="The message body"),
|
|
25
|
+
"posted_by": fields.Nested(user_ref_fields, description="The message author"),
|
|
26
|
+
"posted_on": fields.ISODateTime(description="The message posting date"),
|
|
27
|
+
"spam": fields.Nested(spam_fields),
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
discussion_fields = api.model(
|
|
32
|
+
"Discussion",
|
|
33
|
+
{
|
|
34
|
+
"id": fields.String(description="The discussion identifier"),
|
|
35
|
+
"subject": fields.Nested(api.model_reference, description="The discussion target object"),
|
|
36
|
+
"class": fields.ClassName(description="The object class", discriminator=True),
|
|
37
|
+
"title": fields.String(description="The discussion title"),
|
|
38
|
+
"user": fields.Nested(user_ref_fields, description="The discussion author"),
|
|
39
|
+
"created": fields.ISODateTime(description="The discussion creation date"),
|
|
40
|
+
"closed": fields.ISODateTime(description="The discussion closing date"),
|
|
41
|
+
"closed_by": fields.Nested(
|
|
42
|
+
user_ref_fields, allow_null=True, description="The user who closed the discussion"
|
|
43
|
+
),
|
|
44
|
+
"discussion": fields.Nested(message_fields),
|
|
45
|
+
"url": fields.UrlFor("api.discussion", description="The discussion API URI"),
|
|
46
|
+
"extras": fields.Raw(description="Extra attributes as key-value pairs"),
|
|
47
|
+
"spam": fields.Nested(spam_fields),
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
start_discussion_fields = api.model(
|
|
52
|
+
"DiscussionStart",
|
|
53
|
+
{
|
|
54
|
+
"title": fields.String(description="The title of the discussion to open", required=True),
|
|
55
|
+
"comment": fields.String(description="The content of the initial comment", required=True),
|
|
56
|
+
"subject": fields.Nested(
|
|
57
|
+
api.model_reference, description="The discussion target object", required=True
|
|
58
|
+
),
|
|
59
|
+
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
comment_discussion_fields = api.model(
|
|
64
|
+
"DiscussionResponse",
|
|
65
|
+
{
|
|
66
|
+
"comment": fields.String(description="The comment to submit", required=True),
|
|
67
|
+
"close": fields.Boolean(
|
|
68
|
+
description="Is this a closing response. Only subject owner can close"
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
discussion_page_fields = api.model("DiscussionPage", fields.pager(discussion_fields))
|
|
70
74
|
|
|
71
75
|
parser = api.parser()
|
|
72
76
|
parser.add_argument(
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
"sort", type=str, default="-created", location="args", help="The sorting attribute"
|
|
78
|
+
)
|
|
75
79
|
parser.add_argument(
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
"closed",
|
|
81
|
+
type=boolean,
|
|
82
|
+
location="args",
|
|
83
|
+
help="Filters discussions on their closed status if specified",
|
|
84
|
+
)
|
|
78
85
|
parser.add_argument(
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
"for", type=str, location="args", action="append", help="Filter discussions for a given subject"
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument("user", type=str, location="args", help="Filter discussions created by a user")
|
|
89
|
+
parser.add_argument("page", type=int, default=1, location="args", help="The page to fetch")
|
|
81
90
|
parser.add_argument(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
parser.add_argument(
|
|
85
|
-
'page', type=int, default=1, location='args', help='The page to fetch')
|
|
86
|
-
parser.add_argument(
|
|
87
|
-
'page_size', type=int, default=20, location='args',
|
|
88
|
-
help='The page size to fetch')
|
|
91
|
+
"page_size", type=int, default=20, location="args", help="The page size to fetch"
|
|
92
|
+
)
|
|
89
93
|
|
|
90
94
|
|
|
91
|
-
@ns.route(
|
|
92
|
-
@ns.doc(delete={
|
|
95
|
+
@ns.route("/<id>/spam/", endpoint="discussion_spam")
|
|
96
|
+
@ns.doc(delete={"id": "unspam"})
|
|
93
97
|
class DiscussionSpamAPI(SpamAPIMixin):
|
|
94
98
|
model = Discussion
|
|
95
99
|
|
|
96
100
|
|
|
97
|
-
@ns.route(
|
|
101
|
+
@ns.route("/<id>/", endpoint="discussion")
|
|
98
102
|
class DiscussionAPI(API):
|
|
99
|
-
|
|
103
|
+
"""
|
|
100
104
|
Base class for a discussion thread.
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
@api.doc("get_discussion")
|
|
103
108
|
@api.marshal_with(discussion_fields)
|
|
104
109
|
def get(self, id):
|
|
105
|
-
|
|
110
|
+
"""Get a discussion given its ID"""
|
|
106
111
|
discussion = Discussion.objects.get_or_404(id=id_or_404(id))
|
|
107
112
|
return discussion
|
|
108
113
|
|
|
109
114
|
@api.secure
|
|
110
|
-
@api.doc(
|
|
115
|
+
@api.doc("comment_discussion")
|
|
111
116
|
@api.expect(comment_discussion_fields)
|
|
112
|
-
@api.response(
|
|
113
|
-
|
|
117
|
+
@api.response(
|
|
118
|
+
403, "Not allowed to close this discussion " "OR can't add comments on a closed discussion"
|
|
119
|
+
)
|
|
114
120
|
@api.marshal_with(discussion_fields)
|
|
115
121
|
def post(self, id):
|
|
116
|
-
|
|
122
|
+
"""Add comment and optionally close a discussion given its ID"""
|
|
117
123
|
discussion = Discussion.objects.get_or_404(id=id_or_404(id))
|
|
118
124
|
if discussion.closed:
|
|
119
125
|
api.abort(403, "Can't add comments on a closed discussion")
|
|
120
126
|
form = api.validate(DiscussionCommentForm)
|
|
121
|
-
message = Message(
|
|
122
|
-
content=form.comment.data,
|
|
123
|
-
posted_by=current_user.id
|
|
124
|
-
)
|
|
127
|
+
message = Message(content=form.comment.data, posted_by=current_user.id)
|
|
125
128
|
discussion.discussion.append(message)
|
|
126
129
|
message_idx = len(discussion.discussion) - 1
|
|
127
130
|
close = form.close.data
|
|
@@ -137,81 +140,82 @@ class DiscussionAPI(API):
|
|
|
137
140
|
return discussion
|
|
138
141
|
|
|
139
142
|
@api.secure(admin_permission)
|
|
140
|
-
@api.doc(
|
|
141
|
-
@api.response(403,
|
|
143
|
+
@api.doc("delete_discussion")
|
|
144
|
+
@api.response(403, "Not allowed to delete this discussion")
|
|
142
145
|
def delete(self, id):
|
|
143
|
-
|
|
146
|
+
"""Delete a discussion given its ID"""
|
|
144
147
|
discussion = Discussion.objects.get_or_404(id=id_or_404(id))
|
|
145
148
|
discussion.delete()
|
|
146
149
|
on_discussion_deleted.send(discussion)
|
|
147
|
-
return
|
|
150
|
+
return "", 204
|
|
148
151
|
|
|
149
152
|
|
|
150
|
-
@ns.route(
|
|
151
|
-
@ns.doc(delete={
|
|
153
|
+
@ns.route("/<id>/comments/<int:cidx>/spam", endpoint="discussion_comment_spam")
|
|
154
|
+
@ns.doc(delete={"id": "unspam"})
|
|
152
155
|
class DiscussionSpamAPI(SpamAPIMixin):
|
|
153
156
|
def get_model(self, id, cidx):
|
|
154
157
|
discussion = Discussion.objects.get_or_404(id=id_or_404(id))
|
|
155
158
|
if len(discussion.discussion) <= cidx:
|
|
156
|
-
api.abort(404,
|
|
159
|
+
api.abort(404, "Comment does not exist")
|
|
157
160
|
elif cidx == 0:
|
|
158
|
-
api.abort(400,
|
|
161
|
+
api.abort(400, "You cannot unspam the first comment of a discussion")
|
|
159
162
|
return discussion, discussion.discussion[cidx]
|
|
160
163
|
|
|
161
|
-
|
|
164
|
+
|
|
165
|
+
@ns.route("/<id>/comments/<int:cidx>", endpoint="discussion_comment")
|
|
162
166
|
class DiscussionCommentAPI(API):
|
|
163
|
-
|
|
167
|
+
"""
|
|
164
168
|
Base class for a comment in a discussion thread.
|
|
165
|
-
|
|
169
|
+
"""
|
|
170
|
+
|
|
166
171
|
@api.secure(admin_permission)
|
|
167
|
-
@api.doc(
|
|
168
|
-
@api.response(403,
|
|
172
|
+
@api.doc("delete_discussion_comment")
|
|
173
|
+
@api.response(403, "Not allowed to delete this comment")
|
|
169
174
|
def delete(self, id, cidx):
|
|
170
|
-
|
|
175
|
+
"""Delete a comment given its index"""
|
|
171
176
|
discussion = Discussion.objects.get_or_404(id=id_or_404(id))
|
|
172
177
|
if len(discussion.discussion) <= cidx:
|
|
173
|
-
api.abort(404,
|
|
178
|
+
api.abort(404, "Comment does not exist")
|
|
174
179
|
elif cidx == 0:
|
|
175
|
-
api.abort(400,
|
|
180
|
+
api.abort(400, "You cannot delete the first comment of a discussion")
|
|
176
181
|
discussion.discussion.pop(cidx)
|
|
177
182
|
discussion.save()
|
|
178
|
-
return
|
|
183
|
+
return "", 204
|
|
179
184
|
|
|
180
185
|
|
|
181
|
-
@ns.route(
|
|
186
|
+
@ns.route("/", endpoint="discussions")
|
|
182
187
|
class DiscussionsAPI(API):
|
|
183
|
-
|
|
188
|
+
"""
|
|
184
189
|
Base class for a list of discussions.
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
@api.doc("list_discussions")
|
|
187
193
|
@api.expect(parser)
|
|
188
194
|
@api.marshal_with(discussion_page_fields)
|
|
189
195
|
def get(self):
|
|
190
|
-
|
|
196
|
+
"""List all Discussions"""
|
|
191
197
|
args = parser.parse_args()
|
|
192
198
|
discussions = Discussion.objects
|
|
193
|
-
if args[
|
|
194
|
-
discussions = discussions.generic_in(subject=args[
|
|
195
|
-
if args[
|
|
196
|
-
discussions = discussions(discussion__posted_by=ObjectId(args[
|
|
197
|
-
if args[
|
|
199
|
+
if args["for"]:
|
|
200
|
+
discussions = discussions.generic_in(subject=args["for"])
|
|
201
|
+
if args["user"]:
|
|
202
|
+
discussions = discussions(discussion__posted_by=ObjectId(args["user"]))
|
|
203
|
+
if args["closed"] is False:
|
|
198
204
|
discussions = discussions(closed=None)
|
|
199
|
-
elif args[
|
|
205
|
+
elif args["closed"] is True:
|
|
200
206
|
discussions = discussions(closed__ne=None)
|
|
201
|
-
discussions = discussions.order_by(args[
|
|
202
|
-
return discussions.paginate(args[
|
|
207
|
+
discussions = discussions.order_by(args["sort"])
|
|
208
|
+
return discussions.paginate(args["page"], args["page_size"])
|
|
203
209
|
|
|
204
210
|
@api.secure
|
|
205
|
-
@api.doc(
|
|
211
|
+
@api.doc("create_discussion")
|
|
206
212
|
@api.expect(start_discussion_fields)
|
|
207
213
|
@api.marshal_with(discussion_fields)
|
|
208
214
|
def post(self):
|
|
209
|
-
|
|
215
|
+
"""Create a new Discussion"""
|
|
210
216
|
form = api.validate(DiscussionCreateForm)
|
|
211
217
|
|
|
212
|
-
message = Message(
|
|
213
|
-
content=form.comment.data,
|
|
214
|
-
posted_by=current_user.id)
|
|
218
|
+
message = Message(content=form.comment.data, posted_by=current_user.id)
|
|
215
219
|
discussion = Discussion(user=current_user.id, discussion=[message])
|
|
216
220
|
form.populate_obj(discussion)
|
|
217
221
|
discussion.save()
|
|
@@ -9,11 +9,11 @@ class DiscussionFactory(ModelFactory):
|
|
|
9
9
|
class Meta:
|
|
10
10
|
model = Discussion
|
|
11
11
|
|
|
12
|
-
title = factory.Faker(
|
|
12
|
+
title = factory.Faker("sentence")
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MessageDiscussionFactory(ModelFactory):
|
|
16
16
|
class Meta:
|
|
17
17
|
model = Message
|
|
18
18
|
|
|
19
|
-
content = factory.Faker(
|
|
19
|
+
content = factory.Faker("sentence")
|
udata/core/discussions/forms.py
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
from udata.forms import
|
|
1
|
+
from udata.forms import Form, ModelForm, fields, validators
|
|
2
2
|
from udata.i18n import lazy_gettext as _
|
|
3
3
|
|
|
4
|
-
from .models import Discussion
|
|
5
4
|
from .constants import COMMENT_SIZE_LIMIT
|
|
5
|
+
from .models import Discussion
|
|
6
6
|
|
|
7
|
-
__all__ = (
|
|
7
|
+
__all__ = ("DiscussionCreateForm", "DiscussionCommentForm")
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DiscussionCreateForm(ModelForm):
|
|
11
11
|
model_class = Discussion
|
|
12
12
|
|
|
13
|
-
title = fields.StringField(_(
|
|
13
|
+
title = fields.StringField(_("Title"), [validators.DataRequired()])
|
|
14
14
|
comment = fields.StringField(
|
|
15
|
-
_(
|
|
16
|
-
|
|
15
|
+
_("Comment"), [validators.DataRequired(), validators.Length(max=COMMENT_SIZE_LIMIT)]
|
|
16
|
+
)
|
|
17
|
+
subject = fields.ModelField(_("Subject"), [validators.DataRequired()])
|
|
17
18
|
extras = fields.ExtrasField()
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class DiscussionCommentForm(Form):
|
|
21
22
|
comment = fields.StringField(
|
|
22
|
-
_(
|
|
23
|
+
_("Comment"), [validators.DataRequired(), validators.Length(max=COMMENT_SIZE_LIMIT)]
|
|
24
|
+
)
|
|
23
25
|
close = fields.BooleanField(default=False)
|
udata/core/discussions/models.py
CHANGED
|
@@ -3,9 +3,10 @@ from datetime import datetime
|
|
|
3
3
|
|
|
4
4
|
from flask_login import current_user
|
|
5
5
|
|
|
6
|
-
from udata.mongo import db
|
|
7
6
|
from udata.core.spam.models import SpamMixin, spam_protected
|
|
8
|
-
from .
|
|
7
|
+
from udata.mongo import db
|
|
8
|
+
|
|
9
|
+
from .signals import on_discussion_closed, on_new_discussion, on_new_discussion_comment
|
|
9
10
|
|
|
10
11
|
log = logging.getLogger(__name__)
|
|
11
12
|
|
|
@@ -13,23 +14,29 @@ log = logging.getLogger(__name__)
|
|
|
13
14
|
class Message(SpamMixin, db.EmbeddedDocument):
|
|
14
15
|
content = db.StringField(required=True)
|
|
15
16
|
posted_on = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
16
|
-
posted_by = db.ReferenceField(
|
|
17
|
+
posted_by = db.ReferenceField("User")
|
|
17
18
|
|
|
18
19
|
def texts_to_check_for_spam(self):
|
|
19
20
|
return [self.content]
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
def spam_report_message(self, breadcrumb):
|
|
22
|
-
message =
|
|
23
|
+
message = f"Spam potentiel dans le message"
|
|
23
24
|
if self.posted_by:
|
|
24
25
|
message += f" de [{self.posted_by.fullname}]({self.posted_by.external_url})"
|
|
25
26
|
|
|
26
27
|
if len(breadcrumb) != 2:
|
|
27
|
-
log.warning(
|
|
28
|
+
log.warning(
|
|
29
|
+
f"`spam_report_message` called on message with a breadcrumb of {len(breadcrumb)} elements.",
|
|
30
|
+
extra={"breadcrumb": breadcrumb},
|
|
31
|
+
)
|
|
28
32
|
return message
|
|
29
|
-
|
|
33
|
+
|
|
30
34
|
discussion = breadcrumb[0]
|
|
31
35
|
if not isinstance(discussion, Discussion):
|
|
32
|
-
log.warning(
|
|
36
|
+
log.warning(
|
|
37
|
+
f"`spam_report_message` called on message with a breadcrumb not containing a Discussion at index 0.",
|
|
38
|
+
extra={"breadcrumb": breadcrumb},
|
|
39
|
+
)
|
|
33
40
|
return message
|
|
34
41
|
|
|
35
42
|
message += f" sur la discussion « [{discussion.title}]({discussion.external_url}) »"
|
|
@@ -37,22 +44,18 @@ class Message(SpamMixin, db.EmbeddedDocument):
|
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
class Discussion(SpamMixin, db.Document):
|
|
40
|
-
user = db.ReferenceField(
|
|
47
|
+
user = db.ReferenceField("User")
|
|
41
48
|
subject = db.GenericReferenceField()
|
|
42
49
|
title = db.StringField(required=True)
|
|
43
50
|
discussion = db.ListField(db.EmbeddedDocumentField(Message))
|
|
44
51
|
created = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
45
52
|
closed = db.DateTimeField()
|
|
46
|
-
closed_by = db.ReferenceField(
|
|
53
|
+
closed_by = db.ReferenceField("User")
|
|
47
54
|
extras = db.ExtrasField()
|
|
48
55
|
|
|
49
56
|
meta = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'subject',
|
|
53
|
-
'-created'
|
|
54
|
-
],
|
|
55
|
-
'ordering': ['-created'],
|
|
57
|
+
"indexes": ["user", "subject", "-created"],
|
|
58
|
+
"ordering": ["-created"],
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
def person_involved(self, person):
|
|
@@ -64,8 +67,8 @@ class Discussion(SpamMixin, db.Document):
|
|
|
64
67
|
|
|
65
68
|
def texts_to_check_for_spam(self):
|
|
66
69
|
# Discussion should always have a first message but it's not the case in some tests…
|
|
67
|
-
return [self.title, self.discussion[0].content if len(self.discussion) else
|
|
68
|
-
|
|
70
|
+
return [self.title, self.discussion[0].content if len(self.discussion) else ""]
|
|
71
|
+
|
|
69
72
|
def embeds_to_check_for_spam(self):
|
|
70
73
|
return self.discussion[1:]
|
|
71
74
|
|
|
@@ -89,17 +92,15 @@ class Discussion(SpamMixin, db.Document):
|
|
|
89
92
|
|
|
90
93
|
@property
|
|
91
94
|
def external_url(self):
|
|
92
|
-
return self.subject.url_for(
|
|
93
|
-
|
|
94
|
-
_external=True)
|
|
95
|
-
|
|
95
|
+
return self.subject.url_for(_anchor="discussion-{id}".format(id=self.id), _external=True)
|
|
96
|
+
|
|
96
97
|
def spam_report_message(self, breadcrumb):
|
|
97
|
-
message =
|
|
98
|
+
message = f"Spam potentiel sur la discussion « [{self.title}]({self.external_url}) »"
|
|
98
99
|
if self.user:
|
|
99
100
|
message += f" de [{self.user.fullname}]({self.user.external_url})"
|
|
100
101
|
|
|
101
102
|
return message
|
|
102
|
-
|
|
103
|
+
|
|
103
104
|
@spam_protected()
|
|
104
105
|
def signal_new(self):
|
|
105
106
|
on_new_discussion.send(self)
|
|
@@ -1,32 +1,36 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from udata.features.notifications.actions import notifier
|
|
2
4
|
|
|
3
5
|
from .actions import discussions_for
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
|
|
8
7
|
log = logging.getLogger(__name__)
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
@notifier(
|
|
10
|
+
@notifier("discussion")
|
|
12
11
|
def discussions_notifications(user):
|
|
13
|
-
|
|
12
|
+
"""Notify user about open discussions"""
|
|
14
13
|
notifications = []
|
|
15
14
|
|
|
16
15
|
# Only fetch required fields for notification serialization
|
|
17
16
|
# Greatly improve performances and memory usage
|
|
18
|
-
qs = discussions_for(user).only(
|
|
17
|
+
qs = discussions_for(user).only("id", "created", "title", "subject")
|
|
19
18
|
|
|
20
19
|
# Do not dereference subject (so it's a DBRef)
|
|
21
20
|
# Also improve performances and memory usage
|
|
22
21
|
for discussion in qs.no_dereference():
|
|
23
|
-
notifications.append(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
notifications.append(
|
|
23
|
+
(
|
|
24
|
+
discussion.created,
|
|
25
|
+
{
|
|
26
|
+
"id": discussion.id,
|
|
27
|
+
"title": discussion.title,
|
|
28
|
+
"subject": {
|
|
29
|
+
"id": discussion.subject["_ref"].id,
|
|
30
|
+
"type": discussion.subject["_cls"].lower(),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
)
|
|
31
35
|
|
|
32
36
|
return notifications
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from udata.auth import Permission, UserNeed
|
|
2
|
-
|
|
3
2
|
from udata.core.organization.permissions import (
|
|
4
|
-
OrganizationAdminNeed,
|
|
3
|
+
OrganizationAdminNeed,
|
|
4
|
+
OrganizationEditorNeed,
|
|
5
5
|
)
|
|
6
6
|
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ class CloseDiscussionPermission(Permission):
|
|
|
10
10
|
needs = []
|
|
11
11
|
subject = discussion.subject
|
|
12
12
|
|
|
13
|
-
if getattr(subject,
|
|
13
|
+
if getattr(subject, "organization"):
|
|
14
14
|
needs.append(OrganizationAdminNeed(subject.organization.id))
|
|
15
15
|
needs.append(OrganizationEditorNeed(subject.organization.id))
|
|
16
16
|
elif subject.owner:
|
|
@@ -3,14 +3,14 @@ from blinker import Namespace
|
|
|
3
3
|
namespace = Namespace()
|
|
4
4
|
|
|
5
5
|
#: Trigerred when an discussion is created
|
|
6
|
-
on_new_discussion = namespace.signal(
|
|
6
|
+
on_new_discussion = namespace.signal("on-new-discussion")
|
|
7
7
|
|
|
8
8
|
#: Trigerred when a new comment is posted on an discussion
|
|
9
9
|
# (excluding creation and closing)
|
|
10
|
-
on_new_discussion_comment = namespace.signal(
|
|
10
|
+
on_new_discussion_comment = namespace.signal("on-new-discussion-comment")
|
|
11
11
|
|
|
12
12
|
#: Trigerred when an discussion is closed
|
|
13
|
-
on_discussion_closed = namespace.signal(
|
|
13
|
+
on_discussion_closed = namespace.signal("on-discussion-closed")
|
|
14
14
|
|
|
15
15
|
#: Trigerred when an discussion is deleted
|
|
16
|
-
on_discussion_deleted = namespace.signal(
|
|
16
|
+
on_discussion_deleted = namespace.signal("on-discussion-deleted")
|